Пусть два UDP-сервера прослушивают один и тот же порт?

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

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

Мой вопрос: если это возможно, если есть какие-то недостатки и могут быть проблемы с этим подходом.

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

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

Ответ 1

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

Я исследовал, как и если этот вариант реализован и сосредоточен в основном на Boost:: Asio и Linux.

Boost:: Asio устанавливает этот параметр только в том случае, если ОС равна BSD или MacOSX. Код для этого содержится в файле boost/asio/detail/reactive_socket_service.hpp (Boost Version 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/web/20110807043058/http://kerneltrap.org/mailarchive/linux-netdev/2010/4/19/6274993

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

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

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

Ответ 2

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

Дополнительную информацию см. в разделе 4.12 этот документ.

Ответ 3

Несколько источников объясняют, что вы должны использовать SO_REUSEADDR для окон. Но никто не упоминает, что можно получать сообщение 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)