Ejemplo n.º 1
0
        // start listening for new connections in a background thread and spawn
        // a new thread for each one.
        public bool Start(int port)
        {
            // not if already started
            if (Active)
            {
                return(false);
            }

            // create receive pipe with max message size for pooling
            // => create new pipes every time!
            //    if an old receive thread is still finishing up, it might still
            //    be using the old pipes. we don't want to risk any old data for
            //    our new start here.
            receivePipe = new MagnificentReceivePipe(MaxMessageSize);

            // start the listener thread
            // (on low priority. if main thread is too busy then there is not
            //  much value in accepting even more clients)
            Log.Info("Server: Start port=" + port);
            listenerThread = new Thread(() => { Listen(port); });
            listenerThread.IsBackground = true;
            listenerThread.Priority     = ThreadPriority.BelowNormal;
            listenerThread.Start();
            return(true);
        }
Ejemplo n.º 2
0
        public ConnectionState(TcpClient client, int MaxMessageSize)
        {
            this.client = client;

            // create send pipe with max message size for pooling
            receivePipe = new MagnificentReceivePipe(MaxMessageSize);
            sendPipe    = new MagnificentSendPipe(MaxMessageSize);
        }
Ejemplo n.º 3
0
        // thread receive function is the same for client and server's clients
        public static void ReceiveLoop(int connectionId, TcpClient client, int MaxMessageSize, MagnificentReceivePipe receivePipe, int queueLimit)
        {
            // get NetworkStream from client
            NetworkStream stream = client.GetStream();

            // every receive loop needs it's own receive buffer of
            // HeaderSize + MaxMessageSize
            // to avoid runtime allocations.
            //
            // IMPORTANT: DO NOT make this a member, otherwise every connection
            //            on the server would use the same buffer simulatenously
            byte[] receiveBuffer = new byte[4 + MaxMessageSize];

            // avoid header[4] allocations
            //
            // IMPORTANT: DO NOT make this a member, otherwise every connection
            //            on the server would use the same buffer simulatenously
            byte[] headerBuffer = new byte[4];

            // absolutely must wrap with try/catch, otherwise thread exceptions
            // are silent
            try
            {
                // set connected event in pipe
                receivePipe.SetConnected();

                // 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)
                {
                    // read the next message (blocking) or stop if stream closed
                    if (!ReadMessageBlocking(stream, MaxMessageSize, headerBuffer, receiveBuffer, out int size))
                    {
                        // break instead of return so stream close still happens!
                        break;
                    }

                    // create arraysegment for the read message
                    ArraySegment <byte> message = new ArraySegment <byte>(receiveBuffer, 0, size);

                    // send to main thread via pipe
                    // -> it'll copy the message internally so we can reuse the
                    //    receive buffer for next read!
                    receivePipe.Enqueue(message);

                    // disconnect if receive pipe gets too big.
                    // -> avoids ever growing queue memory if network is slower
                    //    than input
                    // -> disconnecting is great for load balancing. better to
                    //    disconnect one connection than risking every
                    //    connection / the whole server
                    if (receivePipe.Count >= queueLimit)
                    {
                        // log the reason
                        Log.Warning($"receivePipe reached limit of {queueLimit}. This can happen if network messages come in way faster than we manage to process them. Disconnecting this connection for load balancing.");

                        // clear queue so the final disconnect message will be
                        // processed immediately. no need to process thousands
                        // of pending messages before disconnecting.
                        // it would just delay it for quite some time.
                        receivePipe.Clear();

                        // just break. the finally{} will close everything.
                        break;
                    }
                }
            }
            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
                Log.Info("ReceiveLoop: finished receive function for connectionId=" + connectionId + " reason: " + exception);
            }
            finally
            {
                // clean up no matter what
                stream.Close();
                client.Close();

                // set 'Disconnected' event in pipe after disconnecting properly
                // -> always AFTER closing the streams to avoid a race condition
                //    where Disconnected -> Reconnect wouldn't work because
                //    Connected is still true for a short moment before the stream
                //    would be closed.
                receivePipe.SetDisconnected();
            }
        }
Ejemplo n.º 4
0
 // constructor always creates new TcpClient for client connection!
 public ClientConnectionState(int MaxMessageSize) : base(new TcpClient(), MaxMessageSize)
 {
     // create receive pipe with max message size for pooling
     receivePipe = new MagnificentReceivePipe(MaxMessageSize);
 }
Ejemplo n.º 5
0
        public int Tick(int processLimit, Func <bool> checkEnabled = null)
        {
            int remaining = 0;

            // for each connection
            // checks enabled in case a Mirror scene message arrived
            foreach (KeyValuePair <int, ConnectionState> kvp in clients)
            {
                MagnificentReceivePipe receivePipe = kvp.Value.receivePipe;

                // need a processLimit copy just for this connection so that
                // we can count the Connected message as a processed one.
                // => otherwise decreasing the limit in Connected event would
                //    decrease the limit for everyone!
                int connectionProcessLimit = processLimit;

                // always process connect FIRST before anything else
                if (connectionProcessLimit > 0)
                {
                    if (receivePipe.CheckConnected())
                    {
                        OnConnected?.Invoke(kvp.Key);
                        // it counts as a processed message
                        --connectionProcessLimit;
                    }
                }

                // process up to 'processLimit' messages for this connection
                // checks enabled in case a Mirror scene message arrived
                for (int i = 0; i < connectionProcessLimit; ++i)
                {
                    // check enabled in case a Mirror scene message arrived
                    if (checkEnabled != null && !checkEnabled())
                    {
                        break;
                    }

                    // peek first. allows us to process the first queued entry while
                    // still keeping the pooled byte[] alive by not removing anything.
                    if (receivePipe.TryPeek(out ArraySegment <byte> message))
                    {
                        OnData?.Invoke(kvp.Key, message);

                        // IMPORTANT: now dequeue and return it to pool AFTER we are
                        //            done processing the event.
                        receivePipe.TryDequeue();
                    }

                    // AFTER PROCESSING, add remaining ones to our counter
                    remaining += receivePipe.Count;
                }

                // always process disconnect AFTER anything else
                // (should never process data messages after disconnect message)
                if (connectionProcessLimit > 0)
                {
                    if (receivePipe.CheckDisconnected())
                    {
                        OnDisconnected?.Invoke(kvp.Key);
                        connectionsToRemove.Add(kvp.Key);
                    }
                }
            }

            // remove all disconnected connections now that we processed the
            // final disconnect message.
            foreach (int connectionId in connectionsToRemove)
            {
                clients.TryRemove(connectionId, out ConnectionState _);
            }
            connectionsToRemove.Clear();

            return(remaining);
        }