Проблема с трансляцией с использованием Boost.Asio

Я заранее прошу прощения, если на вопрос уже был дан ответ, но я искал и не нашел ничего, что помогло бы мне. Как указано в заголовке вопроса, я пытаюсь передать пакет с сервера группе клиентов, прослушивающих любое сообщение.

Клиент посчитает количество сообщений, полученных им в течение одной секунды.

Серверная сторона вещей выглядит так:

class Server
{
public:

Server(boost::asio::io_service& io)
: socket(io, udp::endpoint(udp::v4(), 8888))
, broadcastEndpoint(address_v4::broadcast(), 8888)
, tickHandler(boost::bind(&Server::Tick, this, boost::asio::placeholders::error))
, timer(io, boost::posix_time::milliseconds(20))
{
socket.set_option(boost::asio::socket_base::reuse_address(true));
socket.set_option(boost::asio::socket_base::broadcast(true));

timer.async_wait(tickHandler);
}

private:

void Tick(const boost::system::error_code&)
{
socket.send_to(boost::asio::buffer(buffer), broadcastEndpoint);

timer.expires_at(timer.expires_at() + boost::posix_time::milliseconds(20));
timer.async_wait(tickHandler);
}

private:

udp::socket socket;
udp::endpoint broadcastEndpoint;

boost::function<void(const boost::system::error_code&)> tickHandler;
boost::asio::deadline_timer timer;

boost::array<char, 100> buffer;

};

Он инициализируется и запускается следующим образом:

int main()
{
try
{
boost::asio::io_service io;
Server server(io);
io.run();
}
catch (const std::exception& e)
{
std::cerr << e.what() << "\n";
}

return 0;
}

Это (по-видимому) работает нормально. Теперь приходит клиент …

void HandleReceive(const boost::system::error_code&, std::size_t bytes)
{
std::cout << "Got " << bytes << " bytes\n";
}

int main(int argc, char* argv[])
{
if (argc != 2)
{
std::cerr << "Usage: " << argv[0] << " <host>\n";
return 1;
}

try
{
boost::asio::io_service io;

udp::resolver resolver(io);
udp::resolver::query query(udp::v4(), argv[1], "1666");

udp::endpoint serverEndpoint = *resolver.resolve(query);
//std::cout << serverEndpoint.address() << "\n";

udp::socket socket(io);
socket.open(udp::v4());

socket.bind(serverEndpoint);

udp::endpoint senderEndpoint;
boost::array<char, 300> buffer;

auto counter = 0;
auto start = std::chrono::system_clock::now();

while (true)
{
socket.receive_from(boost::asio::buffer(buffer), senderEndpoint);
++counter;

auto current = std::chrono::system_clock::now();
if (current - start >= std::chrono::seconds(1))
{
std::cout << counter << "\n";

counter = 0;
start = current;
}
}
}
catch (const std::exception& e)
{
std::cerr << e.what() << "\n";
}

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

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

Я читал, что вы должны привязать сокет клиента к address_v4::any() адрес. Я сделал, это не работает (говорит что-то о сокете, уже использующем адрес / порт).

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

PS: я под Windows 8.

3

Решение

Я немного удивлен, что это работает на той же машине. Я не ожидал, что клиент, прослушивающий порт 1666, получит данные, отправляемые на широковещательный адрес через порт 8888.

bind() назначает местный конечная точка (состоит из локального адреса и порта) к сокету. Когда сокет связывается с конечной точкой, он указывает, что сокет будет принимать только данные, отправленные на связанный адрес и порт. Часто рекомендуется связывать с address_v4::any(), так как при этом будут использоваться все доступные интерфейсы для прослушивания. В случае системы с несколькими интерфейсами (возможно, с несколькими сетевыми картами), привязка к определенному адресу интерфейса приведет к тому, что сокет будет прослушивать только данные, полученные от указанного интерфейса.[1]. Таким образом, можно получить адрес через resolve() когда приложение хочет привязаться к определенному сетевому интерфейсу и хочет поддержать его разрешение путем предоставления IP-адреса напрямую (127.0.0.1) или имени (localhost).

Важно отметить, что при привязке к сокету конечная точка состоит из адреса а также порт. Это источник моего удивления, что он работает на той же машине. Если сервер пишет в широковещательную рассылку: 8888, сокет, привязанный к порту 1666, не должен принимать дейтаграмму. Тем не менее, вот визуальное представление о конечных точках и сети:

                                                               .--------.
.--------.|
.--------. address: any                         address: any .--------.||
|        | port: any      /                  \    port: 8888 |        |||
| server |-( ----------->| address: broadcast |----------> )-| client ||'
|        |                \    port: 8888    /               |        |'
'--------'                                                   '--------'

Сервер связывается с любым адресом и любым портом, включает опцию широковещания и отправляет данные на удаленную конечную точку (широковещание: 8888). Клиенты, привязанные к любому адресу через порт 8888, должны получить данные.

Простой пример заключается в следующем.

Сервер:

#include <boost/asio.hpp>

int main()
{
namespace ip = boost::asio::ip;
boost::asio::io_service io_service;

// Server binds to any address and any port.
ip::udp::socket socket(io_service,
ip::udp::endpoint(ip::udp::v4(), 0));
socket.set_option(boost::asio::socket_base::broadcast(true));

// Broadcast will go to port 8888.
ip::udp::endpoint broadcast_endpoint(ip::address_v4::broadcast(), 8888);

// Broadcast data.
boost::array<char, 4> buffer;
socket.send_to(boost::asio::buffer(buffer), broadcast_endpoint);
}

Клиент:

#include <iostream>

#include <boost/asio.hpp>

int main()
{
namespace ip = boost::asio::ip;
boost::asio::io_service io_service;

// Client binds to any address on port 8888 (the same port on which
// broadcast data is sent from server).
ip::udp::socket socket(io_service,
ip::udp::endpoint(ip::udp::v4(), 8888 ));

ip::udp::endpoint sender_endpoint;

// Receive data.
boost::array<char, 4> buffer;
std::size_t bytes_transferred =
socket.receive_from(boost::asio::buffer(buffer), sender_endpoint);

std::cout << "got " << bytes_transferred << " bytes." << std::endl;
}

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

  • Проверьте соединение между сервером и клиентом.
  • Проверьте исключения брандмауэра.
  • Проверьте широковещательную поддержку / исключения на устройстве маршрутизации.
  • Используйте инструмент сетевого анализа, такой как Wireshark, чтобы убедиться, что время жить Поле в пакетах достаточно велико, чтобы оно не сбрасывалось во время маршрутизации.

1. В Linux широковещательные дейтаграммы, полученные адаптер не будет передано сокету, привязанному к конкретному интерфейс, в качестве пункта назначения дейтаграммы указывается широковещательный адрес. С другой стороны, Windows будет передавать широковещательные дейтаграммы, полученные адаптер к сокетам, привязанным к конкретному интерфейс.

10

Другие решения

Других решений пока нет …