public bool Send(byte[] data) { if (Connected) { // respect max message size to avoid allocation attacks. if (data.Length <= MaxMessageSize) { // add to send queue and return immediately. // calling Send here would be blocking (sometimes for long times // if other side lags or wire was disconnected) sendQueue.Enqueue(data); sendPending.Set(); // interrupt SendThread WaitOne() return(true); } Logger.LogError("Client.Send: message too big: " + data.Length + ". Limit: " + MaxMessageSize); return(false); } Logger.LogWarning("Client.Send: not connected!"); 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(); }