TCP i TLS kao komponenta

↑ Nazad

TcpClient ne nasleđuje Component, pa se pravi omotač. Služi za sirovu vezu ka uređaju (Modbus TCP, štampači, sopstveni protokoli).

TLS: ono što https koristi ispod HTTP-a

Property UseTls umota vezu u SslStream (isti TLS kao https):

Eventi

Connected, Disconnected, DataReceived, Error stižu na UI niti (preko SynchronizationContext), pa u handleru nema Invoke.

TcpClientComponent.cs

using System.ComponentModel;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;

namespace WinFormsApp_test
{
    /// <summary>
    ///  TcpClient kao non-visual komponenta, sa opcionim TLS-om (kao kod https-a).
    /// </summary>
    [ToolboxItem(true)]
    [DesignerCategory("Code")]
    [DefaultEvent(nameof(DataReceived))]
    [DefaultProperty(nameof(Host))]
    public class TcpClientComponent : Component
    {
        private TcpClient? _client;
        private Stream? _stream;
        private readonly SynchronizationContext? _ui = SynchronizationContext.Current;

        public TcpClientComponent() { }

        public TcpClientComponent(IContainer container)
        {
            container.Add(this);
        }

        [Category("Connection"), Description("IP adresa ili ime hosta.")]
        [DefaultValue("127.0.0.1")]
        public string Host { get; set; } = "127.0.0.1";

        [Category("Connection"), Description("TCP port (npr. 443 za TLS).")]
        [DefaultValue(502)]
        public int Port { get; set; } = 502;

        [Category("Security"), Description("Šifrovana veza (TLS), isto što https koristi ispod HTTP-a.")]
        [DefaultValue(false)]
        public bool UseTls { get; set; }

        [Category("Security"), Description("Prihvati i nevažeći/self-signed sertifikat. Samo za testiranje!")]
        [DefaultValue(false)]
        public bool AllowUntrustedCertificate { get; set; }

        [Category("Data"), DefaultValue("ASCII"), Description("ASCII ili UTF-8.")]
        public string EncodingName { get; set; } = "ASCII";

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public bool IsConnected => _client?.Connected ?? false;

        [Category("Data")]
        public event EventHandler<string>? DataReceived;

        [Category("Connection")]
        public event EventHandler? Connected;

        [Category("Connection")]
        public event EventHandler? Disconnected;

        [Category("Connection")]
        public event EventHandler<Exception>? Error;

        private Encoding TextEncoding =>
            EncodingName.Equals("UTF-8", StringComparison.OrdinalIgnoreCase) ? Encoding.UTF8 : Encoding.ASCII;

        public async Task ConnectAsync()
        {
            Disconnect();
            try
            {
                _client = new TcpClient();
                await _client.ConnectAsync(Host, Port);
                Stream stream = _client.GetStream();

                if (UseTls)
                {
                    var ssl = AllowUntrustedCertificate
                        ? new SslStream(stream, false, (_, _, _, _) => true)
                        : new SslStream(stream, false);
                    // Host mora da odgovara imenu u sertifikatu servera
                    await ssl.AuthenticateAsClientAsync(Host);
                    stream = ssl;
                }

                _stream = stream;
                Raise(() => Connected?.Invoke(this, EventArgs.Empty));
                _ = ReadLoopAsync(stream);
            }
            catch (Exception ex)
            {
                Disconnect();
                Raise(() => Error?.Invoke(this, ex));
            }
        }

        public async Task SendAsync(string text)
        {
            if (_stream == null) throw new InvalidOperationException("Nije povezano.");
            byte[] data = TextEncoding.GetBytes(text);
            await _stream.WriteAsync(data);
            await _stream.FlushAsync();
        }

        public void Disconnect()
        {
            _stream?.Dispose();
            _client?.Dispose();
            _stream = null;
            _client = null;
        }

        private async Task ReadLoopAsync(Stream stream)
        {
            var buf = new byte[4096];
            try
            {
                int n;
                while ((n = await stream.ReadAsync(buf)) > 0)
                {
                    string text = TextEncoding.GetString(buf, 0, n);
                    Raise(() => DataReceived?.Invoke(this, text));
                }
            }
            catch (Exception ex) when (ex is IOException or ObjectDisposedException)
            {
                // veza prekinuta ili zatvorena preko Disconnect()
            }
            Raise(() => Disconnected?.Invoke(this, EventArgs.Empty));
        }

        // Event se diže na UI niti → u handleru slobodno menjaš kontrole
        private void Raise(Action a)
        {
            if (_ui != null) _ui.Post(_ => a(), null); else a();
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing) Disconnect();
            base.Dispose(disposing);
        }
    }
}

Upotreba

private async void btnConnect_Click(object sender, EventArgs e)
{
    await tcpClientComponent1.ConnectAsync();
}

private async void btnSend_Click(object sender, EventArgs e)
{
    await tcpClientComponent1.SendAsync(txtSend.Text + "\r\n");
}

private void tcpClientComponent1_DataReceived(object sender, string text)
{
    txtLog.AppendText(text);   // već na UI niti
}

Izvor: 03-tcp-i-tls.md