使用 Boost.Asio 广播的问题问题、Boost、Asio

2023-09-07 13:27:00 作者:似无

如果之前已经回答了这个问题,我提前道歉,但我已经搜索并没有发现任何对我有帮助的东西.如问题标题所示,我正在尝试将一个包从服务器广播到一组侦听任何消息的客户端.

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.

服务器端是这样的:

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

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

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " <host>
";
        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() << "
";

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

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

这在同一台机器上同时运行服务器和客户端时有效,但当我在与我运行客户端的机器不同的机器上运行服务器时不起作用.

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.

我读到您应该将客户端的套接字绑定到 address_v4::any() 地址.我做到了,它不起作用(说明套接字已经在使用地址/端口).

I read you should bind the client's socket to the address_v4::any() address. I did, it doesn't work (says something about a socket already using the address/port).

提前致谢.

PS:我在 Windows 8 下.

PS: I'm under Windows 8.

推荐答案

我有点惊讶这在同一台机器上工作.我没想到客户端在监听 1666 端口时会收到发送到 8888 端口广播地址的数据.

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.

bind() 为套接字分配一个 local 端点(由本地地址和端口组成).当套接字绑定到端点时,它指定套接字将只接收发送到绑定地址和端口的数据.通常建议绑定到 address_v4::any(),因为这将使用所有可用的接口进行监听.在具有多个接口的系统(可能是多个 NIC 卡)的情况下,绑定到特定接口地址将导致套接字仅侦听从指定接口接收的数据[1].因此,人们可能会发现自己通过 resolve() 当应用程序想要绑定到特定的网络接口并希望通过直接提供 IP (127.0.0.1) 或名称 (localhost) 来支持解析它时.

bind() assigns a local endpoint (composed of a local address and port) to the socket. 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. 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]. 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).

需要注意的是,当绑定到套接字时,端点由地址和端口组成.这是我惊讶的原因,它可以在同一台机器上运行.如果服务器正在写入广播:8888,则绑定到端口 1666 的套接字不应接收数据报.不过,这里是端点和网络的示意图:

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. Nevertheless, here is a visual of the endpoints and networking:

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

服务器绑定到任何地址和任何端口,启用广播选项,并将数据发送到远程端点(广播:8888).绑定到端口 8888 上的任何地址的客户端应该会收到数据.

The server binds to any address and any port, enables the broadcast option, and sends data to the remote endpoint (broadcast:8888). Clients bound to the any address on port 8888 should receive the data.

一个简单的例子如下.

服务器:

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

当客户端与服务器不在同一位置时,可能是各种网络相关问题:

When the client is not co-located with the server, then it could be a variety of network related issues:

验证服务器和客户端之间的连接.验证防火墙例外情况.验证路由设备上的广播支持/例外情况.使用网络分析工具,例如 Wireshark,来验证 生存时间 数据包中的字段足够高,在路由期间不会被丢弃. 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.

1.在 Linux 上,适配器接收的广播数据报不会被传递到绑定到特定接口的套接字,因为数据报的目的地设置为广播地址.另一方面,Windows 会将适配器接收到的广播数据报传递给绑定到特定接口的套接字.

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. On the other hand, Windows will pass broadcast datagrams received by an adapter to sockets bound to a specific interface.

 
精彩推荐
图片推荐