gpt4 book ai didi

c# - UDP 打洞。让服务器与客户端交谈

转载 作者:可可西里 更新时间:2023-11-01 03:09:23 28 4
gpt4 key购买 nike

我已经阅读了很多关于如何实现 UDP 打洞的书,但由于某种原因我无法让它工作。

对于那些不熟悉什么是udp打洞的人,这里是我自己的定义:

目标是能够在两个客户端(客户端 A
和客户端 B) 在服务器的帮助下。因此客户端 A 连接到服务器并发送其信息。客户端 B 也这样做。服务器具有必要的信息,以便客户端 A 能够向客户端 B 发送数据,反之亦然。因此,服务器将该信息提供给两个客户端。一旦两个客户端都获得了彼此的信息,就可以在没有服务器帮助的情况下开始在这些客户端之间发送和接收数据。

我的目标是能够做我刚刚描述的事情(udp 打洞)。 在这样做之前,我认为能够从服务器连接到客户端会很有帮助 .为此,我计划向服务器发送有关客户端的信息。服务器收到该信息后,尝试从头开始连接到客户端。一旦我能够执行,我应该拥有开始实现真正的 udp 打洞所需的一切。

这是我如何设置:

enter image description here

顶部路由器将服务器和底部路由器连接到 LAN 端口。底部路由器 (NAT) 通过其 WAN 端口连接到顶部路由器。客户端计算机连接到底部路由器到其 LAN 端口之一。

因此,在该连接中,客户端能够看到服务器,但服务器无法看到客户端。

所以我用伪代码做的算法是:

  • 客户端连接到服务器。
  • 客户端向服务器发送一些 UDP 包,以便在 NAT 上打开一些端口
  • 将有关客户端正在监听的端口的信息发送到服务器。
  • 服务器收到该信息后,尝试从头开始连接到客户端。

  • 下面是代码中的实现:

    服务器:
    static void Main()
    {
    /* Part 1 receive data from client */
    UdpClient listener = new UdpClient(11000);
    IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, 11000);
    string received_data;
    byte[] receive_byte_array = listener.Receive(ref groupEP);
    received_data = Encoding.ASCII.GetString(receive_byte_array, 0, receive_byte_array.Length);

    // get info
    var ip = groupEP.Address.ToString();
    var port = groupEP.Port;

    /* Part 2 atempt to connect to client from scratch */
    // now atempt to send data to client from scratch once we have the info
    Socket sendSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
    IPEndPoint endPointClient = new IPEndPoint(IPAddress.Parse(ip), port);
    sendSocket.SendTo(Encoding.ASCII.GetBytes("Hello"), endPointClient);
    }

    客户:
    static void Main(string[] args)
    {
    /* Part 1 send info to server */
    Socket sending_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
    IPAddress send_to_address = IPAddress.Parse("192.168.0.132");
    IPEndPoint sending_end_point = new IPEndPoint(send_to_address, 11000);
    sending_socket.SendTo(Encoding.ASCII.GetBytes("Test"), sending_end_point);

    // get info
    var port = sending_socket.LocalEndPoint.ToString().Split(':')[1];

    /* Part 2 receive data from server */
    IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, int.Parse(port));
    byte[] buffer = new byte[1024];
    sending_socket.Receive(buffer);
    }

    出于某种原因,它工作了几次! 当客户端成功接收数据上线时工作: sending_socket.Receive(buffer);
    注意事项:
    如果在第二部分的服务器上,我使用了实例变量 listner而不是创建新变量 sendSocket并通过该变量发送字节,客户端能够接收正在发送的数据。请记住,服务器的第二部分将由第二个客户端 B 实现,这就是我从头开始再次初始化变量的原因......

    编辑:

    这是看待同一问题的不同方式。 当我初始化一个新对象而不是使用相同的对象时,客户端不会收到响应。

    我有一个 UdpClient 类型的对象。我能够将带有该对象的数据发送给另一个对等方。如果我创建另一个具有相同属性的相同类型的对象并尝试发送数据,它将不起作用!我可能缺少初始化一些变量。我可以用反射设置私有(private)变量,所以我应该没有问题。无论如何这里是服务器代码:
    public static void Main()
    {
    // wait for client to send data
    UdpClient listener = new UdpClient(11000);
    IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, 11000);
    byte[] receive_byte_array = listener.Receive(ref groupEP);

    // connect so that we are able to send data back
    listener.Connect(groupEP);

    byte[] dataToSend = new byte[] { 1, 2, 3, 4, 5 };

    // now let's atempt to reply back

    // this part does not work!
    UdpClient newClient = CopyUdpClient(listener, groupEP);
    newClient.Send(dataToSend, dataToSend.Length);

    // this part works!
    listener.Send(dataToSend, dataToSend.Length);
    }

    static UdpClient CopyUdpClient(UdpClient client, IPEndPoint groupEP)
    {
    var ip = groupEP.Address.ToString();
    var port = groupEP.Port;
    var newUdpClient = new UdpClient(ip, port);
    return newUdpClient;
    }

    客户端代码基本上将数据发送到服务器,然后等待响应:
        string ipOfServer = "192.168.0.132";
    int portServerIsListeningOn = 11000;

    // send data to server
    Socket sending_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
    IPAddress send_to_address = IPAddress.Parse(ipOfServer);
    IPEndPoint sending_end_point = new IPEndPoint(send_to_address, portServerIsListeningOn);
    sending_socket.SendTo(Encoding.ASCII.GetBytes("Test"), sending_end_point);

    // get info
    var port = sending_socket.LocalEndPoint.ToString().Split(':')[1];

    // now wait for server to send data back
    IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, int.Parse(port));
    byte[] buffer = new byte[1024];
    sending_socket.Receive(buffer); // <----- keeps waiting in here :(

    请注意,客户端位于路由器 (NAT) 后面,否则我不会遇到此问题。 我想复制 udpClient 的原因是我可以将该变量发送到另一台计算机,使另一台计算机能够将数据发送到客户端。

    所以我的问题是 为什么是原始对象 listener能够发送数据但 newClient是不是可以?客户端一直在线等待 sending_socket.Receive(buffer);即使在服务器执行该行之后: newClient.Send(dataToSend, dataToSend.Length); .当监听器发送数据但未发送新客户端时,客户端成功接收数据。如果两个变量具有相同的目标 IP 和端口,为什么会这样?变量如何不同?

    笔记:
    如果服务器和客户端在同一个网络上,则复制有效且变量 newClient能够向客户端发送数据。要模拟此问题,客户端必须位于 NAT(路由器)后面。这种网络的一个例子可能由两个路由器组成。让我们称它们为路由器 X 和路由器 Y。您还需要一个服务器调用该 S. 和一个客户端 C。因此 S 可以连接到 X 的 LAN 端口之一。 C 可以连接到 Y 的 LAN 端口之一。最后将 Y 的 WAN 端口连接到 X 的 LAN 端口之一。

    最佳答案

    嗯,我认为你在这里混淆了几件事。一方面,它真的叫 UDP hole punching .让我尝试解释这应该如何工作。

    NAT路由器通常做port mapping将数据包从内部专用网络转发到外部 Internet 时。

    假设您在 NAT 后面的机器上创建了一个 UDP 套接字,并将数据报发送到某个外部 IP/端口。当携带该数据报的 IP 数据包离开发送机器时,其 IP 报头的源地址字段设置为本地不可全局路由的私有(private) IP 地址(如 192.168.1.15),UDP 报头的源端口字段设置为任何端口分配给套接字(通过绑定(bind)显式分配,或由操作系统从临时端口隐式选择)。我将把这个源端口号称为 P1 .

    然后当 NAT 路由器将该数据包发送到外部网络时,它会将源 IP 地址覆盖为自己的外部 IP 地址(否则无法将数据包路由回),并且经常将源 UDP 端口覆盖为其他值(也许因为专用网络上的某些其他主机使用相同的源端口,这会造成歧义)。原始源端口和新端口号(让我们将其标记为 P2 )之间的映射保留在路由器中以匹配返回数据包。此映射也可能特定于目标 IP 地址和目标 UDP 端口。

    所以现在你已经在路由器上“打了一个洞” - UDP 数据包发送回路由器到端口 P2被转发到 UDP 端口 P1 上的内部机器.同样,根据 NAT 的实现,这可能仅限于来自原始目标 IP 地址和目标 UDP 端口的数据包。

    对于客户端到客户端的通信,您必须通过服务器将一个的外部 IP/端口告诉另一个,希望 NAT 路由器将相同的内部源端口映射到相同的外部源端口。然后客户端将使用这些数据包相互发送。

    希望这可以帮助。

    关于c# - UDP 打洞。让服务器与客户端交谈,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12136031/

    28 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com