UDP 打孔.让服务器与客户端对话

UDP hole punching. Have server talk to client(UDP 打孔.让服务器与客户端对话)
本文介绍了UDP 打孔.让服务器与客户端对话的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我阅读了很多关于如何实现 UDP 打孔的文章,但由于某种原因我无法使其工作.

I been reading a lot on how to implement UDP hole punching but fore some reason I cannot make it work.

对于不熟悉 udp 打孔的人,这里是我自己的定义:

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

The goal is to be able to transfer data between two clients (Client A and client B) with the help of a server. So client A connects to the server and sends its info. Client B does the same. The server has the nessesary info so that Client A is able to send data to Client B and vise versa . Therefore the server gives that info to both clients. Once both clients have that info about each other it is possible to start sending and receiving data between those clients without the help of the server.

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

My goal is to be able to do what I just described (udp hole punching). Before doing so I think it will be helpful to be able to connect from the server to the client. In order to do so I plan to send the server the info about the client. Once the server receives that info attempt to connect to the client from scratch. Once I am able to perform that I should have everything I need to start implementing the real udp hole punching.

我是这样设置的:

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

The top router has the server and bottom router connected to LAN ports. The bottom router (NAT) is connected to the top router via it's WAN port. And the client computer is connected to the bottom router to one of its LAN ports.

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

So in that connection the client is able to see the server but the server is not able to see the client.

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

So the algorithm I have done in pseudo code is:

  • 客户端连接到服务器.
  • 客户端向服务器发送一些 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);

For some reason it worked a few times! It works when the client receives data successfully on the line: sending_socket.Receive(buffer);

注意事项:如果在第二部分的服务器上,我使用实例变量 listner 而不是创建新变量 sendSocket 并通过该变量发送字节,客户端能够接收数据被发送.请记住,服务器的第二部分将由第二个客户端 B 实现,这就是我再次从头开始初始化变量的原因......

Things to note: If on the server on the second part I used the instance variable listner instead of creating the new variable sendSocket and send the bytes through that variable the client is able to receive the data being sent. Remember that the second part of the server is going to be implemented by a second client B that's why I am initializing variables again from scratch...

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

Here is a different way of looking at the same problem. When I initialize a new object instead of using the same object the client does not receives the response.

我有一个 UdpClient 类型的对象.我能够将该对象的数据发送给其他对等方.如果我创建另一个具有相同属性的相同类型的对象并尝试发送数据它不起作用!我可能缺少初始化一些变量.我可以通过反射设置私有变量,所以我应该没有问题.反正这里是服务器代码:

I have a object of type UdpClient. I am able to send data with that object to the other peer. If I create another object of the same type with the same properties and attempt to send data it does not work! I might be missing to initialize some variables. I am able to set private variables with reflection so I should not have a problem. anyways here is the server code:

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;
}

客户端代码基本上是向服务器发送数据,然后等待响应:

the client code basically sends data to the server and then waits for a response:

    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 的原因是我可以将该变量发送到另一台计算机启用另一台计算机向客户端发送数据.

note that the client is behind a router (NAT) otherwise I will not have this problem. The reason why I will like to copy udpClient is so that I can send that variable to another computer enabling the other computer to send data to the client.

所以我的问题是为什么原始对象 listener 能够发送数据而 newClient 不能?即使服务器执行了以下行:newClient.Send(dataToSend, dataToSend.Length);,客户端仍然在行 sending_socket.Receive(buffer); 处等待.当侦听器发送数据而不是newClient时,客户端成功接收数据.如果两个变量具有相同的目标 IP 和端口,为什么会这样?变量有何不同?

So my question is why is the original object listener able to send data but newClient is not able to? The client keeps waiting at line sending_socket.Receive(buffer); even after the server executes the line: newClient.Send(dataToSend, dataToSend.Length);. the client successfully receives data when listener sends the data but not newClient. Why is this if both variables have the same destination IP and port? how do the variables differ?

注意:如果服务器和客户端在同一个网络上,则复制工作并且变量 newClient 能够将数据发送到客户端.要模拟此问题,客户端必须位于 NAT(路由器)之后.这种网络的一个例子可能包括两个路由器.我们称它们为路由器 X 和路由器 Y.您还需要一个称为 S 的服务器和一个客户端 C.因此 S 可以连接到 X 的 LAN 端口之一.C 可以连接到 Y 的 LAN 端口之一.最后将 Y 的 WAN 口连接到 X 的 LAN 口之一.

Note: If the server and client are on the same network then the copy works and variable newClient is able to send data to the client. To simulate this problem the client must be behind a NAT (router). An example of such network may consist of two routers. let's call them router X and router Y. You also need a Server call that S. and a client C. so S can be connected to one of the LAN ports of X. C can be connected to one of the LAN ports of Y. Finally connect the WAN port of Y to one of the LAN ports of X.

推荐答案

终于找到答案了!这是仅使用客户端和服务器的实现.我的下一次尝试将是使用 3 台计算机.无论如何希望这会有所帮助:

Finally found the answer! Here is the implemenation with just a client and a server. My next attempt will be to use 3 computers. anyways hope this helps:

服务器代码:

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

    // get the ip and port number where the client will be listening on
    static IPEndPoint GetClientInfo()
    {
        // wait for client to send data
        using (UdpClient listener = new UdpClient(11000))
        {
            IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, 11000);
            byte[] receive_byte_array = listener.Receive(ref groupEP);

            return groupEP;
        }
    }

    static void Main(string[] args)
    {
        var info = GetClientInfo(); // get client info

        /* NOW THAT WE HAVE THE INFO FROM THE CLIENT WE ARE GONG TO SEND
           DATA TO IT FROM SCRATCH!. NOTE THE CLIENT IS BEHIND A NAT AND
           WE WILL STILL BE ABLE TO SEND PACKAGES TO IT
        */

        // create a new client. this client will be created on a 
        // different computer when I do readl udp punch holing
        UdpClient newClient = ConstructUdpClient(info);

        // send data
        newClient.Send(dataToSend, dataToSend.Length);            
    }

    // Construct a socket with the info received from the client
    static UdpClient ConstructUdpClient(IPEndPoint clientInfo)
    {          
        var ip = clientInfo.Address.ToString();
        var port = clientInfo.Port;

        // this is the part I was missing!!!!
        // the local end point must match. this should be the ip this computer is listening on
        // and also the port            
        UdpClient client = new UdpClient(new IPEndPoint( IPAddress.Any, 11000));

        // lastly we are missing to set the end points. (ip and port client is listening on)

        // the connect method sets the remote endpoints
        client.Connect(ip, port);

        return client;
    }
}

客户端代码:

string ipOfServer = "192.168.0.139";
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); // <----- we can receive data now!!!!!

这篇关于UDP 打孔.让服务器与客户端对话的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本站部分内容来源互联网,如果有图片或者内容侵犯您的权益请联系我们删除!

相关文档推荐

DispatcherQueue null when trying to update Ui property in ViewModel(尝试更新ViewModel中的Ui属性时DispatcherQueue为空)
Drawing over all windows on multiple monitors(在多个监视器上绘制所有窗口)
Programmatically show the desktop(以编程方式显示桌面)
c# Generic Setlt;Tgt; implementation to access objects by type(按类型访问对象的C#泛型集实现)
InvalidOperationException When using Context Injection in ASP.Net Core(在ASP.NET核心中使用上下文注入时发生InvalidOperationException)
LINQ many-to-many relationship, how to write a correct WHERE clause?(LINQ多对多关系,如何写一个正确的WHERE子句?)