2

我正在编写基于 UDP Hole Punching 的应用程序。我在客户端之间建立连接时遇到问题。在每个客户端向服务器发送某些内容并且服务器使用它们的 IP 相互响应之后,客户端无法相互发送任何内容。我错过了什么吗?还是我对 UDP Hole Punching 的理解有误?是的,我有服务器所在的 PC 的外部 IP。

服务器代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using System.IO;


namespace ConsoleApplication2
{
class Program
{

    static void Main(string[] args)
    {
        IPAddress IP = IPAddress.Parse("xx.xx.xx.xxx");
        IPEndPoint localEP = new IPEndPoint(IP, 80);
        UdpClient server = new UdpClient();
        server.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
        server.ExclusiveAddressUse = false;
        server.Client.Bind(localEP);
        IPEndPoint temp;

        IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 80);

        Console.WriteLine("Dane servera : " + localEP);
        byte[] buffer = server.Receive(ref remoteEP);

        Console.WriteLine("Otrzymano dane od : " + remoteEP + " o treści " + Encoding.ASCII.GetString(buffer));

        temp = remoteEP;

        remoteEP = new IPEndPoint(IPAddress.Any, 80);
        byte[] buffer2 = server.Receive(ref remoteEP);
        Console.WriteLine("Otrzymano dane od : " + remoteEP + " o treści " + Encoding.ASCII.GetString(buffer2));

        byte[] response = Encoding.ASCII.GetBytes(temp.ToString());
        server.Send(response, response.Length, remoteEP);
        byte[] response2 = Encoding.ASCII.GetBytes(remoteEP.ToString());
        server.Send(response2, response2.Length,temp );

    }
}
}

客户1:

namespace ConsoleApplication1
{
class Program
{
    public static IPEndPoint CreateIPEndPoint(string endPoint)
    {
        string[] ep = endPoint.Split(':');
        if (ep.Length < 2) throw new FormatException("Invalid endpoint format");
        IPAddress ip;
        if (ep.Length > 2)
        {
            if (!IPAddress.TryParse(string.Join(":", ep, 0, ep.Length - 1), out ip))
            {
                throw new FormatException("Invalid ip-adress");
            }
        }
        else
        {
            if (!IPAddress.TryParse(ep[0], out ip))
            {
                throw new FormatException("Invalid ip-adress");
            }
        }
        int port;
        if (!int.TryParse(ep[ep.Length - 1], NumberStyles.None, NumberFormatInfo.CurrentInfo, out port))
        {
            throw new FormatException("Invalid port");
        }
        return new IPEndPoint(ip, port);
    }
    static void Main(string[] args)
    {
        IPAddress IP = IPAddress.Parse("xx.xx.xx.xxx");
        IPEndPoint localpt = new IPEndPoint(IP, 80);
        UdpClient client = new UdpClient();
        client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
        client.ExclusiveAddressUse = false;
        string powitanie = "ASUS";
        byte[] buffer = new byte[100];
        buffer = Encoding.ASCII.GetBytes(powitanie);
       // client.Connect(localpt);
        client.Send(buffer, buffer.Length,localpt);

        byte[] otrzymane = client.Receive(ref localpt);
        Console.WriteLine("Odpowiedz servera : " + Encoding.ASCII.GetString(otrzymane));
        Console.Read();
        IPEndPoint TV = CreateIPEndPoint(Encoding.ASCII.GetString(otrzymane));


        byte[] buffer2 = client.Receive(ref TV);
       Console.WriteLine("Odpowiedz klienta : " + Encoding.ASCII.GetString(buffer2));
    }
}
}

客户 2:

namespace ConsoleApplication1
{
class Program
{
    public static IPEndPoint CreateIPEndPoint(string endPoint)
    {
        string[] ep = endPoint.Split(':');
        if (ep.Length < 2) throw new FormatException("Invalid endpoint format");
        IPAddress ip;
        if (ep.Length > 2)
        {
            if (!IPAddress.TryParse(string.Join(":", ep, 0, ep.Length - 1), out ip))
            {
                throw new FormatException("Invalid ip-adress");
            }
        }
        else
        {
            if (!IPAddress.TryParse(ep[0], out ip))
            {
                throw new FormatException("Invalid ip-adress");
            }
        }
        int port;
        if (!int.TryParse(ep[ep.Length - 1], NumberStyles.None, NumberFormatInfo.CurrentInfo, out port))
        {
            throw new FormatException("Invalid port");
        }
        return new IPEndPoint(ip, port);
    }
    static void Main(string[] args)
    {
        IPAddress IP = IPAddress.Parse("xx.xx.xx.xxx");
        IPEndPoint localpt = new IPEndPoint(IP, 80);
        UdpClient client = new UdpClient();
        client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
        client.ExclusiveAddressUse = false;
        string powitanie = "Samsung";
        byte[] buffer = new byte[100];
        buffer = Encoding.ASCII.GetBytes(powitanie);
       // client.Connect(localpt);
        client.Send(buffer, buffer.Length,localpt);

        byte[] otrzymane = client.Receive(ref localpt);
        Console.WriteLine("Odpowiedz servera : " + Encoding.ASCII.GetString(otrzymane));
        Console.Read();
        IPEndPoint TV = CreateIPEndPoint(Encoding.ASCII.GetString(otrzymane));

        client.Send(buffer, buffer.Length, TV);

    }
}
}
4

1 回答 1

0

如果无法访问您的确切环境和网络,我不相信有可能提供一个有信心解决问题的答案。但这里有一些事情要记住:

  1. 首先,“打孔”不是一个明确定义的功能,没有技术规范或行业标准支持。从来没有任何保证它会工作,尽管当然许多路由器以允许它的方式工作。如果您确定您的代码是正确的,但由于某种原因仍然无法正常工作,那么您总是有可能使用一个或多个路由器,而这些路由器根本无法使用该技术。
  2. 根据路由器的行为,客户端从特定端点发送数据报可能会或可能不会足够。在原始出站数据报的接收者回复之前,路由器可能不会将外部数据报路由到该端点,即实际上已经建立了双向通信。您当前的实现似乎包含允许用户测试代码以在尝试发送到另一个客户端之前等待两个服务器回复的代码,但请确保您正在利用它。即,在两个客户端都收到服务器响应之前,您不会尝试从一个客户端发送到另一个客户端。
  3. 除了上述之外,服务器向每个客户端发送数据报可能还不够。路由器仍可能丢弃从未知端点接收到的数据报。因此,经常需要的技术的一个变体是一个客户端尝试向另一个客户端发送数据报,但也通过服务器通知该客户端它已经这样做了(即向服务器发送一个数据报报告这一点,然后服务器向预期的接收客户端发送数据报以通知它)。

    该数据报将被丢弃,但发送客户端的路由器不知道这一点,因此当其他客户端直接回复发送客户端时(已被服务器通知它应该),现在原始发送客户端的路由器会将数据报传递给该客户端。它看到了先前用于该其他客户端的数据报,因此它将来自该其他客户端的入站数据报视为有效。从那时起,两个客户端应该能够直接相互发送。自然,实现这一点需要一个更复杂的应用程序协议,而不仅仅是像您在此处的示例那样转发 IP 地址。
  4. 您的代码示例使用SocketOptionName.ReuseAddress,这几乎总是错误的。在我看来,只有您的服务器套接字绑定到一个显式地址,因此使用此选项可能不会影响测试的结果(即,任何给定地址上仍然只有一个套接字,即使您正在单个测试机器)。但是,如果您的测试环境有更多内容,例如套接字地址确实被重用,则很容易干扰代码的正确操作。

最后,您在评论中提问(请将相关信息和问题放在问题本身中):“我应该使用 udp.connect 连接到服务器,然后在客户端之间使用 udp.send”。答案是不”。首先,Connect()在 UDP 套接字上使用只是为了方便;UDP 本身是无连接的,因此连接 UDP 套接字完全在框架内处理。其次,在 .NET 中,当您“连接”一个 UDP 套接字时,框架将过滤数据报,将它们限制为从“已连接”端点接收到的数据报。这与您想要的打孔完全相反;即您希望能够从服务器和其他客户端接收数据报。

于 2015-11-14T20:31:38.170 回答