Question

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. 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.

Was it helpful?

Solution

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() 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).

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    /               |        |'
'--------'                                                   '--------'

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.

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.

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top