public void Dispose() { if (Buffer != null) { ByteArrayPool.Return(Buffer); } }
// send message (via stream) with the <size,content> message structure protected static bool SendMessage(NetworkStream stream, byte[] content) { // can we still write to this socket (not disconnected?) if (!stream.CanWrite) { Logger.LogWarning("Send: stream not writeable: " + stream); return(false); } // check size if (content.Length > ushort.MaxValue) { Logger.LogError("Send: message too big(" + content.Length + ") max=" + ushort.MaxValue); return(false); } // stream.Write throws exceptions if client sends with high // frequency and the server stops try { // write header+content at once via payload array. writing // header,payload separately would cause 2 TCP packets to be // sent if nagle's algorithm is disabled(2x TCP header overhead) byte[] payload = ByteArrayPool.Take(); // construct header (size) UShortToBytes((ushort)content.Length, payload); Array.Copy(content, 0, payload, 2, content.Length); stream.Write(payload, 0, 2 + content.Length); ByteArrayPool.Return(payload); // flush to make sure it is being sent immediately stream.Flush(); return(true); } catch (Exception exception) { // log as regular message because servers do shut down sometimes Logger.Log("Send: stream.Write exception: " + exception); return(false); } }
// thread receive function is the same for client and server's clients // (static to reduce state for maximum reliability) protected static void ReceiveLoop(int connectionId, TcpClient client, SafeQueue <Message> messageQueue) { // get NetworkStream from client NetworkStream stream = client.GetStream(); // keep track of last message queue warning DateTime messageQueueLastWarning = DateTime.Now; // absolutely must wrap with try/catch, otherwise thread exceptions // are silent try { // add connected event to queue with ip address as data in case // it's needed messageQueue.Enqueue(new Message(connectionId, EventType.Connected, null, 0)); // let's talk about reading data. // -> normally we would read as much as possible and then // extract as many <size,content>,<size,content> messages // as we received this time. this is really complicated // and expensive to do though // -> instead we use a trick: // Read(2) -> size // Read(size) -> content // repeat // Read is blocking, but it doesn't matter since the // best thing to do until the full message arrives, // is to wait. // => this is the most elegant AND fast solution. // + no resizing // + no extra allocations, just one for the content // + no crazy extraction logic while (true) { byte[] content = ByteArrayPool.Take(); // read the next message (blocking) or stop if stream closed if (!ReadMessageBlocking(stream, content, out int size)) { break; } // queue it messageQueue.Enqueue(new Message(connectionId, EventType.Data, content, size)); // it is now up to the developer to Dispose the message to return the // buffer to the pool. // and show a warning if the queue gets too big // -> we don't want to show a warning every single time, // because then a lot of processing power gets wasted on // logging, which will make the queue pile up even more. // -> instead we show it every 10s, so that the system can // use most it's processing power to hopefully process it. if (messageQueue.Count > MessageQueueSizeWarning) { TimeSpan elapsed = DateTime.Now - messageQueueLastWarning; if (elapsed.TotalSeconds > 10) { Logger.LogWarning("ReceiveLoop: messageQueue is getting big(" + messageQueue.Count + "), try calling GetNextMessage more often. You can call it more than once per frame!"); messageQueueLastWarning = DateTime.Now; } } } } catch (Exception exception) { // something went wrong. the thread was interrupted or the // connection closed or we closed our own connection or ... // -> either way we should stop gracefully Logger.Log("ReceiveLoop: finished receive function for connectionId=" + connectionId + " reason: " + exception); } // if we got here then either the client while loop ended, or an // exception happened. disconnect messageQueue.Enqueue(new Message(connectionId, EventType.Disconnected, null, 0)); // clean up no matter what stream.Close(); client.Close(); }