// 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) { // 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 (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); } }