我一直在学习如何实现UDP打洞,但不知为何无法使其生效。
对于不熟悉UDP打洞的人,这是我的定义:
目标是让两个客户端(客户端A和客户端B)能够在服务器的帮助下传输数据。因此,客户端A连接到服务器并发送其信息。客户端B也是如此。服务器有必要的信息,使得客户端A能够向客户端B发送数据,反之亦然。因此,服务器将该信息提供给两个客户端。一旦两个客户端都掌握了关于彼此的信息,就可以开始在两个客户端之间发送和接收数据,而不需要服务器的帮助。
我的目标是能够实现我刚才描述的UDP打洞。 但在此之前,我认为能够从服务器连接到客户端会很有帮助。为了做到这一点,我计划向服务器发送客户端的信息。一旦服务器收到该信息,尝试从头开始连接到客户端。一旦我能够执行此操作,我就应该拥有开始实现真正的UDP打洞所需的一切。
以下是我的设置方式:
顶部路由器连接了服务器和底部路由器到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的对象。 我能够使用该对象将数据发送到其他对等方。 如果我创建具有相同属性的相同类型的另一个对象并尝试发送数据,则不起作用! 我可能错过了初始化某些变量。 我能够使用反射设置私有变量,所以我不应该有问题。 无论如何,这是服务器代码:
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
不能呢?即使在服务器执行以下行之后:newClient.Send(dataToSend, dataToSend.Length);
,客户端仍然在sending_socket.Receive(buffer);
处等待。当监听器发送数据时,客户端成功接收数据,但是使用新的客户端却无法实现。如果这两种变量都具有相同的目标IP和端口,则为什么会出现这种情况?这些变量有何不同?
注意:
如果服务器和客户端在同一网络中,则复制有效,并且变量newClient
能够向客户端发送数据。要模拟此问题,必须将客户端放在NAT(路由器)后面。这样的网络示例可能由两个路由器组成。我们称其为路由器X和路由器Y。您还需要一个名为S的服务器。客户端C连接到Y的其中一个LAN端口。最后,将Y的WAN端口连接到X的其中一个LAN端口。