/// <inheritdoc /> internal override void StartListening(EndPoint desiredLocalListenEndPoint, bool useRandomPortFailOver) { if (desiredLocalListenEndPoint.GetType() != typeof(IPEndPoint)) { throw new ArgumentException("Invalid desiredLocalListenEndPoint type provided.", "desiredLocalListenEndPoint"); } if (IsListening) { throw new InvalidOperationException("Attempted to call StartListening when already listening."); } IPEndPoint desiredLocalListenIPEndPoint = (IPEndPoint)desiredLocalListenEndPoint; try { UDPConnection = new UDPConnection(new ConnectionInfo(ConnectionType.UDP, new IPEndPoint(IPAddress.Any, 0), desiredLocalListenIPEndPoint, ApplicationLayerProtocol, this), ListenerDefaultSendReceiveOptions, UDPOptions, true); } catch (SocketException) { if (useRandomPortFailOver) { try { UDPConnection = new UDPConnection(new ConnectionInfo(ConnectionType.UDP, new IPEndPoint(IPAddress.Any, 0), new IPEndPoint(desiredLocalListenIPEndPoint.Address, 0), ApplicationLayerProtocol, this), ListenerDefaultSendReceiveOptions, UDPOptions, 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 " + desiredLocalListenIPEndPoint.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 " + desiredLocalListenIPEndPoint.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 #" + desiredLocalListenIPEndPoint.Port.ToString() + " on " + desiredLocalListenIPEndPoint.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 #" + desiredLocalListenIPEndPoint.Port.ToString() + " on " + desiredLocalListenIPEndPoint.Address + ". This endPoint may not support listening or possibly try again using a different port."); } } #if WINDOWS_PHONE || NETFX_CORE this.LocalListenEndPoint = new IPEndPoint(IPAddress.Parse(UDPConnection.socket.Information.LocalAddress.DisplayName.ToString()), int.Parse(UDPConnection.socket.Information.LocalPort)); #else this.LocalListenEndPoint = (IPEndPoint)UDPConnection.udpClient.LocalIPEndPoint; #endif this.IsListening = true; }
/// <summary> /// Sends a <see cref="Packet"/> to the provided endPoint. Offers more performance if an identical packet is being sent to multiple peers. /// NOTE: Any possible reply will be ignored unless listening for incoming UDP packets. /// </summary> /// <typeparam name="packetPayloadObjectType">The type of object encapsulated by the provided packet</typeparam> /// <param name="packetToSend">The packet to send</param> /// <param name="ipEndPoint">The destination IPEndPoint. Supports multicast endpoints.</param> /// <param name="sendReceiveOptions">The sendReceiveOptions to use for this send</param> /// <param name="applicationLayerProtocol">If enabled NetworkComms.Net uses a custom /// application layer protocol to provide useful features such as inline serialisation, /// transparent packet transmission, remote peer handshake and information etc. We strongly /// recommend you use the NetworkComms.Net application layer protocol.</param> public static void SendObject <packetPayloadObjectType>(IPacket packetToSend, IPEndPoint ipEndPoint, SendReceiveOptions sendReceiveOptions, ApplicationLayerProtocolStatus applicationLayerProtocol) { if (ipEndPoint == null) { throw new ArgumentNullException("ipEndPoint"); } if (sendReceiveOptions == null) { throw new ArgumentNullException("sendReceiveOptions"); } if (applicationLayerProtocol == ApplicationLayerProtocolStatus.Undefined) { throw new ArgumentException("A value of ApplicationLayerProtocolStatus.Undefined is invalid when using this method.", "applicationLayerProtocol"); } if (sendReceiveOptions.Options.ContainsKey("ReceiveConfirmationRequired")) { throw new ArgumentException("Attempted to use a rouge UDP sender when the provided send receive" + " options specified the ReceiveConfirmationRequired option, which is unsupported. Please create a specific connection" + "instance to use this feature.", "sendReceiveOptions"); } //Check the send receive options if (applicationLayerProtocol == ApplicationLayerProtocolStatus.Disabled) { if (sendReceiveOptions.DataSerializer != DPSManager.GetDataSerializer <NullSerializer>()) { throw new ArgumentException("Attempted to use a rouge UDP sender when the provided send receive" + " options serialiser was not NullSerializer. Please provide compatible send receive options in order to successfully" + " instantiate this unmanaged connection.", "sendReceiveOptions"); } if (sendReceiveOptions.DataProcessors.Count > 0) { throw new ArgumentException("Attempted to use a rouge UDP sender when the provided send receive" + " options contains data processors. Data processors may not be used with unmanaged connections." + " Please provide compatible send receive options in order to successfully instantiate this unmanaged connection.", "sendReceiveOptions"); } } List <UDPConnection> connectionsToUse = null; //If we are already listening on what will be the outgoing adaptor we can send with that client to ensure reply packets are collected //The exception here is the broadcasting which goes out all adaptors if (ipEndPoint.Address != IPAddress.Broadcast) { #region Discover best local endpoint //Initialise best local end point as match all IPEndPoint bestLocalEndPoint = new IPEndPoint(IPAddress.Any, 0); try { bestLocalEndPoint = IPTools.BestLocalEndPoint(ipEndPoint); //Set the port to 0 to match all. bestLocalEndPoint.Port = 0; } catch (SocketException ex) { throw new ConnectionSetupException("Attempting to determine the best local endPoint to connect to " + ipEndPoint + " resulted in a socket exception.", ex); } catch (Exception ex) { LogTools.LogException(ex, "BestLocalEndPointError", "Error while attempting to determine the best local end point to contact " + ipEndPoint.ToString()); } #endregion Discover best local endpoint #region Check For Existing Local Listener List <UDPConnectionListener> existingListeners = Connection.ExistingLocalListeners <UDPConnectionListener>(bestLocalEndPoint); for (int i = 0; i < existingListeners.Count; i++) { if (existingListeners[i].UDPConnection.ConnectionInfo.ApplicationLayerProtocol == applicationLayerProtocol) { connectionsToUse = new List <UDPConnection> { existingListeners[i].UDPConnection }; //Once we have a matching connection we can break break; } } #endregion Check For Existing Local Listener //If we have not picked up an existing listener we need to use/create a rougeSender if (connectionsToUse == null) { #region Check For Suitable Rouge Sender lock (udpRogueSenderCreationLocker) { if (NetworkComms.commsShutdown) { throw new CommunicationException("Attempting to send UDP packet but NetworkCommsDotNet is in the process of shutting down."); } else { if (!udpRogueSenders.ContainsKey(applicationLayerProtocol) || !udpRogueSenders[applicationLayerProtocol].ContainsKey(bestLocalEndPoint) || udpRogueSenders[applicationLayerProtocol][bestLocalEndPoint].ConnectionInfo.ConnectionState == ConnectionState.Shutdown) { //Create a new rogue sender if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace("Creating UDPRougeSender."); } if (!udpRogueSenders.ContainsKey(applicationLayerProtocol)) { udpRogueSenders.Add(applicationLayerProtocol, new Dictionary <IPEndPoint, UDPConnection>()); } IPAddress anyRemoteIP = AnyRemoteIPAddress(ipEndPoint.AddressFamily); udpRogueSenders[applicationLayerProtocol][bestLocalEndPoint] = new UDPConnection(new ConnectionInfo(ConnectionType.UDP, new IPEndPoint(anyRemoteIP, 0), bestLocalEndPoint, applicationLayerProtocol), sendReceiveOptions, UDPConnection.DefaultUDPOptions, false); } connectionsToUse = new List <UDPConnection> { udpRogueSenders[applicationLayerProtocol][bestLocalEndPoint] }; } } #endregion Check For Suitable Rouge Sender } } else { #region Get A Sender On All Interfaces For Broadcast lock (udpRogueSenderCreationLocker) { //We do something special for broadcasts by selected EVERY adaptor if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace("Getting senders for UDP broadcasting."); } if (!udpRogueSenders.ContainsKey(applicationLayerProtocol)) { udpRogueSenders.Add(applicationLayerProtocol, new Dictionary <IPEndPoint, UDPConnection>()); } connectionsToUse = new List <UDPConnection>(); //This is a broadcast and we need to send the broadcast over every local adaptor List <IPAddress> validLocalIPAddresses = HostInfo.IP.FilteredLocalAddresses(); foreach (IPAddress address in validLocalIPAddresses) { IPEndPoint currentLocalIPEndPoint = new IPEndPoint(address, 0); List <UDPConnectionListener> existingListeners = Connection.ExistingLocalListeners <UDPConnectionListener>(currentLocalIPEndPoint); //If there is an existing listener we use that if (existingListeners.Count > 0) { for (int i = 0; i < existingListeners.Count; i++) { if (existingListeners[i].UDPConnection.ConnectionInfo.ApplicationLayerProtocol == applicationLayerProtocol) { connectionsToUse.Add(existingListeners[i].UDPConnection); //Once we have a matching connection we can break break; } } } else { //If not we check the rouge senders if (!udpRogueSenders[applicationLayerProtocol].ContainsKey(currentLocalIPEndPoint) || udpRogueSenders[applicationLayerProtocol][currentLocalIPEndPoint].ConnectionInfo.ConnectionState == ConnectionState.Shutdown) { IPAddress anyRemoteIP = AnyRemoteIPAddress(currentLocalIPEndPoint.AddressFamily); udpRogueSenders[applicationLayerProtocol][currentLocalIPEndPoint] = new UDPConnection(new ConnectionInfo(ConnectionType.UDP, new IPEndPoint(anyRemoteIP, 0), currentLocalIPEndPoint, applicationLayerProtocol), sendReceiveOptions, UDPConnection.DefaultUDPOptions, false); } connectionsToUse.Add(udpRogueSenders[applicationLayerProtocol][currentLocalIPEndPoint]); } } } #endregion Get A Sender On All Interfaces For Broadcast } foreach (UDPConnection connection in connectionsToUse) { try { //This has been commented out for the time being as it made no difference to the broadcast issue //we were investigating at the time we had issues //Use the network broadcast address instead of the global broadcast address where possible //if (ipEndPoint.Address == IPAddress.Broadcast && connection.ConnectionInfo.LocalIPEndPoint.AddressFamily == AddressFamily.InterNetwork) //{ // IPEndPoint ipEndPointToUse = new IPEndPoint(IPTools.GetIPv4NetworkBroadcastAddress(connection.ConnectionInfo.LocalIPEndPoint.Address), ipEndPoint.Port); // connection.SendPacketSpecific<packetPayloadObjectType>(packetToSend, ipEndPointToUse); //} //else connection.SendPacketSpecific <packetPayloadObjectType>(packetToSend, ipEndPoint); } catch (SocketException) { /* Ignore any socket exceptions */ } } //Dispose of the packet packetToSend.Dispose(); }
/// <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="existingListenerConnection"></param> /// <param name="possibleHandshakeUDPDatagram"></param> /// <param name="establishIfRequired">Will establish the connection, triggering connection establish delegates if a new /// connection is returned</param> /// <returns></returns> internal static UDPConnection GetConnection(ConnectionInfo connectionInfo, UDPOptions level, SendReceiveOptions defaultSendReceiveOptions, bool listenForReturnPackets, UDPConnection existingListenerConnection, HandshakeUDPDatagram possibleHandshakeUDPDatagram, bool establishIfRequired = true) { connectionInfo.ConnectionType = ConnectionType.UDP; bool newConnection = false; UDPConnection connection = null; lock (NetworkComms.globalDictAndDelegateLocker) { List <Connection> existingConnections = NetworkComms.GetExistingConnection(connectionInfo.RemoteIPEndPoint, connectionInfo.LocalIPEndPoint, ConnectionType.UDP, connectionInfo.ApplicationLayerProtocol); if (existingConnections.Count > 0) { connection = (UDPConnection)existingConnections[0]; } 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 (existingListenerConnection == null) { try { IPEndPoint localEndPoint = IPTools.BestLocalEndPoint(connectionInfo.RemoteIPEndPoint); //Set the port to 0 so that we match any listener localEndPoint.Port = 0; List <UDPConnectionListener> existingListeners = Connection.ExistingLocalListeners <UDPConnectionListener>(localEndPoint); for (int i = 0; i < existingListeners.Count; i++) { if (existingListeners[i].UDPConnection.ConnectionInfo.ApplicationLayerProtocol == connectionInfo.ApplicationLayerProtocol) { existingListenerConnection = existingListeners[i].UDPConnection; //If we are using an existing listener there is no need to listen for packets listenForReturnPackets = false; //Once we have a matching connection we can break break; } } } catch (Exception) { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace("Failed to determine preferred existing udpClientListener to " + connectionInfo.RemoteIPEndPoint.Address + ":" + connectionInfo.RemoteIPEndPoint.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, existingListenerConnection); newConnection = true; } } //If we expect a UDP handshake we need to handle incoming datagrams here, if we have it available, // before trying to establish the connection. //This is different for TCP connections because things happen in the reverse order //UDP - Already listening, receive connectionsetup, configure connection //TCP - Receive TCPClient, configure connection, start listening for connectionsetup, wait for connectionsetup // //possibleHandshakeUDPDatagram will only be set when GetConnection() is called from a listener //If multiple threads try to create an outgoing UDP connection to the same endPoint all but the originating //thread will be held on connection.WaitForConnectionEstablish(); if (possibleHandshakeUDPDatagram != null && (connection.ConnectionUDPOptions & UDPOptions.Handshake) == UDPOptions.Handshake) { lock (connection.packetBuilder.Locker) { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace(" ... " + possibleHandshakeUDPDatagram.DatagramBytes.Length.ToString() + " handshake bytes added to packetBuilder for " + connection.ConnectionInfo + ". Cached " + connection.packetBuilder.TotalBytesCached.ToString() + " bytes, expecting " + connection.packetBuilder.TotalBytesExpected.ToString() + " bytes."); } connection.packetBuilder.AddPartialPacket(possibleHandshakeUDPDatagram.DatagramBytes.Length, possibleHandshakeUDPDatagram.DatagramBytes); if (connection.packetBuilder.TotalBytesCached > 0) { connection.IncomingPacketHandleHandOff(connection.packetBuilder); } } if (connection.packetBuilder.TotalPartialPacketCount > 0) { LogTools.LogException(new Exception("Packet builder had " + connection.packetBuilder.TotalBytesCached + " bytes remaining after a call to IncomingPacketHandleHandOff with connection " + connection.ConnectionInfo + ". Until sequenced packets are implemented this indicates a possible error."), "UDPConnectionError"); } possibleHandshakeUDPDatagram.DatagramHandled = true; } //We must perform the establish outside the lock as for TCP connections if (newConnection && establishIfRequired) { //Call establish on the connection if it is not a rogue sender or listener if (!connectionInfo.RemoteIPEndPoint.Address.Equals(IPAddress.Any) && !connectionInfo.RemoteIPEndPoint.Address.Equals(IPAddress.IPv6Any)) { connection.EstablishConnection(); } } else if (!newConnection) { connection.WaitForConnectionEstablish(NetworkComms.ConnectionEstablishTimeoutMS); } //UDP does not need keep alives //if (!NetworkComms.commsShutdown) // TriggerConnectionKeepAliveThread(); return(connection); }
/// <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> internal UDPConnection(ConnectionInfo connectionInfo, SendReceiveOptions defaultSendReceiveOptions, UDPOptions level, bool listenForIncomingPackets, UDPConnection existingConnection = null) : base(connectionInfo, defaultSendReceiveOptions) { if (connectionInfo.ConnectionType != ConnectionType.UDP) { throw new ArgumentException("Provided connectionType must be UDP.", "connectionInfo"); } if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace("Creating new UDPConnection with " + connectionInfo); } if (connectionInfo.ApplicationLayerProtocol == ApplicationLayerProtocolStatus.Disabled && level != UDPOptions.None) { throw new ArgumentException("If the application layer protocol has been disabled the provided UDPOptions can only be UDPOptions.None."); } ConnectionUDPOptions = 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.RemoteIPEndPoint.Address.Equals(IPAddress.Any) || connectionInfo.RemoteIPEndPoint.Address.Equals(IPAddress.IPv6Any)) { #if WINDOWS_PHONE || NETFX_CORE //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.LocalIPEndPoint.Address.ToString()), ConnectionInfo.LocalIPEndPoint.Port.ToString()).AsTask().Wait(); #else //We are creating an unbound endPoint, this is currently the rogue UDP sender and listeners only udpClient = new UdpClientWrapper(new UdpClient(ConnectionInfo.LocalIPEndPoint)); #endif } else { //If this is a specific connection we link to a default end point here isIsolatedUDPConnection = true; #if WINDOWS_PHONE || NETFX_CORE if (ConnectionInfo.LocalEndPoint == null || (ConnectionInfo.LocalIPEndPoint.Address == IPAddress.Any && connectionInfo.LocalIPEndPoint.Port == 0) || (ConnectionInfo.LocalIPEndPoint.Address == IPAddress.IPv6Any && connectionInfo.LocalIPEndPoint.Port == 0)) { socket = new DatagramSocket(); if (listenForIncomingPackets) { socket.MessageReceived += socket_MessageReceived; } socket.ConnectAsync(new HostName(ConnectionInfo.RemoteIPEndPoint.Address.ToString()), ConnectionInfo.RemoteIPEndPoint.Port.ToString()).AsTask().Wait(); } else { socket = new DatagramSocket(); if (listenForIncomingPackets) { socket.MessageReceived += socket_MessageReceived; } EndpointPair pair = new EndpointPair(new HostName(ConnectionInfo.LocalIPEndPoint.Address.ToString()), ConnectionInfo.LocalIPEndPoint.Port.ToString(), new HostName(ConnectionInfo.RemoteIPEndPoint.Address.ToString()), ConnectionInfo.RemoteIPEndPoint.Port.ToString()); socket.ConnectAsync(pair).AsTask().Wait(); } #else if (ConnectionInfo.LocalEndPoint == null) { udpClient = new UdpClientWrapper(new UdpClient(ConnectionInfo.RemoteEndPoint.AddressFamily)); } else { udpClient = new UdpClientWrapper(new UdpClient(ConnectionInfo.LocalIPEndPoint)); } //By calling connect we discard packets from anything other then the provided remoteEndPoint on our localEndPoint udpClient.Connect(ConnectionInfo.RemoteIPEndPoint); #endif } #if !WINDOWS_PHONE && !NETFX_CORE //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.RemoteIPEndPoint.Address.Equals(IPAddress.Any)) { throw new Exception("If an existing udpClient is provided it must be unbound to a specific remoteEndPoint"); } #if WINDOWS_PHONE || NETFX_CORE //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.udpClient = existingConnection.udpClient; #endif } IPEndPoint localEndPoint; #if WINDOWS_PHONE || NETFX_CORE localEndPoint = new IPEndPoint(IPAddress.Parse(socket.Information.LocalAddress.DisplayName.ToString()), int.Parse(socket.Information.LocalPort)); #else localEndPoint = udpClient.LocalIPEndPoint; #endif //We can update the localEndPoint so that it is correct if (!ConnectionInfo.LocalEndPoint.Equals(localEndPoint)) { //We should now be able to set the connectionInfo localEndPoint NetworkComms.UpdateConnectionReferenceByEndPoint(this, ConnectionInfo.RemoteIPEndPoint, localEndPoint); ConnectionInfo.UpdateLocalEndPointInfo(localEndPoint); } }