我读了很多关于如何实现UDP打孔的文章,但是由于某些原因,我不能让它工作。
对于那些不熟悉什么是udp打孔的人,这里有我自己的定义:
目标是能够在服务器的帮助下在两个客户端(客户端A和客户端B)之间传输数据。因此,客户端A连接到服务器并发送其信息。客户端B也执行同样的操作。服务器具有必要的信息,以便客户端A能够向客户端B发送数据,反之亦然。因此,服务器将该信息提供给两个客户端。一旦两个客户端都有了关于彼此的信息,就可以开始在这些客户端之间发送和接收数据,而不需要服务器的帮助。
我的目标是能够做我刚才描述的事情(udp打孔)。在这样做之前,我认为能够从服务器连接到客户端会很有帮助。为了做到这一点,我计划向服务器发送有关客户端的信息。一旦服务器收到该信息,就尝试从头开始连接到客户端。一旦我能够做到这一点,我应该有我需要的一切开始实现真正的udp打洞。
下面是我的设置:
顶部路由器将服务器和底部路由器连接到LAN端口。底部路由器(NAT)通过其WAN端口连接到顶部路由器。并且客户计算机通过其LAN端口之一连接到底部路由器。
因此,在该连接中,客户端能够看到服务器,但服务器无法看到客户端。
所以我用伪代码实现的算法是:
下面是代码的实现:
服务器:
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);
行等待。当监听器发送数据而不是newClient时,客户端成功接收数据。如果两个变量的目的IP和端口相同,为什么会出现这种情况?这些变量有什么不同?
注意:如果服务器和客户端在同一网络上,则复制可以正常工作,并且变量newClient
能够将数据发送到客户端。要模拟此问题,客户端必须位于NAT (路由器)之后。这种网络的一个例子可以由两个路由器组成。我们称它们为路由器X和路由器Y。您还需要一个名为S服务器和一个客户端C。因此,S可以连接到X的LAN端口之一。C可以连接到Y的LAN端口之一。最后,将Y的WAN端口连接到X的LAN端口之一。
发布于 2012-09-02 01:12:15
终于找到答案了!下面是一个客户端和一个服务器的实现。我的下一次尝试是使用3台计算机。无论如何,希望这能有所帮助:
服务器代码:
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!!!!!
发布于 2012-08-31 03:02:16
嗯,我觉得你把几件事搞混了。首先,它真的叫做UDP hole punching。让我来解释一下这应该是如何工作的。
NAT路由器在将数据包从内部专用网络转发到外部互联网时通常会执行。
假设您在NAT之后的机器上创建了一个UDP套接字,并向某个外部IP/端口发送了一个数据报。当承载该数据报的IP分组离开发送机时,其IP报头的源地址字段被设置为本地非全局可路由的专用IP地址(如192.168.1.15
),并且UDP报头的源端口字段被设置为分配给套接字的任何端口(显式地通过绑定,或者由OS从临时端口隐式地挑选)。我将这个源端口号称为P1
。
然后,当NAT路由器在外部网络上发送该数据包时,它会将源IP地址重写为它自己的外部IP地址(否则将无法路由回数据包),并且通常会将源UDP端口重写为其他值(可能是因为专用网络上的某些其他主机使用相同的源端口,这会造成歧义)。原始源端口和新端口号(让我们将其标记为P2
)之间的映射保留在路由器中,以匹配返回的数据包。该映射也可能特定于目标IP地址和目标UDP端口。
因此,现在您已经在路由器上“打了一个洞”--发送回路由器到端口P2
的UDP数据包被转发到UDP端口P1
上的内部机器。同样,根据NAT实施情况,这可能仅限于来自原始目标IP地址和目标UDP端口的数据包。
对于客户端到客户端的通信,您必须通过服务器将一个端口的外部IP/端口告知另一个,希望NAT路由器将相同的内部源端口映射到相同的外部源端口。然后,客户端将使用这些数据向彼此发送数据包。
希望这能有所帮助。
发布于 2012-08-31 06:30:29
您是否考虑过在客户端上使用UPnP来配置NAT穿越,以允许特定端口传入数据包?然后,客户端将只需要将入站IP和端口传送到服务器,并等待服务器发送数据包。http://en.wikipedia.org/wiki/Universal_Plug_and_Play
https://stackoverflow.com/questions/12136031
复制相似问题