Quantcast
Channel: Asynchronous TCP network server with a packet system - Code Review Stack Exchange
Viewing all articles
Browse latest Browse all 2

Asynchronous TCP network server with a packet system

$
0
0

So I'm writing a game emulator and would like some advice after finishing the networking. It is designed to accept multiple connections, and process messages from all of them.

Here is just a brief introduction to the packets and how they are structured. [lengthOfString:short][stringEncodedInUtf8:Byte[]]

Let's start with the NetworkHandler - this class is responsible for accepting new connections and storing them in the collection.

public class NetworkHandler : IDisposable{    private readonly TcpListener _listener;    private readonly IList<NetworkClient> _clients;    private readonly ClientPacketHandler _packetHandler;    public NetworkHandler(TcpListener listener, IList<NetworkClient> clients, ClientPacketHandler packetHandler)    {        _listener = listener;        _clients = clients;        _packetHandler = packetHandler;    }    public void StartListener()    {        _listener.Start();    }    public async Task ListenAsync()    {        while (true)        {            var tcpClient = await _listener.AcceptTcpClientAsync();            var networkClient = new NetworkClient(tcpClient, _packetHandler);            _clients.Add(networkClient);            networkClient.StartReceiving();        }    }    public void Dispose()    {        foreach (var client in _clients)        {            client.Dispose();        }        _listener.Stop();    }}

Then we have the NetworkClient, I made this so NetworkHandler could stay small and to follow SRP - This class handles incoming data from the individual connection (client).

public class NetworkClient{    private readonly TcpClient _tcpClient;    private readonly NetworkStream _networkStream;    private readonly ClientPacketHandler _packetHandler;    public NetworkClient(TcpClient tcpClient, ClientPacketHandler packetHandler)    {        _tcpClient = tcpClient;        _networkStream = tcpClient.GetStream();        _packetHandler = packetHandler;    }    public void StartReceiving()    {        Task.Run(ProcessDataAsync);    }    private async Task ProcessDataAsync()    {        while (true)        {            using var br = new BinaryReader(new MemoryStream(await GetBinaryDataAsync()));            var messageLength = BinaryPrimitives.ReadInt32BigEndian(br.ReadBytes(4));            var packetData = br.ReadBytes(messageLength);            using var br2 = new BinaryReader(new MemoryStream(packetData));            var packetId = BinaryPrimitives.ReadInt16BigEndian(br2.ReadBytes(2));            if (packetId == 26979)            {                await WriteToStreamAsync(Encoding.Default.GetBytes("<?xml version=\"1.0\"?>\r\n<!DOCTYPE cross-domain-policy SYSTEM \"/xml/dtds/cross-domain-policy.dtd\">\r\n<cross-domain-policy>\r\n<policy-file-request/><allow-access-from domain=\"*\" to-ports=\"*\" />\r\n</cross-domain-policy>\0)"));            }            else            {                if (!_packetHandler.TryGetPacket(packetId, out var packet))                {                    Console.WriteLine("Unhandled packet: "+ packetId);                    return;                }                packet.Process(this, new ClientPacketReader(packetData));            }        }    }    private async Task<byte[]> GetBinaryDataAsync()    {        var buffer = new byte[2048];        var memoryStream = new MemoryStream();        var bytesRead = await _networkStream.ReadAsync(buffer, 0, buffer.Length);        while (bytesRead > 0)        {            memoryStream.Write(buffer, 0, buffer.Length);            bytesRead = await memoryStream.ReadAsync(buffer, 0, buffer.Length);        }        return memoryStream.ToArray();    }    private async Task WriteToStreamAsync(byte[] data)    {        await _networkStream.WriteAsync(data, 0, data.Length);    }    public void Dispose()    {        _tcpClient.Dispose();    }}

ClientPacketHandler - fairly straight forward

public class ClientPacketHandler{    private readonly Dictionary<int, IClientPacket> _packets;    public ClientPacketHandler(Dictionary<int, IClientPacket> packets)    {        _packets = packets;    }    public bool TryGetPacket(int packetId, out IClientPacket packet)    {        return _packets.TryGetValue(packetId, out packet);    }}

ClientPackerReader - this will be used to read data from the packet. I feel like this class could be improved by using some built in helper type?

public class ClientPacketReader{    private readonly byte[] _packetData;    private int _packetPosition;    public ClientPacketReader(byte[] packetData)    {        _packetData = packetData ?? new byte[0];    }    public string ReadString() => Encoding.Default.GetString(ReadFromLength());    private byte[] ReadFromLength() => ReadBytes(BinaryPrimitives.ReadInt16BigEndian(ReadBytes(2)));    private byte[] ReadBytes(int bytes)    {        var data = new byte[bytes];        for (var i = 0; i < bytes; i++)        {            data[i] = _packetData[_packetPosition++];        }        return data;    }}

Lastly I want to show you an example packet file

public class ExamplePacket : IClientPacket{    public void Process(NetworkClient client, ClientPacketReader reader)    {            Console.WriteLine("Fetch packet data: "+ reader.ReadString());    }}

Viewing all articles
Browse latest Browse all 2

Latest Images

Trending Articles





Latest Images