Пример пробивания отверстий UDP на Java

Я хочу сделать UDP Hole Punching с двумя клиентами с помощью сервера со статическим IP. Сервер ждет двух клиентов на портах 7070 и 7071. После этого он отправляет друг другу IP-адрес и порт. Эта часть работает нормально. Но я не могу установить связь между двумя клиентами. Я пробовал код в разных сетях Wi-Fi и в мобильной сети 3G. Клиентская программа выдает исключение IO-Exception «Нет маршрута к хосту». Код клиента используется для обоих клиентов. Один раз выполняется с портом 7070 и один раз с 7071.

Как вы думаете, правильно ли я реализовал концепцию пробивки отверстий UDP? Любые идеи, чтобы заставить его работать? Сначала код сервера, затем код клиента.

Спасибо за помощь.

Код сервера:

public class UDPHolePunchingServer {

    public static void main(String args[]) throws Exception {

    // Waiting for Connection of Client1 on Port 7070
    // ////////////////////////////////////////////////

    // open serverSocket on Port 7070
    DatagramSocket serverSocket1 = new DatagramSocket(7070);

    System.out.println("Waiting for Client 1 on Port "
            + serverSocket1.getLocalPort());

    // receive Data
    DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024);
    serverSocket1.receive(receivePacket);

    // Get IP-Address and Port of Client1
    InetAddress IPAddress1 = receivePacket.getAddress();
    int port1 = receivePacket.getPort();
    String msgInfoOfClient1 = IPAddress1 + "-" + port1 + "-";

    System.out.println("Client1: " + msgInfoOfClient1);

    // Waiting for Connection of Client2 on Port 7071
    // ////////////////////////////////////////////////

    // open serverSocket on Port 7071
    DatagramSocket serverSocket2 = new DatagramSocket(7071);

    System.out.println("Waiting for Client 2 on Port "
            + serverSocket2.getLocalPort());

    // receive Data
    receivePacket = new DatagramPacket(new byte[1024], 1024);
    serverSocket2.receive(receivePacket);

    // GetIP-Address and Port of Client1
    InetAddress IPAddress2 = receivePacket.getAddress();
    int port2 = receivePacket.getPort();
    String msgInfoOfClient2 = IPAddress2 + "-" + port2 + "-";

    System.out.println("Client2:" + msgInfoOfClient2);

    // Send the Information to the other Client
    // /////////////////////////////////////////////////

    // Send Information of Client2 to Client1
    serverSocket1.send(new DatagramPacket(msgInfoOfClient2.getBytes(),
            msgInfoOfClient2.getBytes().length, IPAddress1, port1));

    // Send Infos of Client1 to Client2
    serverSocket2.send(new DatagramPacket(msgInfoOfClient1.getBytes(),
            msgInfoOfClient1.getBytes().length, IPAddress2, port2));

    //close Sockets
    serverSocket1.close();
    serverSocket2.close();
}

Код клиента

public class UDPHolePunchingClient {

    public static void main(String[] args) throws Exception {
    // prepare Socket
    DatagramSocket clientSocket = new DatagramSocket();

    // prepare Data
    byte[] sendData = "Hello".getBytes();

    // send Data to Server with fix IP (X.X.X.X)
    // Client1 uses port 7070, Client2 uses port 7071
    DatagramPacket sendPacket = new DatagramPacket(sendData,
            sendData.length, InetAddress.getByName("X.X.X.X"), 7070);
    clientSocket.send(sendPacket);

    // receive Data ==> Format:"<IP of other Client>-<Port of other Client>"
    DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024);
    clientSocket.receive(receivePacket);

    // Convert Response to IP and Port
    String response = new String(receivePacket.getData());
    String[] splitResponse = response.split("-");
    InetAddress ip = InetAddress.getByName(splitResponse[0].substring(1));

    int port = Integer.parseInt(splitResponse[1]);

    // output converted Data for check
    System.out.println("IP: " + ip + " PORT: " + port);

    // close socket and open new socket with SAME localport
    int localPort = clientSocket.getLocalPort();
    clientSocket.close();
    clientSocket = new DatagramSocket(localPort);

    // set Timeout for receiving Data
    clientSocket.setSoTimeout(1000);

    // send 5000 Messages for testing
    for (int i = 0; i < 5000; i++) {

        // send Message to other client
        sendData = ("Datapacket(" + i + ")").getBytes();
        sendPacket = new DatagramPacket(sendData, sendData.length, ip, port);
        clientSocket.send(sendPacket);

        // receive Message from other client
        try {
            receivePacket.setData(new byte[1024]);
            clientSocket.receive(receivePacket);
            System.out.println("REC: "
                    + new String(receivePacket.getData()));

        } catch (Exception e) {
            System.out.println("SERVER TIMED OUT");
        }
    }

    // close connection
    clientSocket.close();
}

ОБНОВЛЕНИЕ Код в целом работает. Я попробовал это в двух разных домашних сетях, и это работает. Но он не работает в моей 3G или университетской сети. В 3G я убедился, что NAT снова сопоставляет два порта (порт клиента и порт, назначенный маршрутизатором) вместе, даже после закрытия и открытия clientSocket. Кто-нибудь знает, почему он не работает тогда?


person Crossader    schedule 25.11.2014    source источник


Ответы (3)


Пробивка отверстий UDP не может быть достигнута со всеми типами NAT. Не существует универсального или надежного способа, определенного для всех типов NAT. Даже для симметричного NAT это очень сложно.

В зависимости от поведения NAT сопоставление портов может различаться для разных устройств, отправляющих пакеты UDP. Например, если A отправляет пакет UDP на B, он может получить какой-то порт, например 50000. Но если A отправляет пакет UDP на C, он может получить другое сопоставление, например 50002. Таким образом, в вашем случае отправка пакета на сервер может дать клиенту какой-то порт, но отправка пакета другому клиенту может дать какой-то другой порт.

Вы можете прочитать больше о поведении NAT здесь:

http://tools.ietf.org/html/rfc4787

https://tools.ietf.org/html/rfc5128

Пробивка отверстий UDP не проходит в 3G

person Kunjan Thadani    schedule 30.11.2014

Для симметричного NAT (сеть 3G, подключенная к другой мобильной сети) вам необходимо выполнить перфорацию Multi-UDP.

Видеть:

  1. https://drive.google.com/file/d/0B1IimJ20gG0SY2NvaE4wRVVMbG8/view?usp=sharing

  2. http://tools.ietf.org/id/draft-takeda-symmetric-nat-traversal-00.txt

  3. https://www.goto.info.waseda.ac.jp/~wei/file/wei-apan-v10.pdf

    1. http://journals.sfu.ca/apan/index.php/apan/article/view/75/pdf_31

Либо так, либо передавать все данные через сервер TURN.

person Community    schedule 14.08.2015

Вы правильно используете сервер рандеву, чтобы информировать каждый узел о другом IP / порте на основе соединения UDP. Однако использование общедоступного IP-адреса и порта, представляющего собой комбинацию, которая будет получена соединением, как у вас, означает, что в сценариях, где оба хоста существуют в одной и той же частной сети, NAT требует перевода шпилек, который иногда не поддерживается.

Чтобы исправить это, вы можете отправить IP-адрес и порт, который, по вашему мнению, имеет ваш узел, в сообщении на сервер (частный IP-адрес / порт) и включить это в информацию, которую каждый узел получает о другом. Затем попытайтесь установить соединение как с общедоступной комбинацией (той, которую вы используете), так и с той, которую я только что упомянул, и просто используйте первую, которая успешно установлена.

person nrmad    schedule 18.04.2020