networking-demo

networking-demo.git
git clone git://git.lenczewski.org/networking-demo.git
Log | Files | Refs | README

Network.cs (5070B)


      1 using System;
      2 using System.Collections.Generic;
      3 using System.Net;
      4 using System.Net.Sockets;
      5 
      6 // for MemoryMarshall, to efficiently read and write buffers
      7 using System.Runtime.InteropServices;
      8 
      9 using NetworkingDemo.Packets;
     10 
     11 namespace NetworkingDemo {
     12 	internal enum ConnectionResult {
     13 		ReceivePackets,
     14 		CloseConnection,
     15 	}
     16 
     17 	internal delegate ConnectionResult PacketHandler(Memory<byte> buffer, Connection conn);
     18 
     19 	internal class Connection {
     20 		private readonly NetworkStream network;
     21 
     22 		private const int PacketHeaderLength = sizeof(PacketType) + sizeof(int);
     23 		private byte[] recvBuffer;
     24 		private int recvBufferOffset;
     25 
     26 		private readonly Dictionary<PacketType, PacketHandler> handlers;
     27 
     28 		internal Connection(NetworkStream network) {
     29 			this.network = network;
     30 
     31 			handlers = new Dictionary<PacketType, PacketHandler>();
     32 		}
     33 
     34 		internal Connection(NetworkStream network, Dictionary<PacketType, PacketHandler> handlers) {
     35 			this.network = network;
     36 
     37 			this.handlers = handlers;
     38 		}
     39 
     40 		internal void On(PacketType type, PacketHandler handler) {
     41 			handlers[type] = handler;
     42 		}
     43 
     44 		internal void Send<T>(T packet) where T : IPacket {
     45 			byte[] buffer = new byte[packet.MaxSerializedLength()];
     46 			int length = packet.SerializeInto(buffer);
     47 
     48 			int raw_type = IPAddress.HostToNetworkOrder((int) packet.Type());
     49 			int raw_length = IPAddress.HostToNetworkOrder(length);
     50 
     51 			Span<byte> header = stackalloc byte[PacketHeaderLength];
     52 			MemoryMarshal.Write(header.Slice(0, sizeof(int)), ref raw_type);
     53 			MemoryMarshal.Write(header.Slice(sizeof(int), sizeof(int)), ref raw_length);
     54 
     55 			network.Write(header);
     56 
     57 			network.Write(buffer, 0, length);
     58 		}
     59 
     60 		internal void BeginRecv() {
     61 			recvBuffer = new byte[PacketHeaderLength];
     62 			recvBufferOffset = 0;
     63 
     64 			network.BeginRead(recvBuffer, 0, PacketHeaderLength, OnRecvHeader, null);
     65 		}
     66 
     67 		private void OnRecvHeader(IAsyncResult result) {
     68 			int bytes_read = network.EndRead(result);
     69 			if (bytes_read <= 0) {
     70 				Console.WriteLine("Error! Zero bytes read when receiving header!");
     71 				return;
     72 			}
     73 
     74 			recvBufferOffset += bytes_read;
     75 			if (recvBufferOffset < recvBuffer.Length) {
     76 				network.BeginRead(recvBuffer, recvBufferOffset,
     77 						  recvBuffer.Length - recvBufferOffset,
     78 						  OnRecvHeader, null);
     79 				return;
     80 			}
     81 
     82 			Span<byte> header = recvBuffer;
     83 
     84 			int raw_type = MemoryMarshal.Read<int>(header.Slice(0, sizeof(int)));
     85 			int raw_length = MemoryMarshal.Read<int>(header.Slice(sizeof(int), sizeof(int)));
     86 
     87 			PacketType type = (PacketType) IPAddress.NetworkToHostOrder(raw_type);
     88 			int length = IPAddress.NetworkToHostOrder(raw_length);
     89 
     90 			recvBuffer = new byte[length];
     91 			recvBufferOffset = 0;
     92 
     93 			network.BeginRead(recvBuffer, 0, length, OnRecvBody, type);
     94 		}
     95 
     96 		private void OnRecvBody(IAsyncResult result) {
     97 			int bytes_read = network.EndRead(result);
     98 			if (bytes_read <= 0) {
     99 				Console.WriteLine("Error! Zero bytes read when receiving body!");
    100 				return;
    101 			}
    102 
    103 			recvBufferOffset += bytes_read;
    104 			if (recvBufferOffset < recvBuffer.Length) {
    105 				network.BeginRead(recvBuffer, recvBufferOffset,
    106 						  recvBuffer.Length - recvBufferOffset,
    107 						  OnRecvBody, result.AsyncState);
    108 				return;
    109 			}
    110 
    111 			PacketType type = (PacketType) result.AsyncState!;
    112 			ConnectionResult handlerResult = handlers[type](recvBuffer, this);
    113 
    114 			switch (handlerResult) {
    115 				case ConnectionResult.ReceivePackets:
    116 					BeginRecv();
    117 					break;
    118 
    119 				case ConnectionResult.CloseConnection:
    120 					network.Close();
    121 					break;
    122 			}
    123 		}
    124 	}
    125 
    126 	internal class Server {
    127 		private readonly TcpListener listener;
    128 
    129 		private const int MaxConcurrentClients = 16;
    130 
    131 		private int nextClientId;
    132 		private readonly Dictionary<int, Connection> clients;
    133 
    134 		private readonly Dictionary<PacketType, PacketHandler> handlers;
    135 
    136 		internal Server(IPAddress addr, int port) {
    137 			listener = new TcpListener(addr, port);
    138 
    139 			nextClientId = 0;
    140 			clients = new Dictionary<int, Connection>();
    141 
    142 			handlers = new Dictionary<PacketType, PacketHandler>();
    143 		}
    144 
    145 		internal void On(PacketType type, PacketHandler handler) {
    146 			handlers[type] = handler;
    147 		}
    148 
    149 		internal void Start() {
    150 			listener.Start();
    151 
    152 			listener.BeginAcceptTcpClient(OnAcceptClient, null);
    153 		}
    154 
    155 		private void OnAcceptClient(IAsyncResult result) {
    156 			TcpClient peer = listener.EndAcceptTcpClient(result);
    157 			listener.BeginAcceptTcpClient(OnAcceptClient, null);
    158 
    159 			if (clients.Count == MaxConcurrentClients) {
    160 				peer.Close();
    161 				peer.Dispose();
    162 				return;
    163 			}
    164 
    165 			int clientId = nextClientId++;
    166 			clients[clientId] = new Connection(peer.GetStream(), handlers);
    167 			Handle(clients[clientId]);
    168 		}
    169 
    170 		private void Handle(Connection connection) {
    171 			// send the welcome packet
    172 			connection.Send(new WelcomePacket("Hello!"));
    173 
    174 			// start waiting on responses
    175 			connection.BeginRecv();
    176 		}
    177 	}
    178 
    179 	internal class Client {
    180 		private readonly TcpClient connection;
    181 
    182 		internal Client() {
    183 			connection = new TcpClient();
    184 		}
    185 
    186 		internal Connection ConnectTo(IPAddress addr, int port) {
    187 			connection.Connect(addr, port);
    188 			return new Connection(connection.GetStream());
    189 		}
    190 	}
    191 }