Skip to main content

TCP / UDP Channels

Zeus exposes TCP and UDP transports as IChannel, so protocol and business code do not need to care whether the bytes came from a socket, serial port, or virtual channel.

TCP Client​

builder.AddTcpClient("plc", "192.168.1.10", 502);

Use the options overload for custom timeouts:

builder.AddTcpClient("plc", options =>
{
options.Host = "192.168.1.10";
options.Port = 502;
options.ConnectTimeoutMilliseconds = 2000;
});

Port 502 is common for Modbus TCP. Other binary protocols can use the same channel and layer Custom Framing or a protocol package on top.

TCP Server​

AddTcpServer listens locally. It fits devices or lower-level controllers that connect to the host application.

builder.AddTcpServer("listener", localPort: 1502);

If localPort is 0, the OS assigns a temporary port:

var listener = (TcpServerChannel)app.Channels.Get("listener");
await app.StartAsync();
Console.WriteLine(listener.LocalEndPoint?.Port);

Parameterless WriteAsync still replies to the most recent client. For multiple clients, use e.RemoteEndPoint or ISessionChannel:

listener.DataReceived += async (_, e) =>
{
if (e.RemoteEndPoint is { } remote)
{
await listener.WriteAsync(remote, e.Data);
return;
}

await listener.WriteAsync(e.Data);
};

Use BroadcastAsync to push to every connected client.

await listener.BroadcastAsync("SYNC"u8.ToArray());

BroadcastAsync observes cancellation. Caller cancellation is rethrown as OperationCanceledException, does not mark the server channel Faulted, and does not drop otherwise connected clients.

UDP Client​

AddUdpClient sends to and receives from a fixed remote endpoint:

builder.AddUdpClient("sensor", "192.168.1.20", 1502);

Use options to bind a fixed local port:

builder.AddUdpClient("sensor", options =>
{
options.Host = "192.168.1.20";
options.Port = 1502;
options.LocalPort = 1502;
});

UDP has no connection, delivery guarantee, ordering, or retransmission. Implement request timeouts and retries in the protocol layer when needed.

UDP Server​

builder.AddUdpServer("listener", localPort: 1502);

Parameterless WriteAsync still replies to the most recent remote endpoint. For multiple senders, use e.RemoteEndPoint. If no datagram has been received yet, Zeus cannot infer where to send a parameterless response.

JSON Examples​

{
"channels": [
{
"name": "plc",
"type": "tcp",
"options": {
"host": "192.168.1.10",
"port": 502
}
},
{
"name": "listener",
"type": "udp-server",
"options": {
"localAddress": "0.0.0.0",
"localPort": 1502
}
}
]
}

If connection or send/receive fails, check the remote listener, firewall, and local port conflicts. During early development, replace the channel with Virtual Channel.