/// <summary> /// Internal constructor for UDP connections /// </summary> /// <param name="connectionInfo"></param> /// <param name="defaultSendReceiveOptions"></param> /// <param name="level"></param> /// <param name="listenForIncomingPackets"></param> /// <param name="existingConnection"></param> private UDPConnection(ConnectionInfo connectionInfo, SendReceiveOptions defaultSendReceiveOptions, UDPOptions level, bool listenForIncomingPackets, UDPConnection existingConnection = null) : base(connectionInfo, defaultSendReceiveOptions) { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace("Creating new UDPConnection with " + connectionInfo); } UDPOptions = level; if (listenForIncomingPackets && existingConnection != null) { throw new Exception("Unable to listen for incoming packets if an existing client has been provided. This is to prevent possible multiple accidently listens on the same client."); } if (existingConnection == null) { if (connectionInfo.RemoteEndPoint.Address.Equals(IPAddress.Any) || connectionInfo.RemoteEndPoint.Address.Equals(IPAddress.IPv6Any)) { #if WINDOWS_PHONE //We are creating an unbound endPoint, this is currently the rogue UDP sender and listeners only socket = new DatagramSocket(); if (listenForIncomingPackets) { socket.MessageReceived += socket_MessageReceived; } socket.BindEndpointAsync(new HostName(ConnectionInfo.LocalEndPoint.Address.ToString()), ConnectionInfo.LocalEndPoint.Port.ToString()).AsTask().Wait(); #else //We are creating an unbound endPoint, this is currently the rogue UDP sender and listeners only udpClientThreadSafe = new UdpClientThreadSafe(new UdpClient(ConnectionInfo.LocalEndPoint)); #endif } else { //If this is a specific connection we link to a default end point here isIsolatedUDPConnection = true; #if WINDOWS_PHONE if (ConnectionInfo.LocalEndPoint == null) { socket = new DatagramSocket(); if (listenForIncomingPackets) { socket.MessageReceived += socket_MessageReceived; } socket.ConnectAsync(new HostName(ConnectionInfo.RemoteEndPoint.Address.ToString()), ConnectionInfo.RemoteEndPoint.Port.ToString()).AsTask().Wait(); } else { socket = new DatagramSocket(); if (listenForIncomingPackets) { socket.MessageReceived += socket_MessageReceived; } EndpointPair pair = new EndpointPair(new HostName(ConnectionInfo.LocalEndPoint.Address.ToString()), ConnectionInfo.LocalEndPoint.Port.ToString(), new HostName(ConnectionInfo.RemoteEndPoint.Address.ToString()), ConnectionInfo.RemoteEndPoint.Port.ToString()); socket.ConnectAsync(pair).AsTask().Wait(); } #else if (ConnectionInfo.LocalEndPoint == null) { udpClientThreadSafe = new UdpClientThreadSafe(new UdpClient(ConnectionInfo.RemoteEndPoint.AddressFamily)); } else { udpClientThreadSafe = new UdpClientThreadSafe(new UdpClient(ConnectionInfo.LocalEndPoint)); } //By calling connect we discard packets from anything other then the provided remoteEndPoint on our localEndPoint udpClientThreadSafe.Connect(ConnectionInfo.RemoteEndPoint); #endif } #if !WINDOWS_PHONE //NAT traversal does not work in .net 2.0 //Mono does not seem to have implemented AllowNatTraversal method and attempting the below method call will throw an exception //if (Type.GetType("Mono.Runtime") == null) //Allow NAT traversal by default for all udp clients // udpClientThreadSafe.AllowNatTraversal(true); if (listenForIncomingPackets) { StartIncomingDataListen(); } #endif } else { if (!existingConnection.ConnectionInfo.RemoteEndPoint.Address.Equals(IPAddress.Any)) { throw new Exception("If an existing udpClient is provided it must be unbound to a specific remoteEndPoint"); } #if WINDOWS_PHONE //Using an exiting client allows us to send from the same port as for the provided existing connection this.socket = existingConnection.socket; #else //Using an exiting client allows us to send from the same port as for the provided existing connection this.udpClientThreadSafe = existingConnection.udpClientThreadSafe; #endif } IPEndPoint localEndPoint; #if WINDOWS_PHONE localEndPoint = new IPEndPoint(IPAddress.Parse(socket.Information.LocalAddress.DisplayName.ToString()), int.Parse(socket.Information.LocalPort)); #else localEndPoint = udpClientThreadSafe.LocalEndPoint; #endif //We can update the localEndPoint so that it is correct if (ConnectionInfo.LocalEndPoint == null || ConnectionInfo.LocalEndPoint.Port == 0) { ConnectionInfo.UpdateLocalEndPointInfo(localEndPoint); } }
/// <summary> /// Incoming data listen sync method /// </summary> private void IncomingUDPPacketWorker() { try { while (true) { if (ConnectionInfo.ConnectionState == ConnectionState.Shutdown) { break; } IPEndPoint endPoint = new IPEndPoint(IPAddress.None, 0); byte[] receivedBytes = udpClientThreadSafe.Receive(ref endPoint); if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace("Received " + receivedBytes.Length.ToString() + " bytes via UDP from " + endPoint.Address + ":" + endPoint.Port.ToString() + "."); } if (isIsolatedUDPConnection) { //This connection was created for a specific remoteEndPoint so we can handle the data internally //Lock on the packetbuilder locker as we may recieve udp packets in parallel from this host lock (packetBuilder.Locker) { packetBuilder.AddPartialPacket(receivedBytes.Length, receivedBytes); if (packetBuilder.TotalBytesCached > 0) { IncomingPacketHandleHandOff(packetBuilder); } } } else { //Look for an existing connection, if one does not exist we will create it //This ensures that all further processing knows about the correct endPoint UDPConnection connection = GetConnection(new ConnectionInfo(true, ConnectionType.UDP, endPoint, udpClientThreadSafe.LocalEndPoint), ConnectionDefaultSendReceiveOptions, UDPOptions, false, this); //Lock on the packetbuilder locker as we may recieve udp packets in parallel from this host lock (connection.packetBuilder.Locker) { //We pass the data off to the specific connection connection.packetBuilder.AddPartialPacket(receivedBytes.Length, receivedBytes); if (connection.packetBuilder.TotalBytesCached > 0) { connection.IncomingPacketHandleHandOff(connection.packetBuilder); } if (connection.packetBuilder.TotalPartialPacketCount > 0) { connection.packetBuilder.ClearNTopBytes(connection.packetBuilder.TotalBytesCached); //We cant close the connection here because it may be one of the shared udp listeners. For now we will just log. NetworkComms.LogError(new Exception("Packet builder had remaining packets after a call to IncomingPacketHandleHandOff. Until sequenced packets are implemented this indicates a possible error."), "UDPConnectionError"); } } } } } //On any error here we close the connection catch (NullReferenceException) { CloseConnection(true, 20); } catch (ArgumentNullException) { CloseConnection(true, 37); } catch (IOException) { CloseConnection(true, 21); } catch (ObjectDisposedException) { CloseConnection(true, 22); } catch (SocketException) { //Recieve may throw a SocketException ErrorCode=10054 after attempting to send a datagram to an unreachable target. //We will try to get around this by ignoring the ICMP packet causing these problems on client creation CloseConnection(true, 23); } catch (InvalidOperationException) { CloseConnection(true, 24); } catch (Exception ex) { NetworkComms.LogError(ex, "Error_UDPConnectionIncomingPacketHandler"); CloseConnection(true, 41); } //Clear the listen thread object because the thread is about to end incomingDataListenThread = null; if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace("Incoming data listen thread ending for " + ConnectionInfo); } }
void socket_MessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args) { try { var stream = args.GetDataStream().AsStreamForRead(); var dataLength = args.GetDataReader().UnconsumedBufferLength; byte[] receivedBytes = new byte[dataLength]; using (MemoryStream mem = new MemoryStream(receivedBytes)) stream.CopyTo(mem); stream = null; if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace("Received " + receivedBytes.Length + " bytes via UDP from " + args.RemoteAddress + ":" + args.RemotePort + "."); } if (isIsolatedUDPConnection) { //This connection was created for a specific remoteEndPoint so we can handle the data internally //Lock on the packetbuilder locker as we may recieve udp packets in parallel from this host lock (packetBuilder.Locker) { packetBuilder.AddPartialPacket(receivedBytes.Length, receivedBytes); if (packetBuilder.TotalBytesCached > 0) { IncomingPacketHandleHandOff(packetBuilder); } } } else { var remoteEndPoint = new IPEndPoint(IPAddress.Parse(args.RemoteAddress.DisplayName.ToString()), int.Parse(args.RemotePort)); var localEndPoint = new IPEndPoint(IPAddress.Parse(sender.Information.LocalAddress.DisplayName.ToString()), int.Parse(sender.Information.LocalPort)); //Look for an existing connection, if one does not exist we will create it //This ensures that all further processing knows about the correct endPoint UDPConnection connection = GetConnection(new ConnectionInfo(true, ConnectionType.UDP, remoteEndPoint, localEndPoint), ConnectionDefaultSendReceiveOptions, UDPOptions, false, this); //We pass the data off to the specific connection //Lock on the packetbuilder locker as we may recieve udp packets in parallel from this host lock (connection.packetBuilder.Locker) { connection.packetBuilder.AddPartialPacket(receivedBytes.Length, receivedBytes); if (connection.packetBuilder.TotalBytesCached > 0) { connection.IncomingPacketHandleHandOff(connection.packetBuilder); } if (connection.packetBuilder.TotalPartialPacketCount > 0) { connection.packetBuilder.ClearNTopBytes(connection.packetBuilder.TotalBytesCached); //We cant close the connection here because it may be one of the shared udp listeners. For now we will just log. NetworkComms.LogError(new Exception("Packet builder had remaining packets after a call to IncomingPacketHandleHandOff. Until sequenced packets are implemented this indicates a possible error."), "UDPConnectionError"); } } } } //On any error here we close the connection catch (NullReferenceException) { CloseConnection(true, 25); } catch (ArgumentNullException) { CloseConnection(true, 38); } catch (IOException) { CloseConnection(true, 26); } catch (ObjectDisposedException) { CloseConnection(true, 27); } catch (SocketException) { //Recieve may throw a SocketException ErrorCode=10054 after attempting to send a datagram to an unreachable target. //We will try to get around this by ignoring the ICMP packet causing these problems on client creation CloseConnection(true, 28); } catch (InvalidOperationException) { CloseConnection(true, 29); } catch (Exception ex) { NetworkComms.LogError(ex, "Error_UDPConnectionIncomingPacketHandler"); CloseConnection(true, 30); } }
/// <summary> /// Listen for incoming UDP packets on specified <see cref="IPEndPoint"/>. /// </summary> /// <param name="newLocalEndPoint">The localEndPoint to listen for packets on</param> /// <param name="useRandomPortFailOver">If true and the requested local port is not available will select one at random. If false and a port is unavailable will throw <see cref="CommsSetupShutdownException"/></param> public static void StartListening(IPEndPoint newLocalEndPoint, bool useRandomPortFailOver = true) { lock (udpClientListenerLocker) { //If a listener has already been added there is no need to continue if (udpConnectionListeners.ContainsKey(newLocalEndPoint)) { return; } UDPConnection newListeningConnection; try { newListeningConnection = new UDPConnection(new ConnectionInfo(true, ConnectionType.UDP, new IPEndPoint(IPAddress.Any, 0), newLocalEndPoint), NetworkComms.DefaultSendReceiveOptions, UDPOptions.None, true); } catch (SocketException) { if (useRandomPortFailOver) { try { newListeningConnection = new UDPConnection(new ConnectionInfo(true, ConnectionType.UDP, new IPEndPoint(IPAddress.Any, 0), new IPEndPoint(newLocalEndPoint.Address, 0)), NetworkComms.DefaultSendReceiveOptions, UDPOptions.None, true); } catch (SocketException) { //If we get another socket exception this appears to be a bad IP. We will just ignore this IP if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Error("It was not possible to open a random port on " + newLocalEndPoint.Address + ". This endPoint may not support listening or possibly try again using a different port."); } throw new CommsSetupShutdownException("It was not possible to open a random port on " + newLocalEndPoint.Address + ". This endPoint may not support listening or possibly try again using a different port."); } } else { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Error("It was not possible to open port #" + newLocalEndPoint.Port.ToString() + " on " + newLocalEndPoint.Address + ". This endPoint may not support listening or possibly try again using a different port."); } throw new CommsSetupShutdownException("It was not possible to open port #" + newLocalEndPoint.Port.ToString() + " on " + newLocalEndPoint.Address + ". This endPoint may not support listening or possibly try again using a different port."); } } #if WINDOWS_PHONE IPEndPoint ipEndPointUsed = new IPEndPoint(IPAddress.Parse(newListeningConnection.socket.Information.LocalAddress.DisplayName.ToString()), int.Parse(newListeningConnection.socket.Information.LocalPort)); #else IPEndPoint ipEndPointUsed = (IPEndPoint)newListeningConnection.udpClientThreadSafe.LocalEndPoint; #endif if (udpConnectionListeners.ContainsKey(ipEndPointUsed)) { throw new CommsSetupShutdownException("Unable to add new UDP listenerInstance to udpConnectionListeners as there is an existing entry."); } else { udpConnectionListeners.Add(ipEndPointUsed, newListeningConnection); if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Info("Added new UDP listener localEndPoint - " + ipEndPointUsed.Address + ":" + ipEndPointUsed.Port.ToString()); } } } if (!NetworkComms.commsShutdown) { TriggerConnectionKeepAliveThread(); } }
/// <summary> /// Internal UDP creation method that performs the necessary tasks /// </summary> /// <param name="connectionInfo"></param> /// <param name="defaultSendReceiveOptions"></param> /// <param name="level"></param> /// <param name="listenForReturnPackets"></param> /// <param name="existingConnection"></param> /// <returns></returns> internal static UDPConnection GetConnection(ConnectionInfo connectionInfo, SendReceiveOptions defaultSendReceiveOptions, UDPOptions level, bool listenForReturnPackets, UDPConnection existingConnection) { connectionInfo.ConnectionType = ConnectionType.UDP; UDPConnection connection = null; lock (NetworkComms.globalDictAndDelegateLocker) { if (NetworkComms.ConnectionExists(connectionInfo.RemoteEndPoint, ConnectionType.UDP)) { connection = (UDPConnection)NetworkComms.GetExistingConnection(connectionInfo.RemoteEndPoint, ConnectionType.UDP); } else { //If we are listening on what will be the outgoing adaptor we send with that client to ensure if our connection info is handed off we are connectable by others if (existingConnection == null) { try { IPEndPoint localEndPoint = NetworkComms.BestLocalEndPoint(connectionInfo.RemoteEndPoint); lock (udpClientListenerLocker) { IPEndPoint existingLocalEndPoint = ExistingLocalListenEndPoints(localEndPoint.Address); if (existingLocalEndPoint != null) { existingConnection = udpConnectionListeners[existingLocalEndPoint]; //If we are using an existing listener there is no need to listen for packets listenForReturnPackets = false; } } } catch (Exception) { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace("Failed to determine preferred existing udpClientListener to " + connectionInfo.RemoteEndPoint.Address + ":" + connectionInfo.RemoteEndPoint.Port.ToString() + ". Will create an isolated udp connection instead."); } } } //If an existing connection does not exist but the info we are using suggests it should we need to reset the info //so that it can be reused correctly. This case generally happens when using Comms in the format //UDPConnection.GetConnection(info).SendObject(packetType, objToSend); if (connectionInfo.ConnectionState == ConnectionState.Established || connectionInfo.ConnectionState == ConnectionState.Shutdown) { connectionInfo.ResetConnectionInfo(); } connection = new UDPConnection(connectionInfo, defaultSendReceiveOptions, level, listenForReturnPackets, existingConnection); } } if (!NetworkComms.commsShutdown) { TriggerConnectionKeepAliveThread(); } return(connection); }
/// <summary> /// Polls all existing connections based on ConnectionKeepAlivePollIntervalSecs value. Serverside connections are polled slightly earlier than client side to help reduce potential congestion. /// </summary> /// <param name="returnImmediately"></param> private static void AllConnectionsSendNullPacketKeepAlive(bool returnImmediately = false) { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace("Starting AllConnectionsSendNullPacketKeepAlive"); } //Loop through all connections and test the alive state List <Connection> allConnections = NetworkComms.GetExistingConnection(); int remainingConnectionCount = allConnections.Count; #if WINDOWS_PHONE QueueItemPriority nullSendPriority = QueueItemPriority.High; #else QueueItemPriority nullSendPriority = QueueItemPriority.AboveNormal; #endif ManualResetEvent allConnectionsComplete = new ManualResetEvent(false); for (int i = 0; i < allConnections.Count; i++) { //We don't send null packets to unconnected udp connections UDPConnection asUDP = allConnections[i] as UDPConnection; if (asUDP != null && asUDP.UDPOptions == UDPOptions.None) { if (Interlocked.Decrement(ref remainingConnectionCount) == 0) { allConnectionsComplete.Set(); } continue; } else { int innerIndex = i; NetworkComms.CommsThreadPool.EnqueueItem(nullSendPriority, new WaitCallback((obj) => { try { //If the connection is server side we poll preferentially if (allConnections[innerIndex] != null) { if (allConnections[innerIndex].ConnectionInfo.ServerSide) { //We check the last incoming traffic time //In scenarios where the client is sending us lots of data there is no need to poll if ((DateTime.Now - allConnections[innerIndex].ConnectionInfo.LastTrafficTime).TotalSeconds > ConnectionKeepAlivePollIntervalSecs) { allConnections[innerIndex].SendNullPacket(); } } else { //If we are client side we wait upto an additional 3 seconds to do the poll //This means the server will probably beat us if ((DateTime.Now - allConnections[innerIndex].ConnectionInfo.LastTrafficTime).TotalSeconds > ConnectionKeepAlivePollIntervalSecs + 1.0 + (NetworkComms.randomGen.NextDouble() * 2.0)) { allConnections[innerIndex].SendNullPacket(); } } } } catch (Exception) { } finally { if (Interlocked.Decrement(ref remainingConnectionCount) == 0) { allConnectionsComplete.Set(); } } }), null); } } //Max wait is 1 seconds per connection if (!returnImmediately && allConnections.Count > 0) { if (!allConnectionsComplete.WaitOne(allConnections.Count * 2500)) { //This timeout should not really happen so we are going to log an error if it does NetworkComms.LogError(new TimeoutException("Timeout after " + allConnections.Count.ToString() + " seconds waiting for null packet sends to finish. " + remainingConnectionCount.ToString() + " connection waits remain. This error indicates very high send load or a possible send deadlock."), "NullPacketKeepAliveTimeoutError"); } } }