-
Notifications
You must be signed in to change notification settings - Fork 1
Networking Getting started
First of all we need to create our custom server/client/connection. It's necessary to take control over a connection.
Let's make a simple chat
public class MyTcpServer : TcpServer
{
public MyTcpServer(TcpConfigurationServer configuration) : base(configuration)
{
}
protected override TcpConnection CreateConnection()
{
return new MyTcpConnection(this);
}
}
public class MyTcpClient : TcpClient
{
public MyTcpClient(TcpConfigurationClient configuration) : base(configuration)
{
}
protected override TcpConnection CreateConnection()
{
return new MyTcpConnection(this);
}
}
public class MyTcpConnection : TcpConnection
{
public MyTcpConnection(TcpPeer peer) : base(peer)
{
}
protected override void OnMessageReceived(MessageEventArgs args)
{
using (args)
{
string nickname = args.Message.ReadString();
string message = args.Message.ReadString();
logger.Info($"[{Parent}] [RECEIVED] {nickname}: {message}");
if (!IsClientConnection)
_ = SendChatMessage("Server", "Message accepted");
}
base.OnMessageReceived(args);
}
public async Task SendChatMessage(string nickname, string message)
{
using (RawMessage newMsg = Parent.CreateMessage())
{
newMsg.Write(nickname);
newMsg.Write(message);
await this.SendMessageAsync(newMsg);
logger.Info($"[{Parent}] [SENT] {nickname}: {message}");
}
}
}
You may ask the question "Why is sending a message is asynchronous operation, but receiving is not". The answer is performance. When we receiving a message we are striving to make the network thread ASAP, to get the next message, but sending is a blocking operation, we don't want anything to block the receiving thread. Sending a message may take some (especially if connection simulation is enabled), so it's asynchronous.
Remember, you're always responsible for disposing incoming and outgoing messages. If you won't do it, it will make additional stress on GC
Let's create client/server and make a test
TcpConfigurationServer configurationServer = new TcpConfigurationServer();
configurationServer.LogManager = new LogManager(LogSeverity.INFO,
new LoggingHandlerConsole(new LoggingFormatterDefault()));
MyTcpServer server = new MyTcpServer(configurationServer);
server.Start();
server.Listen(10000); //Listen is a non blocking method
TcpConfigurationClient configurationClient = new TcpConfigurationClient();
configurationClient.LogManager = new LogManager(LogSeverity.INFO,
new LoggingHandlerConsole(new LoggingFormatterDefault()));
MyTcpClient client = new MyTcpClient(configurationClient);
client.Start();
await client.ConnectAsync("127.0.0.1", 10000);
await (client.Connection as MyTcpConnection)?.SendChatMessage("Client", "Hello")!;
so output:
[2022-08-25 17:38:47.587] [TcpClient] [INFO] TcpClient status changed from Disconnected to Connecting
[2022-08-25 17:38:47.616] [TcpClient] [INFO] Starting the connection to 127.0.0.1:10000
[2022-08-25 17:38:47.669] [TcpConnection] [INFO] #1 initialized
[2022-08-25 17:38:47.669] [TcpConnection] [INFO] #1 initialized
[2022-08-25 17:38:47.678] [TcpClient] [INFO] TcpClient status changed from Connecting to Connected
[2022-08-25 17:38:47.779] [TcpConnection] [INFO] [Neon.Test.Tcp.MyTcpClient] [SENT] Client: Hello
[2022-08-25 17:38:47.787] [TcpConnection] [INFO] [Neon.Test.Tcp.MyTcpServer] [RECEIVED] Client: Hello
[2022-08-25 17:38:47.792] [TcpConnection] [INFO] [Neon.Test.Tcp.MyTcpServer] [SENT] Server: Message accepted
[2022-08-25 17:38:47.794] [TcpConnection] [INFO] [Neon.Test.Tcp.MyTcpClient] [RECEIVED] Server: Message accepted
TcpClient
, TcpServer
, UdpClient
, UdpServer
, TcpConnection
, UdpConnection
have protected virtual method to create a connection and control it's state
-
CreateConnection
- this method is not synchronized with a context -
OnConnectionOpened
- this method is synchronized with a context -
OnConnectionClosed
- this method is synchronized with a context
TcpClient
, UdpClient
have status change protected virtual method
-
OnClientStatusChanged
- this method is synchronized with a context
TcpServer
, UdpServer
have method to filter incoming connections
-
OnAcceptConnection
- if method returnsfalse
connection will be dropped before opening. This method is not synchronized with a context. Returnstrue
by default
All the calls of events and RPC methods are synchronized with your context (if set), see more
UDP works very similar to TCP, it just gives you some options how to send messages
DeliveryType
-
Unreliable
- the other side may not receive the message, or receive it multiple times. -
UnreliableSequenced
- every message have sequence number increased by 1 every time. The other side will drop any messages where sequence number less or equals the last one. This method could help you to get rid of old and duplicate messages. Game servers often use it for movement synchronization. -
ReliableUnordered
- the channel with reliable delivery, but messages may come in different order. -
ReliableOrdered
- this delivery method completely repeats TCP, messages come reliably in the same order
Channels
Every delivery type has 7 channels (0-7) to split traffic. For example UnreliableSequenced
messages will have different sequences in different channels.