首先对不起我的英语不好,但这不是我的第一语言 . 我的问题是:我正致力于使用Java实现一个简单的桌面共享软件 . 第一个问题是如何通信NAT后面的两个客户端 . 经过对Internet的一些研究,我发现其中一个更好的解决方案是使用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中打开了一个洞,可以接受并插入到从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发送数据包时,我可以使用端口预测,所以我只是尝试了这个:

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将创建此 Map :

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应该接受这个数据包,因为在它的表中有接收它的信息 .

但是在这种情况下,没有一个客户端从另一个接收信息 . 我究竟做错了什么?或者我没有理解的东西?