UDP дырокол. Поговорите с клиентом на сервере

Я много читал о том, как реализовать дырокол UDP, но по какой-то причине я не могу заставить его работать.

Для тех, кто не знает, что такое пробивание отверстий в udp, вот мое собственное определение:

Цель состоит в том, чтобы иметь возможность передавать данные между двумя клиентами (клиент A и клиент B) с помощью сервера. Таким образом, клиент А подключается к серверу и отправляет свою информацию. Клиент Б делает то же самое. На сервере есть необходимая информация, чтобы Клиент А мог отправлять данные Клиенту Б и наоборот. Поэтому сервер предоставляет эту информацию обоим клиентам. Как только оба клиента получат эту информацию друг о друге, можно начать отправку и получение данных между этими клиентами без помощи сервера.

Моя цель - сделать то, что я только что описал (пробивание дырок в udp). Прежде чем сделать это, я думаю, будет полезно иметь возможность подключения с сервера к клиенту. Для этого я планирую отправить на сервер информацию о клиенте. Как только сервер получит эту информацию, попытайтесь подключиться к клиенту с нуля. Как только я смогу выполнить это, у меня должно быть все, что мне нужно, чтобы начать осуществлять настоящую пробивку дырок в udp.

Вот как у меня все настроено:

Верхний маршрутизатор имеет сервер, а нижний маршрутизатор подключен к портам локальной сети. Нижний маршрутизатор (NAT) подключен к верхнему маршрутизатору через порт WAN. И клиентский компьютер подключен к нижнему маршрутизатору к одному из его портов LAN.

Таким образом, в этом соединении клиент может видеть сервер, но сервер не может видеть клиент.

Итак, алгоритм, который я сделал в псевдокоде:

  • Клиент подключается к серверу.
  • Клиент отправляет несколько пакетов UDP на сервер, чтобы открыть некоторые порты на NAT
  • Отправьте на сервер информацию о том, какие порты слушает клиент.
  • Как только сервер получит эту информацию, попытайтесь подключиться к клиенту с нуля.

Вот реализация в коде:

Сервер:

static void Main()
{     
    /* Part 1 receive data from client */
    UdpClient listener = new UdpClient(11000);
    IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, 11000);
    string received_data;
    byte[] receive_byte_array = listener.Receive(ref groupEP);       
    received_data = Encoding.ASCII.GetString(receive_byte_array, 0, receive_byte_array.Length);

    // get info
    var ip = groupEP.Address.ToString();
    var port = groupEP.Port;

    /* Part 2 atempt to connect to client from scratch */
    // now atempt to send data to client from scratch once we have the info       
    Socket sendSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
    IPEndPoint endPointClient = new IPEndPoint(IPAddress.Parse(ip), port);
    sendSocket.SendTo(Encoding.ASCII.GetBytes("Hello"), endPointClient);
}

Клиент:

static void Main(string[] args)
{
    /* Part 1 send info to server */
    Socket sending_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram,  ProtocolType.Udp);
    IPAddress send_to_address = IPAddress.Parse("192.168.0.132");
    IPEndPoint sending_end_point = new IPEndPoint(send_to_address, 11000);
    sending_socket.SendTo(Encoding.ASCII.GetBytes("Test"), sending_end_point);

    // get info
    var port = sending_socket.LocalEndPoint.ToString().Split(':')[1];

    /* Part 2 receive data from server */
    IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, int.Parse(port));
    byte[] buffer = new byte[1024];
    sending_socket.Receive(buffer);
} 

По какой-то причине это сработало несколько раз! Это работает, когда клиент успешно получает данные на линии: sending_socket.Receive(buffer);

На что обратить внимание: если на сервере во второй части я использовал переменную instance listner вместо создания новой переменной sendSocket и отправлять байты через эту переменную, клиент может получить отправляемые данные. Помните, что вторая часть сервера будет реализована вторым клиентом B, поэтому я снова инициализирую переменные с нуля...


Редактировать:

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

У меня есть объект типа UdpClient. Я могу отправить данные с этим объектом на другой узел. Если я создаю другой объект того же типа с теми же свойствами и пытаюсь отправить данные, он не работает! Я мог бы пропустить, чтобы инициализировать некоторые переменные. Я могу установить частные переменные с отражением, поэтому у меня не должно быть проблем. В любом случае вот код сервера:

public static void Main()
{
    // wait for client to send data
    UdpClient listener = new UdpClient(11000);
    IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, 11000);        
    byte[] receive_byte_array = listener.Receive(ref groupEP);

    // connect so that we are able to send data back
    listener.Connect(groupEP);

    byte[] dataToSend = new byte[] { 1, 2, 3, 4, 5 };

    // now let's atempt to reply back

    // this part does not work!
    UdpClient newClient = CopyUdpClient(listener, groupEP);
    newClient.Send(dataToSend, dataToSend.Length);

    // this part works!
    listener.Send(dataToSend, dataToSend.Length);
}

static UdpClient CopyUdpClient(UdpClient client, IPEndPoint groupEP)
{
    var ip = groupEP.Address.ToString();
    var port = groupEP.Port;
    var newUdpClient = new UdpClient(ip, port);
    return newUdpClient;
}

код клиента в основном отправляет данные на сервер, а затем ожидает ответа:

    string ipOfServer = "192.168.0.132";
    int portServerIsListeningOn = 11000;

    // send data to server
    Socket sending_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
    IPAddress send_to_address = IPAddress.Parse(ipOfServer);
    IPEndPoint sending_end_point = new IPEndPoint(send_to_address, portServerIsListeningOn);
    sending_socket.SendTo(Encoding.ASCII.GetBytes("Test"), sending_end_point);

    // get info
    var port = sending_socket.LocalEndPoint.ToString().Split(':')[1];

    // now wait for server to send data back
    IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, int.Parse(port));
    byte[] buffer = new byte[1024];
    sending_socket.Receive(buffer); // <----- keeps waiting in here :(

обратите внимание, что клиент находится за маршрутизатором (NAT), иначе у меня не будет этой проблемы. Причина, по которой я хотел бы скопировать udpClient, заключается в том, что я могу отправить эту переменную на другой компьютер, позволяя другому компьютеру отправлять данные клиенту.

Итак, мой вопрос, почему оригинальный объект listener возможность отправлять данные, но newClient не умеет? Клиент продолжает ждать в очереди sending_socket.Receive(buffer); даже после того, как сервер выполнит строку: newClient.Send(dataToSend, dataToSend.Length);, клиент успешно получает данные, когда слушатель отправляет данные, но не newClient. Почему, если обе переменные имеют одинаковый IP-адрес и порт назначения? как переменные отличаются?

Примечание: если сервер и клиент находятся в одной сети, то копия работает и переменная newClient умеет отправлять данные клиенту. Чтобы смоделировать эту проблему, клиент должен находиться за NAT (маршрутизатором). Пример такой сети может состоять из двух маршрутизаторов. давайте назовем их маршрутизатором X и маршрутизатором Y. Вам также нужен вызов сервера, который S. и клиент C. так что S можно подключить к одному из портов LAN X. C можно подключить к одному из портов LAN Y. Наконец, подключите порт WAN Y к одному из портов LAN X.

4 ответа

Решение

Наконец-то нашел ответ! Вот реализация только с клиентом и сервером. Моя следующая попытка будет использовать 3 компьютера. В любом случае надеюсь, что это поможет:

Код сервера:

class Program
{
    static byte[] dataToSend = new byte[] { 1, 2, 3, 4, 5 };

    // get the ip and port number where the client will be listening on
    static IPEndPoint GetClientInfo()
    {
        // wait for client to send data
        using (UdpClient listener = new UdpClient(11000))
        {
            IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, 11000);
            byte[] receive_byte_array = listener.Receive(ref groupEP);

            return groupEP;
        }
    }

    static void Main(string[] args)
    {
        var info = GetClientInfo(); // get client info

        /* NOW THAT WE HAVE THE INFO FROM THE CLIENT WE ARE GONG TO SEND
           DATA TO IT FROM SCRATCH!. NOTE THE CLIENT IS BEHIND A NAT AND
           WE WILL STILL BE ABLE TO SEND PACKAGES TO IT
        */

        // create a new client. this client will be created on a 
        // different computer when I do readl udp punch holing
        UdpClient newClient = ConstructUdpClient(info);

        // send data
        newClient.Send(dataToSend, dataToSend.Length);            
    }

    // Construct a socket with the info received from the client
    static UdpClient ConstructUdpClient(IPEndPoint clientInfo)
    {          
        var ip = clientInfo.Address.ToString();
        var port = clientInfo.Port;

        // this is the part I was missing!!!!
        // the local end point must match. this should be the ip this computer is listening on
        // and also the port            
        UdpClient client = new UdpClient(new IPEndPoint( IPAddress.Any, 11000));

        // lastly we are missing to set the end points. (ip and port client is listening on)

        // the connect method sets the remote endpoints
        client.Connect(ip, port);

        return client;
    }
}

код клиента:

string ipOfServer = "192.168.0.139";
int portServerIsListeningOn = 11000;

// send data to server
Socket sending_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
IPAddress send_to_address = IPAddress.Parse(ipOfServer);
IPEndPoint sending_end_point = new IPEndPoint(send_to_address, portServerIsListeningOn);
sending_socket.SendTo(Encoding.ASCII.GetBytes("Test"), sending_end_point);

// get info
var port = sending_socket.LocalEndPoint.ToString().Split(':')[1];

// now wait for server to send data back
IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, int.Parse(port));
byte[] buffer = new byte[1024];
sending_socket.Receive(buffer); // <----- we can receive data now!!!!!

Хм, я думаю, что вы путаете несколько вещей здесь. Во-первых, это действительно называется дыроколом UDP. Позвольте мне попытаться объяснить, как это должно работать.

NAT-маршрутизаторы обычно выполняют сопоставление портов при пересылке пакетов из внутренней частной сети во внешний Интернет.

Скажем, вы создали UDP-сокет на машине за NAT и отправили дейтаграмму на какой-то внешний IP / порт. Когда IP-пакет, переносящий эту дейтаграмму, покидает отправляющую машину, его IP-заголовок имеет поле адреса источника, установленное на локальный не глобально маршрутизируемый частный IP-адрес (например, 192.168.1.15), а в заголовке UDP поле исходного порта установлено на любой порт, который был назначен сокету (либо явно через привязку, либо неявно выбирается ОС из временных портов). Я позвоню по этому номеру порта источника P1,

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

Итак, теперь вы "пробили дыру" в маршрутизаторе - UDP-пакеты отправляются обратно на маршрутизатор в порт P2 пересылаются на внутренний компьютер через порт UDP P1, Опять же, в зависимости от реализации NAT, это может быть ограничено только пакетами с исходного целевого IP-адреса и целевого порта UDP.

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

Надеюсь это поможет.

Рассматривали ли вы использование UPnP на клиенте для настройки обхода NAT для разрешения входящих пакетов на конкретный порт? Тогда клиенту нужно будет только сообщить входящий IP-адрес и порт серверу и подождать, пока сервер отправит пакеты. http://en.wikipedia.org/wiki/Universal_Plug_and_Play

Кажется, вы можете подключиться к серверу в первый раз. После успешного подключения вам необходимо каждый раз закрывать и отключать подключение. Пожалуйста, проверьте этот пример кода http://codeidol.com/csharp/csharp-network/Connectionless-Sockets/A-Simple-UDP-Application/

Другие вопросы по тегам