private Thread IgnoranceClientThread() { ThreadBootstrapStruct threadBootstrap = new ThreadBootstrapStruct { hostAddress = clientConnectionAddress, port = (ushort)CommunicationPort, maxChannels = Channels.Length, maxPacketSize = MaxPacketSizeInKb * 1024, threadPumpTimeout = EnetPollTimeout }; Thread t = new Thread(() => ClientWorkerThread(threadBootstrap)); return(t); }
// Server thread. private Thread IgnoranceServerThread() { string bindAddress = string.Empty; ThreadBootstrapStruct startupInformation = new ThreadBootstrapStruct() { hostAddress = bindAddress, port = (ushort)CommunicationPort, maxPacketSize = MaxPacketSizeInKb * 1024, maxPeers = MaximumPeerCCU, maxChannels = Channels.Length, threadPumpTimeout = EnetPollTimeout }; Thread t = new Thread(() => ServerWorkerThread(startupInformation)); return(t); }
private Thread IgnoranceClientThread() { statistics = new PeerStatistics(); ThreadBootstrapStruct threadBootstrap = new ThreadBootstrapStruct { hostAddress = clientConnectionAddress, port = (ushort)CommunicationPort, maxChannels = Channels.Length, maxPacketSize = MaxPacketSizeInKb * 1024, threadPumpTimeout = EnetPollTimeout, pingUpdateInterval = StatisticsCalculationInterval, }; Thread t = new Thread(() => ClientWorkerThread(threadBootstrap)); return(t); }
private static void ServerWorkerThread(ThreadBootstrapStruct startupInformation) { // Thread Safety: Initialize ENet in it's own thread. if (Library.Initialize()) { Debug.Log("Ignorance has initialized ENet."); } else { Debug.LogError("Ignorance has failed to initialize ENet."); return; } // Connection ID. // TODO: Change the name of this. int nextConnectionId = 1; // Server address properties Address eAddress = new Address() { Port = startupInformation.port, }; // Bind on everything or not? if (!string.IsNullOrEmpty(startupInformation.hostAddress)) { eAddress.SetHost(startupInformation.hostAddress); } using (Host serverWorkerHost = new Host()) { try { serverWorkerHost.Create(eAddress, startupInformation.maxPeers, startupInformation.maxChannels, 0, 0); ServerStarted = true; } catch (Exception e) { Debug.LogError("Ignorance encountered a fatal exception. You might have found a bug. Please report this on the GitHub Issue tracker and provide details as to what happened.\n" + $"The exception returned was: {e}"); return; } Debug.Log($"Ignorance server thread ready for connections. Listening on UDP port {startupInformation.port}.\n" + $"Capacity: {startupInformation.maxPeers} peers with {startupInformation.maxChannels} channels each. Max packet size: {startupInformation.maxPacketSize} bytes"); // The meat and potatoes. while (!serverShouldCeaseOperation) { // Outgoing stuff while (MirrorServerOutgoingQueue.TryDequeue(out OutgoingPacket opkt)) { switch (opkt.commandType) { case CommandPacketType.ServerWantsToDisconnectClient: if (ConnectionIDToPeers.TryGetValue(opkt.connectionId, out Peer bootedPeer)) { bootedPeer.DisconnectLater(0); } break; case CommandPacketType.Nothing: default: if (ConnectionIDToPeers.TryGetValue(opkt.connectionId, out Peer target)) { // Return code from the send function int returnCode = target.Send(opkt.channelId, ref opkt.payload); if (returnCode != 0) { print($"Error code {returnCode} returned trying to send {opkt.payload.Length} bytes to Peer {target.ID} on channel {opkt.channelId}"); } } break; } } // Get everything out the door. serverWorkerHost.Flush(); // Incoming stuffs now. bool hasBeenPolled = false; while (!hasBeenPolled) { if (serverWorkerHost.CheckEvents(out Event netEvent) <= 0) { if (serverWorkerHost.Service(startupInformation.threadPumpTimeout, out netEvent) <= 0) { break; } hasBeenPolled = true; } switch (netEvent.Type) { case EventType.None: break; case EventType.Connect: // Add to dictionaries. if (!PeersToConnectionIDs.TryAdd(netEvent.Peer, nextConnectionId)) { Debug.LogWarning($"It seems we already know this client in our Connection ID to Peer Mapping. Replacing."); PeersToConnectionIDs[netEvent.Peer] = nextConnectionId; } if (!ConnectionIDToPeers.TryAdd(nextConnectionId, netEvent.Peer)) { Debug.LogWarning($"It seems we already know this client in our Peer to ConnectionID Mapping. Replacing."); ConnectionIDToPeers[nextConnectionId] = netEvent.Peer; } // Send a message back to mirror. IncomingPacket newConnectionPkt = default; newConnectionPkt.connectionId = nextConnectionId; newConnectionPkt.type = MirrorPacketType.ServerClientConnected; newConnectionPkt.ipAddress = netEvent.Peer.IP; MirrorServerIncomingQueue.Enqueue(newConnectionPkt); nextConnectionId++; break; case EventType.Disconnect: case EventType.Timeout: if (PeersToConnectionIDs.TryGetValue(netEvent.Peer, out int deadPeer)) { IncomingPacket disconnectionPkt = default; disconnectionPkt.connectionId = deadPeer; disconnectionPkt.type = MirrorPacketType.ServerClientDisconnected; disconnectionPkt.ipAddress = netEvent.Peer.IP; MirrorServerIncomingQueue.Enqueue(disconnectionPkt); ConnectionIDToPeers.TryRemove(deadPeer, out _); } PeersToConnectionIDs.TryRemove(netEvent.Peer, out _); break; case EventType.Receive: int dataConnID = -1; if (PeersToConnectionIDs.TryGetValue(netEvent.Peer, out dataConnID)) { if (!netEvent.Packet.IsSet) { Debug.LogWarning("Ignorance: A incoming packet is not set correctly - attempting to continue!"); return; } if (netEvent.Packet.Length > startupInformation.maxPacketSize) { Debug.LogWarning($"Ignorance: Packet too large for buffer; dropping. Packet {netEvent.Packet.Length} bytes > {startupInformation.maxPacketSize} byte limit"); netEvent.Packet.Dispose(); return; } // Copy to the packet cache. try { IncomingPacket dataPkt = default; dataPkt.connectionId = dataConnID; dataPkt.channelId = netEvent.ChannelID; dataPkt.type = MirrorPacketType.ServerClientSentData; dataPkt.ipAddress = netEvent.Peer.IP; byte[] rentedBuffer = System.Buffers.ArrayPool <byte> .Shared.Rent(netEvent.Packet.Length); netEvent.Packet.CopyTo(rentedBuffer); dataPkt.data = rentedBuffer; MirrorServerIncomingQueue.Enqueue(dataPkt); } catch (Exception e) { Debug.LogError($"Ignorance caught an exception while trying to copy data from the unmanaged (ENet) world to managed (Mono/IL2CPP) world. Please consider reporting this to the Ignorance developer on GitHub.\n" + $"Exception returned was: {e.Message}\n" + $"Debug details: {(netEvent.Packet.Data == null ? "NetEvent Packet payload was NULL" : $"NetEvent Packet payload was valid")}, {netEvent.Packet.Length} byte(s) packet\n" + $"Stack Trace: {e.StackTrace}"); return; } } else { // Kick the peer. netEvent.Peer.DisconnectNow(0); } // Dispose of the packet - we're done. netEvent.Packet.Dispose(); break; } } } // Disconnect everyone, we're done here. print($"Server thread is now kicking all connected peers..."); foreach (KeyValuePair <int, Peer> kv in ConnectionIDToPeers) { kv.Value.DisconnectNow(0); } print("Server thread is now flushing and cleaning up..."); serverWorkerHost.Flush(); // BUGFIX issue #59: "Player crash on second server client connection" // https://github.com/SoftwareGuy/Ignorance/issues/59 ConnectionIDToPeers.Clear(); PeersToConnectionIDs.Clear(); // Server is no longer started. ServerStarted = false; Library.Deinitialize(); Debug.Log("Ignorance has deinitialized ENet."); } }
private static void ClientWorkerThread(ThreadBootstrapStruct startupInfo) { // Setup... uint nextStatsUpdate = 0; Address cAddress = new Address(); // Drain anything in the queues... while (MirrorClientIncomingQueue.TryDequeue(out _)) { ; } while (MirrorClientOutgoingQueue.TryDequeue(out _)) { ; } // Thread Safety: Initialize ENet in its own thread. if (Library.Initialize()) { Debug.Log("Ignorance has initialized ENet."); } else { Debug.LogError("Ignorance has failed to initialize ENet."); return; } // This comment was actually left blank, but now it's not. You're welcome. using (Host cHost = new Host()) { try { cHost.Create(null, 1, startupInfo.maxChannels, 0, 0); ClientStarted = true; } catch (Exception e) { Debug.LogError("Ignorance encountered a fatal exception. To help debug the issue, use a Debug DLL of ENet and look for a logfile in the root of your " + $"application's folder.\nIf you believe you found a bug, please report it on the GitHub issue tracker. The exception returned was: {e}"); return; } // Attempt to start connection... if (!cAddress.SetHost(startupInfo.hostAddress)) { Debug.LogError("Ignorance was unable to set the hostname or address. Was this string even valid? Please check it and try again."); return; } cAddress.Port = startupInfo.port; Peer cPeer = cHost.Connect(cAddress, startupInfo.maxChannels); while (!clientShouldCeaseOperation) { bool clientWasPolled = false; if (Library.Time >= nextStatsUpdate) { statistics.CurrentPing = cPeer.RoundTripTime; statistics.BytesReceived = cPeer.BytesReceived; statistics.BytesSent = cPeer.BytesSent; statistics.PacketsLost = cPeer.PacketsLost; statistics.PacketsSent = cPeer.PacketsSent; // Library.Time is milliseconds, so we need to do some quick math. nextStatsUpdate = Library.Time + (uint)(startupInfo.pingUpdateInterval * 1000); } while (!clientWasPolled) { if (cHost.CheckEvents(out Event netEvent) <= 0) { if (cHost.Service(startupInfo.threadPumpTimeout, out netEvent) <= 0) { break; } clientWasPolled = true; } switch (netEvent.Type) { case EventType.Connect: // Client connected. IncomingPacket connPkt = default; connPkt.type = MirrorPacketType.ClientConnected; MirrorClientIncomingQueue.Enqueue(connPkt); break; case EventType.Timeout: print("Ignorance: connection timeout"); // Client disconnected. IncomingPacket timeoutPkt = default; timeoutPkt.type = MirrorPacketType.ClientDisconnected; MirrorClientIncomingQueue.Enqueue(timeoutPkt); break; case EventType.Disconnect: // Client disconnected. IncomingPacket disconnPkt = default; disconnPkt.type = MirrorPacketType.ClientDisconnected; MirrorClientIncomingQueue.Enqueue(disconnPkt); break; case EventType.Receive: // Client recieving some data. if (!netEvent.Packet.IsSet) { print("Ignorance WARNING: A incoming packet is not set correctly."); break; } if (netEvent.Packet.Length > startupInfo.maxPacketSize) { print($"Ignorance: Packet dropped as it was too large from Peer {netEvent.Peer.ID}! {netEvent.Packet.Length} packet bytes > allowed size of {startupInfo.maxPacketSize} bytes."); netEvent.Packet.Dispose(); break; } else { // invoke on the client. try { IncomingPacket dataPkt = default; dataPkt.type = MirrorPacketType.ClientGotData; dataPkt.channelId = netEvent.ChannelID; byte[] rentedBuffer = System.Buffers.ArrayPool <byte> .Shared.Rent(netEvent.Packet.Length); netEvent.Packet.CopyTo(rentedBuffer); dataPkt.data = rentedBuffer; MirrorClientIncomingQueue.Enqueue(dataPkt); } catch (Exception e) { Debug.LogError($"Ignorance caught an exception while trying to copy data from the unmanaged (ENet) world to managed (Mono/IL2CPP) world. Please consider reporting this to the Ignorance developer on GitHub.\n" + $"Exception returned was: {e.Message}\n" + $"Debug details: {(netEvent.Packet.Data == null ? "NetEvent Packet payload was NULL" : $"NetEvent Packet payload was valid")}, {netEvent.Packet.Length} byte(s) packet\n" + $"Stack Trace: {e.StackTrace}"); break; } netEvent.Packet.Dispose(); } break; } } // Outgoing stuff while (MirrorClientOutgoingQueue.TryDequeue(out OutgoingPacket opkt)) { if (opkt.commandType == CommandPacketType.ClientDisconnectionRequest) { cPeer.DisconnectNow(0); return; } int returnCode = cPeer.Send(opkt.channelId, ref opkt.payload); if (returnCode != 0) { print($"Ignorance: Could not send {opkt.payload.Length} bytes to server on channel {opkt.channelId}, error code {returnCode}"); } } } cPeer.DisconnectNow(0); cHost.Flush(); ClientStarted = false; } Library.Deinitialize(); Debug.Log("Ignorance has deinitialized ENet."); }
private static void ClientWorkerThread(ThreadBootstrapStruct startupInfo) { // Setup... uint nextStatsUpdate = 0; byte[] workerPacketBuffer = new byte[startupInfo.maxPacketSize]; Address cAddress = new Address(); // Drain anything in the queues... while (MirrorClientIncomingQueue.TryDequeue(out _)) { ; } while (MirrorClientOutgoingQueue.TryDequeue(out _)) { ; } // This comment was actually left blank, but now it's not. You're welcome. using (Host cHost = new Host()) { try { cHost.Create(null, 1, startupInfo.maxChannels, 0, 0, startupInfo.maxPacketSize); ClientStarted = true; } catch (Exception e) { Debug.LogError("Ignorance encountered a fatal exception. To help debug the issue, use a Debug DLL of ENET and look for a 'enet_log.txt' file in the root of your " + $"application folder.\nIf you believe you found a bug, please report it on the GitHub issue tracker. The exception returned was: {e.ToString()}"); return; } // Attempt to start connection... cAddress.SetHost(startupInfo.hostAddress); cAddress.Port = startupInfo.port; Peer cPeer = cHost.Connect(cAddress, startupInfo.maxChannels); while (!clientShouldCeaseOperation) { bool clientWasPolled = false; if (Library.Time >= nextStatsUpdate) { statistics.CurrentPing = cPeer.RoundTripTime; statistics.BytesReceived = cPeer.BytesReceived; statistics.BytesSent = cPeer.BytesSent; statistics.PacketsLost = cPeer.PacketsLost; statistics.PacketsSent = cPeer.PacketsSent; // Library.Time is milliseconds, so we need to do some quick math. nextStatsUpdate = Library.Time + (uint)(startupInfo.pingUpdateInterval * 1000); } while (!clientWasPolled) { if (cHost.CheckEvents(out Event networkEvent) <= 0) { if (cHost.Service(startupInfo.threadPumpTimeout, out networkEvent) <= 0) { break; } clientWasPolled = true; } switch (networkEvent.Type) { case EventType.Connect: // Client connected. IncomingPacket connPkt = default; connPkt.type = MirrorPacketType.ClientConnected; MirrorClientIncomingQueue.Enqueue(connPkt); break; case EventType.Timeout: case EventType.Disconnect: // Client disconnected. IncomingPacket disconnPkt = default; disconnPkt.type = MirrorPacketType.ClientDisconnected; MirrorClientIncomingQueue.Enqueue(disconnPkt); break; case EventType.Receive: // Client recieving some data. if (!networkEvent.Packet.IsSet) { print("Ignorance WARNING: A incoming packet is not set correctly."); break; } if (networkEvent.Packet.Length > workerPacketBuffer.Length) { print($"Ignorance: Packet too big to fit in buffer. {networkEvent.Packet.Length} packet bytes vs {workerPacketBuffer.Length} cache bytes {networkEvent.Peer.ID}."); networkEvent.Packet.Dispose(); break; } else { // invoke on the client. try { networkEvent.Packet.CopyTo(workerPacketBuffer); } catch (Exception e) { Debug.LogError($"Ignorance caught an exception while trying to copy data from the unmanaged (ENET) world to managed (Mono/IL2CPP) world. Please consider reporting this to the Ignorance developer on GitHub.\n" + $"Exception returned was: {e.Message}\n" + $"Debug details: {(workerPacketBuffer == null ? "packet buffer was NULL" : $"{workerPacketBuffer.Length} byte work buffer")}, {networkEvent.Packet.Length} byte(s) network packet length\n" + $"Stack Trace: {e.StackTrace}"); networkEvent.Packet.Dispose(); break; } int spLength = networkEvent.Packet.Length; IncomingPacket dataPkt = default; dataPkt.type = MirrorPacketType.ClientGotData; dataPkt.data = new byte[spLength]; // Grrr!!! networkEvent.Packet.CopyTo(dataPkt.data); MirrorClientIncomingQueue.Enqueue(dataPkt); } break; } networkEvent.Packet.Dispose(); }
private static void ServerWorkerThread(ThreadBootstrapStruct startupInformation) { // Worker buffer. byte[] workerPacketBuffer = new byte[startupInformation.maxPacketSize]; // Connection ID. int nextConnectionId = 1; // Server address properties Address eAddress = new Address() { Port = startupInformation.port, }; // Bind on everything or not? if (!string.IsNullOrEmpty(startupInformation.hostAddress)) { eAddress.SetHost(startupInformation.hostAddress); } using (Host serverWorkerHost = new Host()) { try { serverWorkerHost.Create(eAddress, startupInformation.maxPeers, startupInformation.maxChannels, 0, 0, startupInformation.maxPacketSize); ServerStarted = true; } catch (Exception e) { Debug.LogError("Ignorance encountered a fatal exception. I'm sorry, but I gotta bail - if you believe you found a bug, please report it on the GitHub.\n" + $"The exception returned was: {e.ToString()}"); return; } Debug.Log($"Ignorance Server worker thread is ready for connections! I'm listening on UDP port {startupInformation.port}.\n" + $"Capacity: {startupInformation.maxPeers} peers with {startupInformation.maxChannels} channels. My buffer size is {startupInformation.maxPacketSize} bytes"); // The meat and potatoes. while (!serverShouldCeaseOperation) { // Outgoing stuff while (MirrorServerOutgoingQueue.TryDequeue(out OutgoingPacket opkt)) { switch (opkt.commandType) { case CommandPacketType.BootToTheFace: if (ConnectionIDToPeers.TryGetValue(opkt.connectionId, out Peer bootedPeer)) { bootedPeer.DisconnectLater(0); } break; case CommandPacketType.Nothing: default: if (ConnectionIDToPeers.TryGetValue(opkt.connectionId, out Peer target)) { int returnCode = target.SendAndReturnStatusCode(opkt.channelId, ref opkt.payload); if (returnCode != 0) { print($"Could not send {opkt.payload.Length} bytes to target peer {target.ID} on channel {opkt.channelId}, error code {returnCode}"); } } break; } } // Flush here? serverWorkerHost.Flush(); // Incoming stuffs now. bool hasBeenPolled = false; while (!hasBeenPolled) { if (serverWorkerHost.CheckEvents(out Event netEvent) <= 0) { if (serverWorkerHost.Service(startupInformation.threadPumpTimeout, out netEvent) <= 0) { break; } hasBeenPolled = true; } switch (netEvent.Type) { case EventType.None: break; case EventType.Connect: int connectionId = nextConnectionId; nextConnectionId += 1; // Add to dictionaries. if (!PeersToConnectionIDs.TryAdd(netEvent.Peer, connectionId)) { Debug.LogError($"ERROR: We already know this client in our Connection ID to Peer Mapping?!"); } if (!ConnectionIDToPeers.TryAdd(connectionId, netEvent.Peer)) { Debug.LogError($"ERROR: We already know this client in our Peer to ConnectionID Mapping?!"); } // Send a message back to mirror. IncomingPacket newConnectionPkt = default; newConnectionPkt.connectionId = connectionId; newConnectionPkt.type = MirrorPacketType.ServerClientConnected; newConnectionPkt.ipAddress = netEvent.Peer.IP; MirrorServerIncomingQueue.Enqueue(newConnectionPkt); break; case EventType.Disconnect: case EventType.Timeout: if (PeersToConnectionIDs.TryGetValue(netEvent.Peer, out int deadPeer)) { IncomingPacket disconnectionPkt = default; disconnectionPkt.connectionId = deadPeer; disconnectionPkt.type = MirrorPacketType.ServerClientDisconnected; disconnectionPkt.ipAddress = netEvent.Peer.IP; MirrorServerIncomingQueue.Enqueue(disconnectionPkt); ConnectionIDToPeers.TryRemove(deadPeer, out Peer _); } PeersToConnectionIDs.TryRemove(netEvent.Peer, out int _); break; case EventType.Receive: int dataConnID = -1; if (PeersToConnectionIDs.TryGetValue(netEvent.Peer, out dataConnID)) { if (netEvent.Packet.Length > startupInformation.maxPacketSize) { Debug.LogWarning($"Ignorance WARNING: Packet too large for buffer; dropping. Packet {netEvent.Packet.Length} bytes; limit is {startupInformation.maxPacketSize} bytes."); netEvent.Packet.Dispose(); return; } // Copy to the packet cache. netEvent.Packet.CopyTo(workerPacketBuffer); int spLength = netEvent.Packet.Length; IncomingPacket dataPkt = default; dataPkt.connectionId = dataConnID; dataPkt.channelId = (int)netEvent.ChannelID; dataPkt.type = MirrorPacketType.ServerClientSentData; // TODO: Come up with a better method of doing this. dataPkt.data = new byte[spLength]; Array.Copy(workerPacketBuffer, 0, dataPkt.data, 0, spLength); // Faulty .Array on the end seems to return the rest of the buffer as well instead of just 10 bytes or whatever // dataPkt.data = new ArraySegment<byte>(workerPacketBuffer, 0, spLength); dataPkt.ipAddress = netEvent.Peer.IP; MirrorServerIncomingQueue.Enqueue(dataPkt); } else { // Kick the peer. netEvent.Peer.DisconnectNow(0); } netEvent.Packet.Dispose(); break; } } } // Disconnect everyone, we're done here. print($"Kicking all connected Peers..."); foreach (KeyValuePair <int, Peer> kv in ConnectionIDToPeers) { kv.Value.DisconnectNow(0); } print("Flushing..."); serverWorkerHost.Flush(); ServerStarted = false; } }
private static void ClientWorkerThread(ThreadBootstrapStruct startupInfo) { // Setup... byte[] workerPacketBuffer = new byte[startupInfo.maxPacketSize]; Address cAddress = new Address(); // Drain anything in the queues... while (MirrorClientIncomingQueue.TryDequeue(out _)) { ; } while (MirrorClientOutgoingQueue.TryDequeue(out _)) { ; } // using (Host cHost = new Host()) { try { cHost.Create(null, 1, startupInfo.maxChannels, 0, 0, startupInfo.maxPacketSize); ClientStarted = true; } catch (Exception e) { Debug.LogError("Ignorance encountered a fatal exception. I'm sorry, but I gotta bail - if you believe you found a bug, please report it on the GitHub.\n" + $"The exception returned was: {e.ToString()}"); return; } // Attempt to start connection... cAddress.SetHost(startupInfo.hostAddress); cAddress.Port = startupInfo.port; Peer cPeer = cHost.Connect(cAddress, startupInfo.maxChannels); while (!clientShouldCeaseOperation) { bool clientWasPolled = false; while (!clientWasPolled) { if (cHost.CheckEvents(out Event networkEvent) <= 0) { if (cHost.Service(startupInfo.threadPumpTimeout, out networkEvent) <= 0) { break; } clientWasPolled = true; } switch (networkEvent.Type) { case EventType.Connect: // Client connected. IncomingPacket connPkt = default; connPkt.type = MirrorPacketType.ClientConnected; MirrorClientIncomingQueue.Enqueue(connPkt); break; case EventType.Timeout: case EventType.Disconnect: // Client disconnected. IncomingPacket disconnPkt = default; disconnPkt.type = MirrorPacketType.ClientDisconnected; MirrorClientIncomingQueue.Enqueue(disconnPkt); break; case EventType.Receive: // Client recieving some data. if (networkEvent.Packet.Length > workerPacketBuffer.Length) { print($"Ignorance: Packet too big to fit in buffer. {networkEvent.Packet.Length} packet bytes vs {workerPacketBuffer.Length} cache bytes {networkEvent.Peer.ID}."); networkEvent.Packet.Dispose(); } else { // invoke on the client. networkEvent.Packet.CopyTo(workerPacketBuffer); int spLength = networkEvent.Packet.Length; networkEvent.Packet.Dispose(); IncomingPacket dataPkt = default; dataPkt.type = MirrorPacketType.ClientGotData; dataPkt.data = new byte[spLength]; // Grrr!!! networkEvent.Packet.CopyTo(dataPkt.data); MirrorClientIncomingQueue.Enqueue(dataPkt); } break; } } // Outgoing stuff while (MirrorClientOutgoingQueue.TryDequeue(out OutgoingPacket opkt)) { if (opkt.commandType == CommandPacketType.ClientDisconnectRequest) { cPeer.DisconnectNow(0); return; } int returnCode = cPeer.SendAndReturnStatusCode(opkt.channelId, ref opkt.payload); if (returnCode != 0) { print($"Ignorance: Could not send {opkt.payload.Length} bytes to server on channel {opkt.channelId}, error code {returnCode}"); } } } cPeer.DisconnectNow(0); cHost.Flush(); ClientStarted = false; } }