/// <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> /// Establish the connection /// </summary> protected override void EstablishConnectionSpecific() { #if WINDOWS_PHONE if (socket == null) { ConnectSocket(); } //For the local endpoint var localEndPoint = new IPEndPoint(IPAddress.Parse(socket.Information.LocalAddress.CanonicalName.ToString()), int.Parse(socket.Information.LocalPort)); //We should now be able to set the connectionInfo localEndPoint ConnectionInfo.UpdateLocalEndPointInfo(localEndPoint); //Set the outgoing buffer size socket.Control.OutboundBufferSizeInBytes = (uint)NetworkComms.SendBufferSizeBytes; #else if (tcpClient == null) { ConnectSocket(); } //We should now be able to set the connectionInfo localEndPoint ConnectionInfo.UpdateLocalEndPointInfo((IPEndPoint)tcpClient.Client.LocalEndPoint); //We are going to be using the networkStream quite a bit so we pull out a reference once here tcpClientNetworkStream = tcpClient.GetStream(); //When we tell the socket/client to close we want it to do so immediately //this.tcpClient.LingerState = new LingerOption(false, 0); //We need to set the keep alive option otherwise the connection will just die at some random time should we not be using it //NOTE: This did not seem to work reliably so was replaced with the keepAlive packet feature //this.tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); tcpClient.ReceiveBufferSize = NetworkComms.ReceiveBufferSizeBytes; tcpClient.SendBufferSize = NetworkComms.SendBufferSizeBytes; //This disables the 'nagle alogrithm' //http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.nodelay.aspx //Basically we may want to send lots of small packets (<200 bytes) and sometimes those are time critical (e.g. when establishing a connection) //If we leave this enabled small packets may never be sent until a suitable send buffer length threshold is passed. i.e. BAD tcpClient.NoDelay = true; tcpClient.Client.NoDelay = true; #endif //Start listening for incoming data StartIncomingDataListen(); //Get a list of existing listeners List <IPEndPoint> existingListeners = TCPConnection.ExistingLocalListenEndPoints(ConnectionInfo.LocalEndPoint.Address); //Select a listener for this connection IPEndPoint selectedExistingListener = null; if (existingListeners.Count > 0) { selectedExistingListener = (existingListeners.Contains(ConnectionInfo.LocalEndPoint) ? ConnectionInfo.LocalEndPoint : existingListeners[0]); } //If we are server side and we have just received an incoming connection we need to return a conneciton id //This id will be used in all future connections from this machine if (ConnectionInfo.ServerSide) { if (selectedExistingListener == null) { throw new ConnectionSetupException("Detected a server side connection when an existing listener was not present."); } if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Debug("Waiting for client connnectionInfo from " + ConnectionInfo); } //Wait for the client to send its identification if (!connectionSetupWait.WaitOne(NetworkComms.ConnectionEstablishTimeoutMS)) { throw new ConnectionSetupException("Timeout waiting for client connectionInfo with " + ConnectionInfo + ". Connection created at " + ConnectionInfo.ConnectionCreationTime.ToString("HH:mm:ss.fff") + ", its now " + DateTime.Now.ToString("HH:mm:ss.f")); } if (connectionSetupException) { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Debug("Connection setup exception. ServerSide with " + ConnectionInfo + ", " + connectionSetupExceptionStr); } throw new ConnectionSetupException("ServerSide. " + connectionSetupExceptionStr); } //Trigger the connection establish delegates before replying to the connection establish base.EstablishConnectionSpecific(); //Once we have the clients id we send our own SendObject(Enum.GetName(typeof(ReservedPacketType), ReservedPacketType.ConnectionSetup), new ConnectionInfo(ConnectionType.TCP, NetworkComms.NetworkIdentifier, new IPEndPoint(ConnectionInfo.RemoteEndPoint.Address, selectedExistingListener.Port), true), NetworkComms.InternalFixedSendReceiveOptions); } else { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Debug("Sending connnectionInfo to " + ConnectionInfo); } //As the client we initiated the connection we now forward our local node identifier to the server //If we are listening we include our local listen port as well SendObject(Enum.GetName(typeof(ReservedPacketType), ReservedPacketType.ConnectionSetup), new ConnectionInfo(ConnectionType.TCP, NetworkComms.NetworkIdentifier, new IPEndPoint(ConnectionInfo.RemoteEndPoint.Address, (selectedExistingListener != null ? selectedExistingListener.Port : ConnectionInfo.LocalEndPoint.Port)), selectedExistingListener != null), NetworkComms.InternalFixedSendReceiveOptions); //Wait here for the server end to return its own identifier if (!connectionSetupWait.WaitOne(NetworkComms.ConnectionEstablishTimeoutMS)) { throw new ConnectionSetupException("Timeout waiting for server connnectionInfo from " + ConnectionInfo + ". Connection created at " + ConnectionInfo.ConnectionCreationTime.ToString("HH:mm:ss.fff") + ", its now " + DateTime.Now.ToString("HH:mm:ss.f")); } //If we are client side we can update the localEndPoint for this connection to reflect what the remote end might see if we are also listening if (selectedExistingListener != null) { ConnectionInfo.UpdateLocalEndPointInfo(selectedExistingListener); } if (connectionSetupException) { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Debug("Connection setup exception. ClientSide with " + ConnectionInfo + ", " + connectionSetupExceptionStr); } throw new ConnectionSetupException("ClientSide. " + connectionSetupExceptionStr); } //Trigger the connection establish delegates once the server has replied to the connection establish base.EstablishConnectionSpecific(); } #if !WINDOWS_PHONE //Once the connection has been established we may want to re-enable the 'nagle algorithm' used for reducing network congestion (apparently). //By default we leave the nagle algorithm disabled because we want the quick through put when sending small packets if (EnableNagleAlgorithmForNewConnections) { tcpClient.NoDelay = false; tcpClient.Client.NoDelay = false; } #endif }