/// <summary>Triggers NAT traversal for a given host.</summary> /// <param name="serverName">The public IP address of the host.</param> /// <param name="serverPort">The public port of the host.</param> /// <returns>If successful an instance of EnabledAddresses is returned, otherwise null.</returns> /// <remarks>This method should be called by a client before attempting to connect to the DirectPlay session.</remarks> public unsafe static EnabledAddresses TestNatTraversal(string serverName, int serverPort) { Regex ipAddressRegex = new Regex(@"^(?<1>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Singleline); Match ipAddressMatch = ipAddressRegex.Match(serverName); if (ipAddressMatch.Success) { IPAddress publicServerAddress = IPAddress.Parse(ipAddressMatch.Groups[1].Value); try { using (UdpClient socket = new UdpClient()) { socket.Client.ReceiveTimeout = 2000; socket.Connect(natTraversalServiceUrl, natTraversalServicePort); IPEndPoint localEndPoint = (IPEndPoint)socket.Client.LocalEndPoint; // send "lookup" message { MsgLookup msg = new MsgLookup(publicServerAddress, (ushort)serverPort, localEndPoint.Address, (ushort)localEndPoint.Port); byte[] datagram = new byte[sizeof(MsgLookup)]; for (int i = 0; i < datagram.Length; ++i) { datagram[i] = ((byte *)&msg)[i]; } socket.Send(datagram, datagram.Length); } // block until a message is received or 2 seconds have elapsed IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); byte[] bytesReceived = socket.Receive(ref remoteEndPoint); // handle the message if (bytesReceived.Length >= 3 && bytesReceived[0] == 'z' && bytesReceived[1] == 't') { switch (bytesReceived[2]) { case (byte)MessageType.AckLookup: if (bytesReceived.Length == sizeof(MsgAckLookup)) { MsgAckLookup msg = new MsgAckLookup(); for (int i = 0; i < sizeof(MsgAckLookup); ++i) { ((byte *)&msg)[i] = bytesReceived[i]; } // handle MsgAckLookup // report host's private address EnabledAddresses result = new EnabledAddresses(); result.HostPublicAddress = new IPAddress((uint)IPAddress.HostToNetworkOrder((int)msg.HostPublicIp)).ToString(); result.HostPublicPort = msg.HostPublicPort; result.HostPrivateAddress = new IPAddress((uint)IPAddress.HostToNetworkOrder((int)msg.HostPrivateIp)).ToString(); result.HostPrivatePort = msg.HostPrivatePort; result.ClientPrivateAddress = ((IPEndPoint)socket.Client.LocalEndPoint).Address.ToString(); result.ClientPrivatePort = ((IPEndPoint)socket.Client.LocalEndPoint).Port; return(result); } break; case (byte)MessageType.NotFound: if (bytesReceived.Length == sizeof(MsgNotFound)) { MsgNotFound msg = new MsgNotFound(); for (int i = 0; i < sizeof(MsgNotFound); ++i) { ((byte *)&msg)[i] = bytesReceived[i]; } // handle MsgNotFound // just exit and return null } break; } } } } catch { } } return(null); }
/// <summary>Connect to a server.</summary> /// <param name="serverName">IP address or hostname of the server.</param> /// <param name="serverPort">IP port on which the server is listening.</param> /// <remarks>The first client to connect becomes the hosting player.</remarks> public void Connect(string serverName, int serverPort) { Debug.Assert(status == NetworkStatus.Disconnected); serverIsOnSameComputer = (serverName == "localhost"); outboundVideoFrameHistory = new OutboundVideoFrameHistory(); inboundVideoFrameHistories = new Dictionary <int, InboundVideoFrameHistory>(); soundBuffers = new Dictionary <int, Buffer3D>(); client = new Microsoft.DirectX.DirectPlay.Client(InitializeFlags.DisableParameterValidation); client.ConnectComplete += new ConnectCompleteEventHandler(onConnectComplete); client.Receive += new ReceiveEventHandler(onReceive); client.SessionTerminated += new SessionTerminatedEventHandler(onSessionTerminated); status = NetworkStatus.Connecting; // trigger NAT traversal EnabledAddresses enabledAddresses = NatResolver.TestNatTraversal(serverName, serverPort); ApplicationDescription description = new ApplicationDescription(); description.GuidApplication = new Guid("{920BAF09-A06C-47d8-BCE0-21B30D0C3586}"); // try first using the host's public address using (Address hostAddress = (enabledAddresses == null ? new Address(serverName, serverPort) : new Address(enabledAddresses.HostPublicAddress, enabledAddresses.HostPublicPort))) { hostAddress.ServiceProvider = Address.ServiceProviderTcpIp; using (Address device = new Address()) { device.ServiceProvider = Address.ServiceProviderTcpIp; device.AddComponent(Address.KeyTraversalMode, Address.TraversalModeNone); if (enabledAddresses != null) { device.AddComponent(Address.KeyPort, enabledAddresses.ClientPrivatePort); } using (NetworkPacket packet = new NetworkPacket()) { try { client.Connect(description, hostAddress, device, packet, 0); } catch (Exception e) { status = NetworkStatus.Disconnected; ConnectionFailureCause cause = (e is NoConnectionException ? ConnectionFailureCause.NoConnection : (e is NotHostException ? ConnectionFailureCause.NotHost : (e is SessionFullException ? ConnectionFailureCause.SessionFull : ConnectionFailureCause.Other))); // try again using the host's private address if (enabledAddresses != null) { using (Address hostPrivateAddress = new Address(enabledAddresses.HostPrivateAddress, enabledAddresses.HostPrivatePort)) { try { client.Connect(description, hostAddress, device, packet, 0); } catch { NetworkMessage message = new NetworkMessage(0, (byte)ReservedMessageType.ConnectionFailed, new byte[1] { (byte)cause }); lock (networkMessages) { networkMessages.Enqueue(message); } return; } } } else { NetworkMessage message = new NetworkMessage(0, (byte)ReservedMessageType.ConnectionFailed, new byte[1] { (byte)cause }); lock (networkMessages) { networkMessages.Enqueue(message); } return; } } } } } // launch a timer to monitor timeout timeoutTimer = new System.Threading.Timer(onTimeout, client, 4000, 0); }