// the listener thread's listen function // note: no maxConnections parameter. high level API should handle that. // (Transport can't send a 'too full' message anyway) void Listen(int port) { // absolutely must wrap with try/catch, otherwise thread // exceptions are silent try { // start listener on all IPv4 and IPv6 address via .Create listener = TcpListener.Create(port); listener.Server.NoDelay = NoDelay; listener.Server.SendTimeout = SendTimeout; listener.Server.ReceiveTimeout = ReceiveTimeout; listener.Start(); Log.Info("Server: listening port=" + port); // keep accepting new clients while (true) { try { // wait and accept new client // note: 'using' sucks here because it will try to // dispose after thread was started but we still need it // in the thread TcpClient client = listener.AcceptTcpClient(); // set socket options client.NoDelay = NoDelay; client.SendTimeout = SendTimeout; client.ReceiveTimeout = ReceiveTimeout; // generate the next connection id (thread safely) int connectionId = NextConnectionId(); // add to dict immediately ConnectionState connection = new ConnectionState(client, MaxMessageSize); clients[connectionId] = connection; // spawn a send thread for each client Thread sendThread = new Thread(() => { // wrap in try-catch, otherwise Thread exceptions // are silent try { // run the send loop // IMPORTANT: DO NOT SHARE STATE ACROSS MULTIPLE THREADS! ThreadFunctions.SendLoop(connectionId, client, connection.sendPipe, connection.sendPending); } catch (ThreadAbortException) { // happens on stop. don't log anything. // (we catch it in SendLoop too, but it still gets // through to here when aborting. don't show an // error.) } catch (Exception exception) { Log.Error("Server send thread exception: " + exception); } }); sendThread.IsBackground = true; sendThread.Start(); // spawn a receive thread for each client Thread receiveThread = new Thread(() => { // wrap in try-catch, otherwise Thread exceptions // are silent try { // run the receive loop // (receive pipe is shared across all loops) ThreadFunctions.ReceiveLoop(connectionId, client, MaxMessageSize, receivePipe, ReceiveQueueLimit); // IMPORTANT: do NOT remove from clients after the // thread ends. need to do it in Tick() so that the // disconnect event in the pipe is still processed. // (removing client immediately would mean that the // pipe is lost and the disconnect event is never // processed) // sendthread might be waiting on ManualResetEvent, // so let's make sure to end it if the connection // closed. // otherwise the send thread would only end if it's // actually sending data while the connection is // closed. sendThread.Interrupt(); } catch (Exception exception) { Log.Error("Server client thread exception: " + exception); } }); receiveThread.IsBackground = true; receiveThread.Start(); } catch (SocketException exception) { if (exception.ErrorCode == 10035) // WSAEWOULDBLOCK, don't stop server thread { //Log.Info ("Caught WSAEWOULDBLOCK, yielding and retrying later..."); Thread.Yield(); } else { throw exception; } } } } catch (ThreadAbortException exception) { // UnityEditor causes AbortException if thread is still // running when we press Play again next time. that's okay. Log.Info("Server thread aborted. That's okay. " + exception); } catch (SocketException exception) { // calling StopServer will interrupt this thread with a // 'SocketException: interrupted'. that's okay. Log.Info("Server Thread stopped. That's okay. " + exception); } catch (Exception exception) { // something went wrong. probably important. Log.Error("Server Exception: " + exception); } }
// the thread function // STATIC to avoid sharing state! // => pass ClientState object. a new one is created for each new thread! // => avoids data races where an old dieing thread might still modify // the current thread's state :/ static void ReceiveThreadFunction(ClientConnectionState state, string ip, int port, int MaxMessageSize, bool NoDelay, int SendTimeout, int ReceiveTimeout, int ReceiveQueueLimit) { Thread sendThread = null; // absolutely must wrap with try/catch, otherwise thread // exceptions are silent try { // connect (blocking) state.client.Connect(ip, port); state.Connecting = false; // volatile! // set socket options after the socket was created in Connect() // (not after the constructor because we clear the socket there) state.client.NoDelay = NoDelay; state.client.SendTimeout = SendTimeout; state.client.ReceiveTimeout = ReceiveTimeout; // start send thread only after connected // IMPORTANT: DO NOT SHARE STATE ACROSS MULTIPLE THREADS! sendThread = new Thread(() => { ThreadFunctions.SendLoop(0, state.client, state.sendPipe, state.sendPending); }); sendThread.IsBackground = true; sendThread.Start(); // run the receive loop // (receive pipe is shared across all loops) ThreadFunctions.ReceiveLoop(0, state.client, MaxMessageSize, state.receivePipe, ReceiveQueueLimit); } catch (SocketException exception) { // this happens if (for example) the ip address is correct // but there is no server running on that ip/port Log.Info("Client Recv: failed to connect to ip=" + ip + " port=" + port + " reason=" + exception); // add 'Disconnected' event to receive pipe so that the caller // knows that the Connect failed. otherwise they will never know state.receivePipe.Enqueue(0, EventType.Disconnected, default); } catch (ThreadInterruptedException) { // expected if Disconnect() aborts it } catch (ThreadAbortException) { // expected if Disconnect() aborts it } catch (ObjectDisposedException) { // expected if Disconnect() aborts it and disposed the client // while ReceiveThread is in a blocking Connect() call } catch (Exception exception) { // something went wrong. probably important. Log.Error("Client Recv Exception: " + exception); } // sendthread might be waiting on ManualResetEvent, // so let's make sure to end it if the connection // closed. // otherwise the send thread would only end if it's // actually sending data while the connection is // closed. sendThread?.Interrupt(); // Connect might have failed. thread might have been closed. // let's reset connecting state no matter what. state.Connecting = false; // if we got here then we are done. ReceiveLoop cleans up already, // but we may never get there if connect fails. so let's clean up // here too. state.client?.Close(); }
// the thread function // STATIC to avoid sharing state! // => pass ClientState object. a new one is created for each new thread! // => avoids data races where an old dieing thread might still modify // the current thread's state :/ static void ReceiveThreadFunction(ClientState state, string ip, int port, int MaxMessageSize, bool NoDelay, int SendTimeout, int QueueLimit) { Thread sendThread = null; // absolutely must wrap with try/catch, otherwise thread // exceptions are silent try { // connect (blocking) client.Connect(ip, port); Stream stream = client.GetStream(); if (_Encrypted) { RemoteCertificateValidationCallback trustCert = (object sender, X509Certificate x509Certificate, X509Chain x509Chain, SslPolicyErrors policyErrors) => { if (_AcceptSelfSignedCert) { // All certificates are accepted return(true); } else { if (policyErrors == SslPolicyErrors.None) { return(true); } else { return(false); } } }; SslStream encryptedStream = new SslStream(client.GetStream(), false, trustCert, null); stream = encryptedStream; encryptedStream.AuthenticateAsClient(ip); } _Connecting = false; state.client.Connect(ip, port); state.Connecting = false; // volatile! // set socket options after the socket was created in Connect() // (not after the constructor because we clear the socket there) state.client.NoDelay = NoDelay; state.client.SendTimeout = SendTimeout; // start send thread only after connected // IMPORTANT: DO NOT SHARE STATE ACROSS MULTIPLE THREADS! sendThread = new Thread(() => { ThreadFunctions.SendLoop(0, state.client, state.sendPipe, state.sendPending); }); sendThread.IsBackground = true; sendThread.Start(); // run the receive loop // IMPORTANT: DO NOT SHARE STATE ACROSS MULTIPLE THREADS! ThreadFunctions.ReceiveLoop(0, state.client, MaxMessageSize, state.receivePipe, QueueLimit); } catch (SocketException exception) { // this happens if (for example) the ip address is correct // but there is no server running on that ip/port Log.Info("Client Recv: failed to connect to ip=" + ip + " port=" + port + " reason=" + exception); // set 'Disconnected' event to receive pipe so that the caller // knows that the Connect failed. otherwise they will never know state.receivePipe.SetDisconnected(); } catch (ThreadInterruptedException) { // expected if Disconnect() aborts it } catch (ThreadAbortException) { // expected if Disconnect() aborts it } catch (ObjectDisposedException) { // expected if Disconnect() aborts it and disposed the client // while ReceiveThread is in a blocking Connect() call } catch (Exception exception) { // something went wrong. probably important. Log.Error("Client Recv Exception: " + exception); } // sendthread might be waiting on ManualResetEvent, // so let's make sure to end it if the connection // closed. // otherwise the send thread would only end if it's // actually sending data while the connection is // closed. sendThread?.Interrupt(); // Connect might have failed. thread might have been closed. // let's reset connecting state no matter what. state.Connecting = false; // if we got here then we are done. ReceiveLoop cleans up already, // but we may never get there if connect fails. so let's clean up // here too. state.client?.Close(); }
// the listener thread's listen function // note: no maxConnections parameter. high level API should handle that. // (Transport can't send a 'too full' message anyway) void Listen(int port) { // absolutely must wrap with try/catch, otherwise thread // exceptions are silent try { // start listener on all IPv4 and IPv6 address via .Create listener = TcpListener.Create(port); listener.Server.NoDelay = NoDelay; // IMPORTANT: do not set send/receive timeouts on listener. // On linux setting the recv timeout will cause the blocking // Accept call to timeout with EACCEPT (which mono interprets // as EWOULDBLOCK). // https://stackoverflow.com/questions/1917814/eagain-error-for-accept-on-blocking-socket/1918118#1918118 // => fixes https://github.com/vis2k/Mirror/issues/2695 // // listener.Server.SendTimeout = SendTimeout; // listener.Server.ReceiveTimeout = ReceiveTimeout; listener.Start(); Log.Info("Server: listening port=" + port); // keep accepting new clients while (true) { // wait and accept new client // note: 'using' sucks here because it will try to // dispose after thread was started but we still need it // in the thread TcpClient client = listener.AcceptTcpClient(); // set socket options client.NoDelay = NoDelay; //client.SendTimeout = SendTimeout; //client.ReceiveTimeout = ReceiveTimeout; // generate the next connection id (thread safely) int connectionId = NextConnectionId(); // add to dict immediately var connection = new ChicasPlayer(client, MaxMessageSize); connection.OwnerID = connectionId; clients[connectionId] = connection; // spawn a send thread for each client Thread sendThread = new Thread(() => { // wrap in try-catch, otherwise Thread exceptions // are silent try { // run the send loop // IMPORTANT: DO NOT SHARE STATE ACROSS MULTIPLE THREADS! ThreadFunctions.SendLoop(connectionId, client, connection.sendPipe, connection.sendPending); } catch (ThreadAbortException) { // happens on stop. don't log anything. // (we catch it in SendLoop too, but it still gets // through to here when aborting. don't show an // error.) } catch (Exception exception) { Log.Error("Server send thread exception: " + exception); } }); sendThread.IsBackground = true; sendThread.Start(); // spawn a receive thread for each client Thread receiveThread = new Thread(() => { // wrap in try-catch, otherwise Thread exceptions // are silent try { // run the receive loop // (receive pipe is shared across all loops) ThreadFunctions.ReceiveLoop(connectionId, client, MaxMessageSize, receivePipe, ReceiveQueueLimit); // IMPORTANT: do NOT remove from clients after the // thread ends. need to do it in Tick() so that the // disconnect event in the pipe is still processed. // (removing client immediately would mean that the // pipe is lost and the disconnect event is never // processed) // sendthread might be waiting on ManualResetEvent, // so let's make sure to end it if the connection // closed. // otherwise the send thread would only end if it's // actually sending data while the connection is // closed. sendThread.Interrupt(); } catch (Exception exception) { Log.Error("Server client thread exception: " + exception); } }); receiveThread.IsBackground = true; receiveThread.Start(); } } catch (ThreadAbortException exception) { // UnityEditor causes AbortException if thread is still // running when we press Play again next time. that's okay. Log.Info("Server thread aborted. That's okay. " + exception); } catch (SocketException exception) { // calling StopServer will interrupt this thread with a // 'SocketException: interrupted'. that's okay. Log.Info("Server Thread stopped. That's okay. " + exception); } catch (Exception exception) { // something went wrong. probably important. Log.Error("Server Exception: " + exception); } }
// the listener thread's listen function // note: no maxConnections parameter. high level API should handle that. // (Transport can't send a 'too full' message anyway) void Listen(int port) { // absolutely must wrap with try/catch, otherwise thread // exceptions are silent try { // start listener on all IPv4 and IPv6 address via .Create listener = TcpListener.Create(port); listener.Server.NoDelay = NoDelay; listener.Server.SendTimeout = SendTimeout; listener.Start(); Log.Info("Server: listening port=" + port); X509Certificate cert = null; bool selfSignedCert = false; if (Encrypted) { if (CertFile == null) { // Create a new self-signed certificate selfSignedCert = true; cert = new X509Certificate2Builder { SubjectName = string.Format("CN={0}", ((IPEndPoint)listener.LocalEndpoint).Address.ToString()) }.Build(); } else { cert = X509Certificate.CreateFromCertFile(CertFile); selfSignedCert = false; } } // keep accepting new clients while (true) { // wait and accept new client // note: 'using' sucks here because it will try to // dispose after thread was started but we still need it // in the thread TcpClient client = listener.AcceptTcpClient(); // set socket options client.NoDelay = NoDelay; client.SendTimeout = SendTimeout; // generate the next connection id (thread safely) int connectionId = NextConnectionId(); // add to dict immediately ConnectionState connection = new ConnectionState(client, MaxMessageSize); clients[connectionId] = connection; Stream stream = client.GetStream(); Thread sslAuthenticator = null; if (Encrypted) { RemoteCertificateValidationCallback trustCert = (object sender, X509Certificate x509Certificate, X509Chain x509Chain, SslPolicyErrors policyErrors) => { if (selfSignedCert) { // All certificates are accepted return true; } else { if (policyErrors == SslPolicyErrors.None) { return true; } else { return false; } } }; SslStream sslStream = new SslStream(client.GetStream(), false, trustCert); stream = sslStream; sslAuthenticator = new Thread(() => { try { // Using System.Security.Authentication.SslProtocols.None (the Microsoft recommended parameter which // chooses the highest version of TLS) does not seem to work with Unity. Unity 2018.2 added support // for TLS 1.2 when used with the .NET 4.x runtime, so use preprocessor directives to choose the right protocol #if UNITY_2018_2_OR_NEWER && NET_4_6 System.Security.Authentication.SslProtocols protocol = System.Security.Authentication.SslProtocols.Tls12; #else System.Security.Authentication.SslProtocols protocol = System.Security.Authentication.SslProtocols.Default; #endif bool checkCertificateRevocation = !selfSignedCert; sslStream.AuthenticateAsServer(cert, false, protocol, checkCertificateRevocation); } catch (Exception exception) { Logger.LogError("SSL Authenticator exception: " + exception); } }); sslAuthenticator.IsBackground = true; sslAuthenticator.Start(); } // spawn a send thread for each client Thread sendThread = new Thread(() => { // wrap in try-catch, otherwise Thread exceptions // are silent try { if (sslAuthenticator != null) { sslAuthenticator.Join(); } // run the send loop // IMPORTANT: DO NOT SHARE STATE ACROSS MULTIPLE THREADS! ThreadFunctions.SendLoop(connectionId, client, connection.sendPipe, connection.sendPending); } catch (ThreadAbortException) { // happens on stop. don't log anything. // (we catch it in SendLoop too, but it still gets // through to here when aborting. don't show an // error.) } catch (Exception exception) { Log.Error("Server send thread exception: " + exception); } }); sendThread.IsBackground = true; sendThread.Start(); // spawn a receive thread for each client Thread receiveThread = new Thread(() => { // wrap in try-catch, otherwise Thread exceptions // are silent try { if (sslAuthenticator != null) { sslAuthenticator.Join(); } // run the receive loop // IMPORTANT: DO NOT SHARE STATE ACROSS MULTIPLE THREADS! ThreadFunctions.ReceiveLoop(connectionId, client, MaxMessageSize, connection.receivePipe, QueueLimit); // IMPORTANT: do NOT remove from clients after the // thread ends. need to do it in Tick() so that the // disconnect event in the pipe is still processed. // (removing client immediately would mean that the // pipe is lost and the disconnect event is never // processed) // sendthread might be waiting on ManualResetEvent, // so let's make sure to end it if the connection // closed. // otherwise the send thread would only end if it's // actually sending data while the connection is // closed. sendThread.Interrupt(); } catch (Exception exception) { Log.Error("Server client thread exception: " + exception); } }); receiveThread.IsBackground = true; receiveThread.Start(); } } catch (ThreadAbortException exception) { // UnityEditor causes AbortException if thread is still // running when we press Play again next time. that's okay. Log.Info("Server thread aborted. That's okay. " + exception); } catch (SocketException exception) { // calling StopServer will interrupt this thread with a // 'SocketException: interrupted'. that's okay. Log.Info("Server Thread stopped. That's okay. " + exception); } catch (Exception exception) { // something went wrong. probably important. Log.Error("Server Exception: " + exception); } }