Lightweight and simple TCP client-server .NET library with RMI support.
dotnet add package TagBites.Net
Targets netstandard2.0 and net7.0. The only dependency is Newtonsoft.Json, plus System.Reflection.DispatchProxy on netstandard2.0.
A console chat. Every client sends text lines to the server, and the server broadcasts each message to the other clients, together with connect and disconnect notifications.
var client = new Client("127.0.0.1", 8200);
client.Received += (s, e) => Console.WriteLine(e.Message.ToString());
await client.ConnectAsync();
while (Console.ReadLine() is { } message)
await client.SendAsync(message);var server = new Server("127.0.0.1", 8200);
server.ClientConnected += async (s, e) => await server.SendToAllAsync($"{e.Client} connected", e.Client);
server.ClientDisconnected += async (s, e) => await server.SendToAllAsync($"{e.Client} disconnected", e.Client);
server.Received += async (s, e) => await server.SendToAllAsync($"{e.Client}: {e.Message}", e.Client);
server.Listening = true; // starts a new thread
Console.ReadLine(); // for console application to prevent app from closingIn this example a string type is used for communication, but any serializable objects can be sent/received.
By default Newtonsoft.Json is used for serialization, but it can be replaced with a custom implementation.
Full example in this repository: samples/Chat.
The same chat, written as method calls instead of messages. Both sides share two interfaces: the client calls IChatServer.Send on the server, and the server calls IChatClient.OnMessage on every client. Serialization and dispatch happen behind the proxy returned by GetController<T>().
var client = new Client("127.0.0.1", 10500);
client.Use<IChatClient, ChatClient>();
await client.ConnectAsync();
while (Console.ReadLine() is { } message)
client.GetController<IChatServer>().Send(message);The method Use<TControllerInterface, TController>() registers a controller that can be used by the server. The server side can use the IChatClient interface to execute methods implemented by ChatClient on the client's side. The client/server can register many controllers. The controller instance will be created on first use.
GetController<T>() returns a proxy implementing the interface registered on the remote side. Calling a method on it invokes that method on the remote side. A controller method takes primitive or serializable parameters and returns void, Task, or any primitive or serializable type.
var server = new Server("127.0.0.1", 10500);
server.Use<IChatServer, ChatServer>(client => new ChatServer { Client = client, Server = server });
server.ClientConnected += (s, e) =>
{
foreach (var client in server.GetClients())
if (client != e.Client)
client.GetController<IChatClient>().OnMessage(null, $"Client {e.Client.Identity} connected");
Console.WriteLine($"Client {e.Client.Identity} connected.");
};
server.ClientDisconnected += (s, e) =>
{
foreach (var client in server.GetClients())
client.GetController<IChatClient>().OnMessage(null, $"Client {e.Client.Identity} disconnected");
Console.WriteLine($"Client {e.Client.Identity} disconnected.");
};
server.Listening = true;
Console.ReadLine();public interface IChatClient
{
void OnMessage(string userName, string message);
}
public class ChatClient : IChatClient
{
public void OnMessage(string userName, string message)
{
Console.WriteLine(userName == null ? message : $"{userName}: {message}");
}
}
public interface IChatServer
{
void Send(string message);
}
public class ChatServer : IChatServer
{
public Server Server { get; set; }
public ServerClient Client { get; set; }
public void Send(string message)
{
Console.WriteLine($"{Client.Identity}: {message}");
foreach (var client in Server.GetClients())
if (Client != client)
client.GetController<IChatClient>().OnMessage(Client.Identity?.ToString(), message);
}
}Full example in this repository: samples/ChatWithControllers.
There is no message size limit and no rate limiting, so both sides have to be trusted. Authentication is a callback, not a protocol, and credentials travel in plain text unless the connection uses a certificate.