/// <summary> /// Is called if the connection attempt resulted in a failure the remote connection point was unreachable or did not respond. /// </summary> /// <param name="response">Response information that may have been gathered upon the failure.</param> protected virtual void OnConnectionFailure(ConnectionResponse response) { #if DEBUGBUILD //TODO: Add information to the debug logger string for the response. ClassLogger.LogDebug("Failed to connect to a server."); #endif //Just fail silently if not overidden. }
/// <summary> /// Internally called by the app when a shutdown request has been recieved. /// </summary> internal void InternalOnShutdown() { #if DEBUGBUILD ClassLogger.LogDebug("Server shutdown requested."); #endif OnShutdown(); //Do not stop polling the server until the application is about to stop. //In case it is desired to send a final message to clients and subservers we flush the network queue //So these messages can hopefully be sent before shutdown. this.lidgrenServerObj.FlushSendQueue(); isReady = false; }
private void ServiceLidgrenMessage(NetIncomingMessage msg) { if (msg == null) { return; } switch (msg.MessageType) { case NetIncomingMessageType.StatusChanged: try { HandleStatusChange((NetConnectionStatus)msg.ReadByte()); } catch (NetException e) { #if UNITYDEBUG ClassLogger.LogError("Malformed packet recieved. Packet indicated that it was a status change but had no info."); #else //TODO: What shall we do when the packet is malformed here? #endif } catch (LoggableException e) { //Checking this because it can cause some nasty GC to make these string adds. if (ClassLogger.isStateEnabled(LogType.Debug)) { ClassLogger.LogDebug(e.Message + " Inner: " + e.InnerException != null ? e.InnerException.Message : ""); } } break; //We can take advantage of using the same logic for both cases. //All that changes is we till the handler it is an internal message for a Data message type. case NetIncomingMessageType.ExternalHighlevelMessage: case NetIncomingMessageType.Data: try { this.NetworkMessageHandler.DispatchMessage(this, msg, msg.MessageType == NetIncomingMessageType.Data); } catch (LoggableException e) { //Checking this because it can cause some nasty GC to make these string adds. if (ClassLogger.isStateEnabled(LogType.Debug)) { ClassLogger.LogDebug(e.Message + " Inner: " + e.InnerException != null ? e.InnerException.Message : ""); } } break; } }
//TODO: Totally refactor this garbage public void StartPipeListener(string clientHandleString) { #if DEBUGBUILD ClassLogger.LogDebug("Started pipe listener to launcher with handle: " + clientHandleString); #endif Task.Factory.StartNew(() => { try { using (PipeStream ps = new AnonymousPipeClientStream(PipeDirection.In, clientHandleString)) { using (StreamReader reader = new StreamReader(ps, Encoding.Default)) { string message; while (true) { message = reader.ReadLine(); if (message != null) { #if DEBUGBUILD ClassLogger.LogDebug("Recieved message via Launcher pipe: " + message); #endif if (message.Contains("[SHUTDOWN]")) { if (message == "[SHUTDOWN] " + Process.GetCurrentProcess().Id.ToString()) { isReady = false; break; } } } } } } } catch (Exception e) { ClassLogger.LogError(e.Message); } }, TaskCreationOptions.LongRunning); }
//TODO: Address memory leak; not critical atm. private void ReadStatusChange(NetConnectionStatus netConnectionStatus, NetConnection netConnection) { #if DEBUGBUILD this.ClassLogger.LogDebug("Entered into un-collected connection status handler."); #endif //TODO: This whole method won't work if we don't achieve the connection and the list will continue to grow but it's not an imperative issue atm. ConnectionResponse cr = this.UnhandledServerConnections .FirstOrDefault(x => x.InternalNetConnection.RemoteUniqueIdentifier == netConnection.RemoteUniqueIdentifier); switch (netConnectionStatus) { case NetConnectionStatus.Disconnected: //If we get a disconnection and it's about an unhandled server that has yet to fully connect we should remove it. if (cr == null) { return; } UnhandledServerConnections.Remove(cr); this.OnConnectionFailure(cr); break; case NetConnectionStatus.Connected: if (cr == null) { return; } #if DEBUGBUILD if (UnhandledServerConnections.Remove(cr)) { ClassLogger.LogDebug("Successfully removed a CR from a outgoing connection."); } else { ClassLogger.LogDebug("Failed to remove a CR from a failed outgoing connection."); } #else UnhandledServerConnections.Remove(cr); #endif BuildServerPeer(netConnection, cr, ServerConnections); break; } }
private bool TryRegisterApprovedConnection(NetConnection netConnection, byte connectionType) { ClientPeer cp = OnAttemptedConnection(new ConnectionRequest(netConnection.RemoteEndPoint, netConnection.RemoteUniqueIdentifier, netConnection, connectionType)); if (cp != null) { //A connecting subserver should be treated as a client connection. ServerPeers are peers that should be created //on the connected end, which can recieve events and responses, to its requests. So all approved connections should be added to the client //registry. ClassLogger.LogDebug("Connectiontype ID: " + connectionType); #if DEBUGBUILD ClassLogger.LogDebug("Adding new client to ConnectionCollection. ID: " + cp.UniqueConnectionId); #endif return(Clients.Register(new ConnectionPair <NetConnection, ClientPeer>(netConnection, cp), netConnection.RemoteUniqueIdentifier)); } else { ClassLogger.LogDebug("ClientPeer generated for {0}:{1} ID: {2} is null.", netConnection.RemoteEndPoint.Address, netConnection.RemoteEndPoint.Port, netConnection.RemoteUniqueIdentifier); } return(false); }
protected virtual void OnRecieveMessage(NetIncomingMessage msg, bool isInternal) { #if DEBUGBUILD ClassLogger.LogDebug("Recieved a high level message from client ID: " + msg.SenderConnection.RemoteUniqueIdentifier); #endif if (Clients.HasKey(msg.SenderConnection.RemoteUniqueIdentifier)) //Client sent the message { NetworkMessageHandler.DispatchMessage(Clients[msg.SenderConnection.RemoteUniqueIdentifier].HighlevelPeer, msg, isInternal); } else if (ServerConnections.HasKey(msg.SenderConnection.RemoteUniqueIdentifier)) //Subserver sent a message { NetworkMessageHandler.DispatchMessage(ServerConnections[msg.SenderConnection.RemoteUniqueIdentifier].HighlevelPeer, msg, isInternal); } else { ClassLogger.LogWarn("Recieved highlevel message with no receving object."); } //At this point the message is for nobody and we shouldn't have recieved it. //In a perfect world we'd disconnect whoever sent it but we can't be sure a real client actually sent it //The package could be faked so just drop it. }
private void NetworkListenerThreadMethod() { #if UNITYDEBUG || DEBUG ClassLogger.LogDebug("Started network thread."); #endif if (internalLidgrenClient == null || internalLidgrenClient == null) { ClassLogger.LogError("Cannot start listening before connecting."); #if !UNITYDEBUG && !UNITYRELEASE ClassLogger.LogError("Cannot start listening before connecting."); throw new NullReferenceException("Cannot start listening before connecting. Internally a client object is null."); #endif } NetIncomingMessage msg; try { while (_isConnected) { msg = internalLidgrenClient.WaitMessage(20); //TODO: May not be thread safe due to NetTime.Now //This checks to see if Poll was called in the last 3 seconds. If it was not it stops the network thread basically. if (Interlocked.Exchange(ref timeNowByPollComparer, timeNowByPoll) < NetTime.Now - 3) { #if UNITYDEBUG || DEBUG ClassLogger.LogDebug("Stopping network thread."); #endif _isConnected = false; //Should be thread safe and fine to do. this.Disconnect(); } if (msg != null) { try { ServiceLidgrenMessage(msg); //Recycling the message reduces GC which can be make or break for Unity. this.internalLidgrenClient.Recycle(msg); } //We can catch nullreferences here without affecting exceptions external to the library //This is because we have a Queue<Action> and thus we can't possibly catch the exception coming from the main thread via the lambda generated Action instance. catch (NullReferenceException e) { ClassLogger.LogError("Error occurred during polling: " + e.Message + " StackTrace: " + e.StackTrace + " Source: " + e.Source); //If we hit this point it generally means that the object has gone out of scope for some reason without disconnecting (usually in Unity going from playmode //to the editor so at this point we should just catch it and let the application and thread die. _isConnected = false; } } } } catch (LoggableException e) { ClassLogger.LogError(e.Message + e.InnerException != null ? "Inner: " + e.InnerException : ""); } catch (Exception e) { ClassLogger.LogError(e.Message + e.Data); throw; } Disconnect(); networkThread = null; }
private void MessagePoll(NetPeer peer) { //TODO: Examine if this will cause latency issues for handling messages //This is done so that CPU usage isn't too high. It blocks the thread and waits for a message //Nothing but GladNet should be executing on the main thread anyway. NetIncomingMessage msg = peer.WaitMessage(10); //TODO: Refactor if (msg != null && msg.SenderConnection != null) { #if DEBUGBUILD ClassLogger.LogDebug("Recieved a message from client ID: " + msg.SenderConnection.RemoteUniqueIdentifier); #endif switch (msg.MessageType) { case NetIncomingMessageType.ConnectionApproval: ClassLogger.LogDebug("Hit connection approval"); if (this.NetworkMessageHandler.TryReadHailMessage(msg, ExpectedClientHailMessage)) { ClassLogger.LogDebug("About to approve."); try { msg.SenderConnection.Approve(); this.TryRegisterApprovedConnection(msg.SenderConnection, msg.SenderConnection.RemoteHailMessage.ReadByte()); } catch (NetException e) { #if DEBUGBUILD ClassLogger.LogError("Failed to read connection type byte from hail message packet. Exception: " + e.Message); #endif } } else { ClassLogger.LogWarn("Client failed to satisfy hailmessage. Expected: " + ExpectedClientHailMessage); } break; case NetIncomingMessageType.StatusChanged: //Malicious user could try to send a fake StatusChange without the extra byte so try to catch NetException. try { if (Clients.HasKey(msg.SenderConnection.RemoteUniqueIdentifier)) { this.ReadStatusChange((NetConnectionStatus)msg.ReadByte(), Clients[msg.SenderConnection.RemoteUniqueIdentifier]); } else if (ServerConnections.HasKey(msg.SenderConnection.RemoteUniqueIdentifier)) { this.ReadStatusChange((NetConnectionStatus)msg.ReadByte(), ServerConnections[msg.SenderConnection.RemoteUniqueIdentifier]); } else { //If this point is reached it indicates that the status change is not from a registered connection and //this could indicate potentially a subserver connecting or maybe a client message before hail approval. ReadStatusChange((NetConnectionStatus)msg.ReadByte(), msg.SenderConnection); } } catch (NetException e) { #if DEBUGBUILD ClassLogger.LogError("NetConnection ID: " + msg.SenderConnection.RemoteUniqueIdentifier + " sent a potentially malicious StatusChange update. Error: " + e.Message); #endif } break; case NetIncomingMessageType.ExternalHighlevelMessage: OnRecieveMessage(msg, false); break; case NetIncomingMessageType.Data: OnRecieveMessage(msg, true); break; } peer.Recycle(msg); } }