首先抱歉我的英语不好,但这不是我的第一语言。 我的问题是:我正在努力实现一个使用Java的桌面共享的简单软件。第一个问题是如何通信NAT后面的两个客户端。经过对互联网的一些研究,我发现其中一个更好的解决方案是使用UDP打孔技术。 如果我正确理解,此功能按以下方式工作:
A = client1
NA =客户1的nat
B =客户2
NB =客户2的nat
S =公共服务器
1 - A向S
发送UDP数据包2 - S接收公共IP和NA的公共端口
3 - B向S
发送UDP数据包4 - S接收NB的公共IP和公共端口
5 - S通过绑定套接字向A发送一个UDP数据包,公共IP和B端口从A接收第一个数据包
6 - S通过绑定套接字向B发送一个UDP数据包,公共IP和端口为A,接收到来自B的第一个数据包
7 - A向B发送一个UDP数据包,其中包含从S接收到的特性,NB丢弃该数据包,因为它不知道谁是A(它没有该数据包的路由)。但是这在NA中打开了一个洞,可以接受并插入到A从B到达的数据包。
8 - B向A发送UDP数据包,NA可以对数据包进行插入。
据我所知,此方法仅适用于4种NAT类型中的3种:全锥形NAT,受限锥形NAT,受限端口NAT,但不适用于对称NUT。 现在,我意识到一个应该模拟UDP打孔的小软件,这就是代码(我知道它不优雅,但现在我想看看程序是否有效):
客户:
import java.io.IOException;
import java.net.*;
public class JavaClient {
public static void main(String[] args) throws UnknownHostException, SocketException, IOException {
new JavaClient().sendHostInfoToS();
}
private void sendHostInfoToS() {
// TODO code application logic here
byte[] buffer = {
99,99,99,99,99,99,99
} ;
InetAddress address;
try {
//Client contacts server
address = InetAddress.getByName("X.XXX.XX.XXX"); //SERVER
DatagramPacket packet = new DatagramPacket(
buffer, buffer.length, address, 9999
);
DatagramSocket datagramSocket = new DatagramSocket();
datagramSocket.send(packet);
System.out.println(InetAddress.getLocalHost().getHostAddress());
/////////////////////////////////////
//Client waits that server send back a UDP packet containing the information of the second client
DatagramPacket receivePacket = new DatagramPacket(new byte[40], 40);
System.out.println("WAIT RESPONSE...");
datagramSocket.receive(receivePacket);
System.out.println("RESPONSE RECEIVED...");
String sentence = new String( receivePacket.getData());
System.out.println("RECEIVED: " + sentence);
System.out.println("CODING IP...");
Thread.sleep(3000);
String[] p = sentence.split(":");
p[0] = p[0].replace("/","");
System.out.println("END CODING...");
/////////////////////////////////
//Client send to the other client a UDP packet using the information received from the server
byte[] sendData = new byte[8];
String sendString ="test";
sendData = sendString.getBytes();
DatagramPacket sPacket = new DatagramPacket(sendData, sendData.length, InetAddress.getByName(p[0]), Integer.parseInt(p[1].substring(0,5)));
System.out.println("SENDING DATA TO CLIENT " + sPacket.getSocketAddress() + " on port " + Integer.toString(sPacket.getPort()));
DatagramSocket sDatagramSocket = new DatagramSocket();
//datagramSocket = new DatagramSocket();
sDatagramSocket.send(sPacket);
Thread.sleep(500);
System.out.println("DATA SENT...");
/////////////////////////////////////////////
//Client wait a response from the other client
receivePacket = new DatagramPacket(new byte[128], 128);
System.out.println("WAIT RESPONSE...");
//datagramSocket = new DatagramSocket();
sDatagramSocket.receive(receivePacket);
System.out.println("RESPONSE RECEIVED...");
sentence = new String( receivePacket.getData());
System.out.println("RECEIVED: "+ sentence);
/////////////////////////////////
}
catch (Exception e) {
e.printStackTrace();
}
}
}
服务器:
import java.io.IOException;
import java.net.*;
public class JavaServer {
DatagramPacket [] hosts = new DatagramPacket[2];
public static void main(String[] args) throws UnknownHostException, SocketException, IOException {
new JavaServer().waitInUDPConn();
}
DatagramSocket serverSocket;
private void waitInUDPConn() {
try {
serverSocket = new DatagramSocket(9999);
byte[] receiveData = new byte[8];
int indHosts = 0;
while(true) {
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
System.out.println("WAITING DATA...");
serverSocket.receive(receivePacket);
String sentence = new String( receivePacket.getData());
System.out.println("RECEIVED: "+ sentence);
hosts[indHosts] = receivePacket;
indHosts++;
if (indHosts == 2) break;
}
} catch (Exception e) {
}
System.out.println("Hosts 1: "+ hosts[0].getAddress().toString() +", on port "+ Integer.toString(hosts[0].getPort()));
System.out.println("Hosts 2: "+ hosts[1].getAddress().toString() +", on port "+ Integer.toString(hosts[1].getPort()));
sendInfoToC(hosts[0], hosts[1]);
sendInfoToC(hosts[1], hosts[0]);
}
private void sendInfoToC(DatagramPacket dpDest, DatagramPacket dpInfoB) {
try {
byte[] sendData = new byte[8];
System.out.println("START WAIT...");
Thread.sleep(3000);
System.out.println("STOP WAIT...");
InetAddress IPAddress = dpDest.getAddress();
String sendString =dpInfoB.getAddress().toString() +":"+ Integer.toString(dpInfoB.getPort());
sendData = sendString.getBytes();
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, dpDest.getPort());
System.out.println("SENDING PACKET...");
serverSocket.send(sendPacket);
System.out.println("PACKET SENT...");
}
catch (Exception e) {
}
}
}
使用此代码我可以与两个客户端联系服务器,服务器接收来自两个客户端的数据包,服务器发送给A的信息和B的信息,两个客户端都收到信息,但是他们不能一起沟通。 A没有收到来自B的数据包而B没有收到来自A的数据包。所以现在我需要了解我是否支持两个客户端的对称nat。如果我理解其他类型的NAT(全锥形,限制锥形和端口限制锥形),它们对每个外部连接始终使用相同的端口。因此,如果我有和内部电脑的IP:192.168.0.50并且此机器想要到达公共IP(例如54.66.18.22)NAT将在数据包分配公共IP和外部端口(例如端口58000) )。稍后,如果同一台PC(192.168.0.50)想要与另一台外部主机连接,则NAT始终使用相同的外部端口58000.而使用对称NAT,每个连接都将具有不同的外部端口(第一个连接端口58000和第二个连接端口)不同的港口)。现在,如果这是正确的,我认为两个客户端都支持两个对称NAT。我可以确定这一点,因为当我连接到服务器时,从客户端A看到服务器接收到NAT A的公共IP和一个端口,例如55000.如果我关闭客户端并再次运行它,这次我看到服务器收到总是相同的IP(显然),但端口不是55000但55001如果我再次尝试端口是55002,依此类推。同样的事情是第二个客户端,端口从连接到另一个连接增加1。现在,总是如果我理解整个场景,这应该证实我两个客户端都支持对称NAT。但有趣的是,似乎两个NAT都使用一个单位的增量,所以我认为当我从A发送数据包到B和从B发送数据包时,我可以使用端口预测,所以我只是尝试了这个: / p>
DatagramPacket sPacket = new DatagramPacket(sendData, sendData.length, InetAddress.getByName(p[0]), Integer.parseInt(p[1].substring(0,5))+1);
我在从服务器收到的端口上添加了1,因为如果我支持两个对称NAT,那么增量是1端口也是如此。查看示例:
连接到服务器和NAT A向S发送包含以下内容的数据包:45.89.66.125:58000
B连接到服务器并且NAT B向S发送包含以下内容的数据包:144.85.1.18:45000
S将B的信息发送给A,将信息发送给A到B
现在,如果A向B发送此信息,NAT A将创建此地图:
INTERNAL_IP_A:58001-144.85.1.18:45001
对于此连接,NAT A应使用端口58001(最后一个端口+ 1它是对称NAT)
NAT B接收数据包但丢弃它。
现在,如果B使用收到的信息向A发送数据包,NAT B将创建此地图:
INTERNAL_IP_B:45001-45.89.66.125:58001
现在NAT应该接受此数据包,因为在其表中有接收它的信息。
但在这种情况下,没有一个客户端从另一个接收信息。 我究竟做错了什么?或者我还没有理解的是什么?