简体   繁体   English

使用Boost.Asio进行广播问题

[英]Issue with broadcast using Boost.Asio

I apologize in advance if the question has been previously answered, but I've searched and found nothing that helps me. 如果之前已经回答了这个问题,我会事先道歉,但我已经搜索过,发现什么都没有帮助我。 As indicated by the question's title, I'm trying to broadcast a package from a server to a set of clients listening for any message. 正如问题的标题所示,我正在尝试将一个包从服务器广播到一组客户端来监听任何消息。

The client will count the number of messages it receives during one second. 客户端将计算在一秒钟内收到的消息数。

The server side of things goes like this: 服务器端的事情是这样的:

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;

}; };

It is initialized and run in the following way: 它已初始化并以下列方式运行:

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;
}

This (apparently) works fine. 这(显然)工作正常。 Now comes the client... 客户来了...

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";
    }

This works when running both the server and client on the same machine, but doesn't when I run the server on a machine different from that of where I run the client. 这在同一台机器上同时运行服务器和客户端时都有效,但是当我在与运行客户端的机器不同的机器上运行服务器时则不行。

First thing is, it seems odd to me that I have to resolve the server's address. 首先,我必须解决服务器的地址似乎很奇怪。 Perhaps I don't know how broadcasting really works, but I thought the server would send a message using its socket with the broadcast option turned on, and it would arrive to all the sockets in the same network. 也许我不知道广播是如何工作的,但我认为服务器将使用其套接字发送消息并打开广播选项,它将到达同一网络中的所有套接字。

I read you should bind the client's socket to the address_v4::any() address. 我读到你应该将客户端的套接字绑定到address_v4::any()地址。 I did, it doesn't work (says something about a socket already using the address/port). 我做了,它不起作用(说一下已经使用地址/端口的套接字)。

Thanks in advance. 提前致谢。

PS: I'm under Windows 8. PS:我在Windows 8下。

I am a bit surprised this works on the same machine. 我在同一台机器上工作有点惊讶。 I would not have expected the client, listening to port 1666, to receive data being sent to the broadcast address on port 8888. 我不希望客户端,侦听端口1666,接收发送到端口8888上的广播地址的数据。

bind() assigns a local endpoint (composed of a local address and port) to the socket. bind()将一个本地端点(由本地地址和端口组成bind()分配给套接字。 When a socket binds to an endpoint, it specifies that the socket will only receive data sent to the bound address and port. 当套接字绑定到端点时,它指定套接字仅接收发送到绑定地址和端口的数据。 It is often advised to bind to address_v4::any() , as this will use all available interfaces for listening. 通常建议绑定到address_v4::any() ,因为这将使用所有可用的接口进行侦听。 In the case of a system with multiple interfaces (possible multiple NIC cards), binding to a specific interface address will result in the socket only listening to data received from the specified interface [1] . 对于具有多个接口(可能是多个NIC卡)的系统,绑定到特定接口地址将导致套接字仅侦听从指定接口[1]接收的数据。 Thus, one might find themselves obtaining an address through resolve() when the application wants to bind to a specific network interface and wants to support resolving it by providing the IP directly (127.0.0.1) or a name (localhost). 因此,当应用程序想要绑定到特定网络接口并希望通过直接提供IP(127.0.0.1)或名称(localhost)来支持解析时,可能会发现自己通过resolve()获取地址。

It is important to note that when binding to a socket, the endpoint is composed of both an address and port. 需要注意的是,绑定到套接字时,端点由地址端口组成。 This is the source of my surprise that it works on the same machine. 这令我惊讶,因为它可以在同一台机器上运行。 If the server is writing to broadcast:8888, a socket bound to port 1666 should not receive the datagram. 如果服务器正在写入广播:8888,则绑定到端口1666的套接字不应接收数据报。 Nevertheless, here is a visual of the endpoints and networking: 不过,这里是端点和网络的视觉效果:

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

The server binds to any address and any port, enables the broadcast option, and sends data to the remote endpoint (broadcast:8888). 服务器绑定到任何地址和任何端口,启用广播选项,并将数据发送到远程端点(广播:8888)。 Clients bound to the any address on port 8888 should receive the data. 绑定到端口8888上任何地址的客户端应该接收数据。

A simple example is as follows. 一个简单的例子如下。

The server: 服务器:

#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);
}

The client: 客户端:

#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;
}

When the client is not co-located with the server, then it could be a variety of network related issues: 当客户端与服务器不在同一位置时,则可能存在各种与网络相关的问题:

  • Verify connectivity between the server and client. 验证服务器和客户端之间的连接。
  • Verify firewall exceptions. 验证防火墙例外。
  • Verify broadcast support/exceptions on the routing device. 验证路由设备上的广播支持/例外。
  • Use a network analyzer tool, such as Wireshark , to verify that the time to live field in the packets is high enough that it will not be discarded during routing. 使用网络分析器工具(如Wireshark )验证数据包中的生存时间字段是否足够高,以便在路由期间不会丢弃。

1. On Linux, broadcast datagrams received by an adapter will not be passed to a socket bound to a specific interface , as the datagram's destination is set to the broadcast address. 1.在Linux上,由适配器接收的广播数据报不会传递到绑定到特定接口的套接字,因为数据报的目标设置为广播地址。 On the other hand, Windows will pass broadcast datagrams received by an adapter to sockets bound to a specific interface . 另一方面,Windows会将适配器接收的广播数据报传递给绑定到特定接口的套接字。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM