С++ Пробивка отверстий UDP (RTP)

Я делаю программу голосового чата клиент-сервер (неуправляемый С++, win32), в которой клиенты подключаются к серверу с использованием TCP, а функции текстового чата/чата выполняются в TCP, в то время как вся аудиопередача отправляется через отдельный сокет UDP/RTP (используя API из JRTPLIB).

Таким образом, IP-адрес известен из TCP-соединения, и номер порта RTP-сокета может быть отправлен после установления соединения.

Проблема в том, что в TCP только сервер должен выполнять переадресацию портов для обмена данными в обоих направлениях, поскольку вы устанавливаете соединение, в то время как в UDP вам нужно использовать recvfrom() - для чего, на самом деле, нужно, чтобы порты были открыты в первое место на стороне клиента, чего я не хочу (да и не нужно если смотреть на любую многопользовательскую игру или VoIP клиент)

Чтение источников, в которых говорится об UDP Hole Punching (например, http://en.wikipedia.org/wiki/UDP_hole_punching), например, они продолжают упоминать о начале диалога UDP с сервером. Вот в чем дело - как вы на самом деле начинаете udp-разговор (в обе стороны) с сервером без необходимости открытия клиентом каких-либо портов? в TCP, как я уже упоминал, вам просто нужно подключиться() к серверу, и связь возможна в обоих направлениях.

Кроме того, я знаю, что RTP основан на UDP, но есть ли что-то еще, что я должен знать о пробивке отверстий RTP (опять же, с использованием JRTPLIB), что отличает его от UDP?

Заранее спасибо!


person KaiserJohaan    schedule 08.01.2011    source источник
comment
Я немного запутался - пробивка отверстий UDP выполняется для обхода NAT, т. Е. Когда вам нужно подключиться к одноранговой сети, и оба одноранговых узла сидят за частной сетью (или маршрутизатором). Сервер используется только как способ «познакомить» двух клиентов. Это то, что вы пытаетесь сделать?   -  person zebrabox    schedule 08.01.2011
comment
Я пытаюсь установить соединение UDP/RTP между клиентами, подключенными к серверу, без открытия клиентами каких-либо портов. Насколько я понимаю, клиенты обмениваются IP/портами с сервером, который затем делится ими между клиентами, чтобы они могли напрямую общаться друг с другом.   -  person KaiserJohaan    schedule 08.01.2011
comment
Что вы подразумеваете под открытием любых портов? Для их связи должны быть открыты порты, что и делает перфорация отверстий UDP.   -  person nos    schedule 08.01.2011
comment
Как тогда онлайн-игры и ventrilo/skype позволяют общаться? В этих приложениях вам не нужно открывать какие-либо порты для связи UDP.   -  person KaiserJohaan    schedule 08.01.2011
comment
@KaiserJohaan Я понимаю, почему ты так думаешь. Каждый клиент открывает порт к серверу. Теперь у сервера есть «проходимый» обратный путь через NAT к внутреннему клиенту. Затем сервер отправляет сведения о конечной точке каждому клиенту, который затем использует этот IP/порт для связи. Таким образом, порт не открывается повторно для одноранговой связи.   -  person zebrabox    schedule 08.01.2011
comment
@zebrabox: Да, теперь я вижу, что неправильно понял определение перфорации отверстий. Порт, используемый на этапе подключения к серверу, совпадает с портом, используемым на этапе передачи между клиентами. В любом случае, если я смогу спасти ветку, описанная мной проблема с приложениями (играми, VOIP и т. д.), которые обмениваются данными через UDP без открытия портов, все еще озадачивает меня.   -  person KaiserJohaan    schedule 08.01.2011
comment
@KaiserJohaan - я бы порекомендовал посмотреть код в libjingle, который является одноранговой библиотекой Google code.google.com/apis/talk/libjingle/index.html   -  person zebrabox    schedule 08.01.2011
comment
@KaiserJohaan Многие из них просто общаются через сервер, и в этом случае проблем нет. Это когда клиенты должны общаться напрямую, вы должны начать обманывать шлюзы nat. Это описано на странице википедии, на которую вы ссылаетесь, а также, например. brynosaurus.com/pub/net/p2pnat , brynosaurus.com/pub/net/internet-drafts/. Фактическая реализация для этого нетривиальна.   -  person nos    schedule 08.01.2011
comment
@nos Действительно - обход NAT невозможен для определенных типов конфигурации NAT, поэтому в этих случаях вам либо нужно пройти через сервер ретрансляции, либо не поддерживать эти типы NAT. Как вы говорите, реализовать это очень нетривиально!   -  person zebrabox    schedule 08.01.2011
comment
Понятно, значит, связь должна передаваться через сервер. Но опять же, для клиента, как они могут получать данные с сервера с помощью udp, фактически не открывая порты, как это делается через TCP с помощью connect ()?   -  person KaiserJohaan    schedule 08.01.2011
comment
Когда клиент обращается к серверу для обмена информацией о порте, соединение открывается как на клиенте, так и на устройстве NAT. Если у вас есть хорошо работающее устройство NAT, одноранговый клиент может использовать это же соединение для обратного подключения. В этот момент связь больше не передается через сервер.   -  person Ravi    schedule 08.01.2011
comment
@KaiserJohaan клиент должен отправить первый пакет UDP. Это открывает порт в клиентском шлюзе, чтобы он мог получать данные с сервера.   -  person nos    schedule 09.01.2011


Ответы (1)


Есть два возможных определения «открытия порта». Один открывает порт с помощью bind() для UDP или listen() для TCP, другой открывает порт в брандмауэре.

Вам нужно открыть порт вызовом API, чтобы что-то получить, от этого никуда не деться, но вы, вероятно, это понимаете, поэтому я думаю, что вы имеете в виду открытие порта в брандмауэре. Но вам не нужно делать это на стороне, которая инициирует связь (клиент). Это относится как к TCP, так и к UDP, если только ваш брандмауэр не настроен в очень параноидальном режиме. Любой разумный брандмауэр разрешит ответ сервера на UDP-порт, если некоторое время назад с этого порта на тот же сервер была отправлена ​​дейтаграмма. Вам нужно пробивать отверстия только в том случае, если обе стороны находятся за брандмауэрами / маршрутизаторами NAT. Вот как это делает Скайп.

Более того, вам даже не нужно возиться с recvfrom() и подобными вещами. Вы можете просто привязать() сокет UDP, а затем использовать connect() и recv()/send() или read()/write() точно так же, как и с TCP.

person Sergei Tachenov    schedule 08.01.2011
comment
Я вижу, это имеет смысл. Для клиента, устанавливает ли connect() это соединение или вам действительно нужно отправить() что-то для брандмауэра, чтобы после этого принимать входящие пакеты на этот порт? - person KaiserJohaan; 08.01.2011
comment
@Kaiser, нет, поскольку UDP - это протокол без установления соединения, все, что делает вызов connect(), - это просто устанавливает адрес по умолчанию для отправки или получения, поэтому вам не нужно указывать его каждый раз. Итак, вы должны отправить () что-то сначала. - person Sergei Tachenov; 08.01.2011
comment
Я понимаю. Спасибо, я собираюсь попробовать это! - person KaiserJohaan; 08.01.2011