Я пытаюсь реализовать простой эскиз UDP-Holepunching в Java, чтобы проверить его концепцию и использовать его в своем приложении C/С++ позже.
Концепция:
Как из Википедии я понял эту концепцию следующим образом: Пусть A и B являются клиентами за сетевой структурой undefined, а C - общедоступным общедоступным сервером.
- A отправляет пакет на сервер C, сервер сохраняет его IP-адрес и порт. C получит публичный IP-адрес NAT. Выполняя это, NAT перед А создаст маршрут, который передаст все пакеты на этом порту в.
- B делает то же, что и A, отправляя пакет на сервер C, который затем сохраняет его адрес и порт, B NAT создает маршрут и т.д.
- На этом этапе C знает как адрес, так и порт каждого клиента. C отправит адрес и порт B в и от A до B.
- A отправляет пакет в B, который будет отклонен B NAT, но при этом откроется "дыра" в NAT, позволяя дальнейшим пакетам с B пройти.
- B отправляет пакет в A, который достигнет A, поскольку "дыра" была "пробита" раньше. Это также откроет "отверстие" в B NAT, позволяя дальнейшим пакетам с прохода A.
- Теперь выполняется пробой, и A и B должны иметь возможность связываться друг с другом P2P
Все это хорошо работает над локальным хостом (что не является таким большим сюрпризом), но в реальном примере это не удается.
Проблема:
A и B могут подключаться к серверу C, который получает свои пакеты, сохраняет свой адрес и порт и передает его другому клиенту. Но на данный момент это терпит неудачу. A и B не могут общаться друг с другом. Поэтому я спрашиваю себя, где я ошибся. Я потратил несколько дней на поиск рабочих примеров в google и 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".
Прошу прощения за длинный код, но я хотел сохранить его.
Я был бы рад, если бы кто-нибудь из вас мог указать мне на ошибки, которые я делаю, объясните мне, почему это не работает, дайте мне рабочий пример или, по крайней мере, укажите мне альтернативу.