TcpClient ne nasleđuje Component, pa se pravi omotač. Služi za sirovu vezu ka uređaju (Modbus TCP, štampači, sopstveni protokoli).
Property UseTls umota vezu u SslStream (isti TLS kao https):
Host mora da odgovara imenu u sertifikatu servera, inače provera pada.AllowUntrustedCertificate prihvata svaki sertifikat (self-signed na uređaju). Samo za testiranje: dok je uključen, veza nije zaštićena od presretanja.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);
}
}
}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
}