NAT 背后的 UDP 孔冲孔概念:问题:实现:
我正在尝试在Java中实现UDP-Holepunching的简单草图,以测试它的概念,并在以后的C / C++应用程序中使用它。
概念:
从维基百科开始,我对这个概念的理解是这样的:让A和B成为未定义的网络结构背后的客户端,让C成为众所周知的公共可访问服务器。
- A 向服务器 C 发送数据包,服务器保存其 IP 地址和端口。C 将获取 A 的 NAT 的公共 IP 地址。执行此操作,A 前面的 NAT 将创建一个路由,该路由将此端口上的所有数据包传递到 A。
- B执行与A相同的操作,将数据包发送到服务器C,然后服务器C将保存其地址和端口,B的NAT创建路由等。
- 此时,C 知道每个客户端的地址和端口。C 会将 B 的地址和端口发送到 A,并将 A 发送到 B。
- A 向 B 发送一个数据包,该数据包将被 B 的 NAT 拒绝,但这样做会在 A 的 NAT 中打开一个“洞”,让来自 B 的更多数据包通过。
- B 向 A 发送一个数据包,该数据包将到达 A,因为之前“打孔”了一个“孔”。这样做还会在B的NAT中打开一个“洞”,让来自A的更多数据包通过。
- 孔打孔现在已经完成了,A和B应该能够相互通信P2P。
这一切都比localhost工作得很好(这不是一个很大的惊喜),但在现实世界的例子中,这失败了。
问题:
A和B都能够连接到服务器C,服务器C获取数据包,存储其地址和端口并将其传输到另一个客户端。但在这一点上,它失败了。A和B无法相互通信。所以我问自己,我哪里做错了。我花了几天时间在谷歌和stackoverflow中搜索工作示例,但我偶然发现的只是使用STUN的建议,这不是我想要的。
实现:
下面我将用Java发布我的草图,因为我不知道我的概念或实现是否有问题。
这是服务器的代码:
public class Server
{
public static void main(String[] args)
{
int port1 = 0, port2 = 0;
String address1 = null, address2;
byte[] bytes = new byte[1024];
try
{
System.out.println("Server waiting");
DatagramSocket ds = new DatagramSocket(789);
while(!Thread.interrupted())
{
DatagramPacket p = new DatagramPacket(bytes, bytes.length);
ds.receive(p);
if(port1 == 0)
{
port1 = p.getPort();
address1 = p.getAddress().getHostAddress();
System.out.println("(1st) Server received:" + new String(bytes) + " from " + address1 + " on port " + port1);
}
else
{
port2 = p.getPort();
address2 = p.getAddress().getHostAddress();
System.out.println("(2nd) Server received:" + new String(bytes) + " from " + address1 + " on port " + port1);
sendConnDataTo(address1, port1, address2, port2, ds);
sendConnDataTo(address2, port2, address1, port1, ds);
}
}
ds.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}
public static void sendConnDataTo(String a1, int p1, String a2, int p2, DatagramSocket ds)
{
byte[] bA, bP;
bA = a1.getBytes();
bP = Integer.toString(p1).getBytes();
DatagramPacket pck;
try
{
pck = new DatagramPacket(bA, bA.length, InetAddress.getByName(a2), p2);
ds.send(pck);
pck = new DatagramPacket(bP, bP.length, InetAddress.getByName(a2), p2);
ds.send(pck);
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
请注意,这只是一些草图,没有真正的应用。服务器应仅接收来自两个客户端的数据包,保存其地址和端口,并将其传递给另一个客户端。
这是客户端的代码:
public class Client
{
private DatagramSocket socket;
private int init = 0;
private String target;
private int port;
public Client()
{
try
{
socket = new DatagramSocket();
}
catch(SocketException e)
{
e.printStackTrace();
}
Thread in = new Thread()
{
public void run()
{
while(true)
{
byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
try
{
socket.receive(packet);
bytes = Arrays.copyOfRange(bytes, 0, packet.getLength());
String s = new String(bytes);
System.out.println("Received: " + s);
if(init == 0)
{
target = s;
System.out.println("Target: " + target);
init++;
}
else if(init == 1)
{
port = Integer.parseInt(s);
System.out.println("Port: " + port);
init++;
}
else System.out.println(new String(bytes));
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
};
in.start();
connectToSupervisor();
}
private void connectToSupervisor()
{
byte[] bytes = new byte[1024];
System.out.println("Greeting server");
System.arraycopy("EHLO".getBytes(), 0, bytes, 0, 4);
try
{
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("localhost"), 789);
socket.send(packet);
System.out.println("Greetings sent...");
}
catch(IOException e)
{
e.printStackTrace();
}
send();
}
private void send()
{
while(init != 2)
{
try
{
Thread.sleep(20L);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
System.out.println("Init completed!");
while(true)
{
byte[] b2 = "Hello".getBytes();
byte[] b1 = new byte[6];
System.arraycopy(b2, 0, b1, 0, b2.length);
try
{
DatagramPacket packet = new DatagramPacket(b1, b1.length, InetAddress.getByName(target), port);
socket.send(packet);
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
public static void main(String[] args)
{
new Client();
}
}
客户端将只向服务器发送一个数据包,从中侦听数据包,从另一个客户端获取连接数据,然后不断向其发送包含“Hello”的数据包。
我很抱歉有很长的代码,但我想保持它的完整。
如果你们中的任何一个人能指出我正在做的错误,解释为什么这不起作用,给我一个工作的例子,或者至少指出我另一种选择,我会很高兴。