Пусть два UDP-сервера слушают на одном и том же порту? - PullRequest
13 голосов
/ 06 декабря 2010

У меня есть клиент, который отправляет данные через UDP-трансляцию. (Скажем, 127.0.0.255:12345)

Теперь я хочу, чтобы несколько серверов прослушивали эти данные. Чтобы сделать это на локальном компьютере, им нужно использовать порт 12345 для прослушивания.

У меня вопрос: возможно ли это, есть ли недостатки и могут ли быть проблемы с этим подходом?

Есть одна альтернатива, которая, к сожалению, приносит много накладных расходов:
Реализовать какой-то процесс регистрации. При запуске каждый сервер сообщает клиенту свой порт. Затем клиент отправляет сообщения на каждый порт (при необходимости отправлять данные несколько раз, необходимо реализовать какое-то подтверждение связи ...) Вы знаете лучшую альтернативу?

Если это имеет значение:
Я использую C ++ с Boost :: Asio. Программное обеспечение должно быть переносимым (в основном Linux и Windows).

Ответы [ 3 ]

24 голосов
/ 06 декабря 2010

Вам нужно будет связать сокет в обоих процессах с параметром SO_REUSEPORT.Если вы не укажете эту опцию в первом процессе, связывание во втором завершится неудачно.Аналогично, если вы укажете эту опцию в первом, а не во втором, связывание во втором завершится неудачно.Эта опция фактически указывает оба запроса («Я хочу привязать к этому порту, даже если он уже связан другим процессом») и разрешение («другие процессы могут привязываться и к этому порту»).

См. Раздел 4.12 этого документа для получения дополнительной информации.

5 голосов
/ 08 декабря 2010

Этот ответ ссылается на ответ cdhowie, который связал документ, в котором говорится, что SO_REUSEPORT будет иметь эффект, которого я пытаюсь достичь.

Я исследовал, как и если эта опция реализована и сфокусирована в основном на Boost :: Asio и Linux.

Boost :: Asio устанавливает эту опцию, только если ОС равна BSD или MacOSX. Код для этого содержится в файле boost/asio/detail/reactive_socket_service.hpp (Boost версии 1.40, в более новых версиях код был перемещен в другие файлы).
Я удивился, почему Asio не определяет эту опцию для таких платформ, как Linux и Windows.

Есть несколько ссылок, обсуждающих, что это не реализовано в Linux: https://web.archive.org/web/20120315052906/http://kerneltrap.org/mailarchive/linux-netdev/2008/8/7/2851754
http://kerneltrap.org/mailarchive/linux-kernel/2010/6/23/4586155

Также есть патч, который должен добавить эту функциональность в ядро: https://web -beta.archive.org / веб / 20110807043058 / http://kerneltrap.org/mailarchive/linux-netdev/2010/4/19/6274993

Я не знаю, существует ли эта опция для Windows, но определение portable в качестве атрибута для программного обеспечения, работающего и в Linux, означает, что SO_REUSEPORT зависит от ОС, и для моего вопроса не существует переносимого решения. .

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

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

4 голосов
/ 22 июля 2014

Несколько источников объясняют, что вы должны использовать SO_REUSEADDR для Windows.Но никто не упоминает, что можно получить UDP-сообщение с без привязки сокета.Приведенный ниже код привязывает сокет к локальной точке listen_endpoint, что очень важно, потому что без этого вы можете и будете получать ваши UDP-сообщения, но по умолчанию вы будете иметь исключительное право собственности на порт.reuse_address (true) на сокете (или на акцепторе при использовании TCP) и впоследствии связывает сокет, это позволит нескольким приложениям или нескольким экземплярам вашего собственного приложения делать это снова, и каждый получит все сообщения.*

// Create the socket so that multiple may be bound to the same address.
boost::asio::ip::udp::endpoint listen_endpoint(
    listen_address, multicast_port);

// == important part ==
socket_.open(listen_endpoint.protocol());
socket_.set_option(boost::asio::ip::udp::socket::reuse_address(true));
socket_.bind(listen_endpoint);
// == important part ==

boost::array<char, 2000> recvBuffer;
socket_.async_receive_from(boost::asio::buffer(recvBuffer), m_remote_endpoint,
        boost::bind(&SocketReader::ReceiveUDPMessage, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)
...