Boost UDP asynchronous client receiving its own da

2019-09-01 10:28发布

问题:

I am trying to make a simple UDP client/server test using Boost::asio. I've already read the documentation and the official tutorials, but I still couldn't get it right.

My problem is specifically on the client side. Here's what I want the client to do: it has to send a simple datagram to the server running at localhost:12345 and then subsequently listen for the response datagram that the server sends. That's not what I am getting, though. Looks like the client is sending the datagram alright, but right after it receives its own datagram! And before someone asks, no, the server is not running :-)

The function send_datagram() seems to correctly send the datagram, but then async_receive_from() seems to be triggered by that sent datagram. Here's the complete source code for the client:

#include <boost/array.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/format.hpp>
#include <iostream>

using boost::asio::ip::udp;

const char *SERVER_ADDRESS = "localhost";
const char *SERVER_PORT = "12345";

boost::asio::io_service io_service;

udp::resolver resolver(io_service);
udp::resolver::query query(udp::v4(), SERVER_ADDRESS, SERVER_PORT);
udp::endpoint server_endpoint = (udp::endpoint) *resolver.resolve(query);
udp::endpoint remote_endpoint;
boost::asio::ip::udp::socket client_socket(io_service, server_endpoint);
boost::array<char, 2048> recv_buffer;

boost::shared_ptr<std::string> message(new std::string("test"));

void handle_recv(const boost::system::error_code& error, std::size_t /* bytes_transferred */) {

    if (error)
        std::cout << error.message() << std::endl;
    else {
        std::cout << "Datagram received" << std::endl;
    }
}

void handle_send(boost::shared_ptr<std::string> /* message */, const boost::system::error_code& error, std::size_t /* bytes_transferred */) {

    if (error)
        std::cout << error.message() << std::endl;
    else {
        std::cout << "Datagram sent" << std::endl;
    }
}

void recv_datagram() {

    client_socket.async_receive_from(
            boost::asio::buffer(recv_buffer),
            remote_endpoint,
            boost::bind(&handle_recv, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)
    );
}

void send_datagram() {

    client_socket.async_send_to(
            boost::asio::buffer(*message),
            server_endpoint,
            boost::bind(&handle_send, message, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)
    );
}

int main(int argc, char** argv) {

    std::cout << "Server address: " << server_endpoint.address() << std::endl;
    std::cout << "Server port: " << server_endpoint.port() << std::endl;
    std::cout << "Remote address: " << remote_endpoint.address() << std::endl;
    std::cout << "Remote port: " << remote_endpoint.port() << std::endl;

    send_datagram();
    recv_datagram();

    io_service.run();

    std::cout << "Done" << std::endl;

    return 0;
}

Here's the output I'm getting:

Server address: 127.0.0.1
Server port: 12345
Remote address: 0.0.0.0
Remote port: 0
Datagram sent
Datagram received
Done

Again, the server was not running during my tests (in fact it isn't even implemented yet!).

I think I'm doing something terrible stupid, but I still haven't figured out what it is.

回答1:

Yeah, so I found what was my problem:

boost::asio::ip::udp::socket client_socket(io_service, server_endpoint);

I accidentaly bound my client socket to the server endpoint, so it is effectively running on port 12345. When I call async_receive_from(), the socket is listening on port 12345, the same port supposed to run the server.

So the solution was to simply define a new endpoint and pass it in the socket construction:

udp::endpoint local_endpoint;
boost::asio::ip::udp::socket client_socket(io_service, local_endpoint);

Now the output is:

Server address: 127.0.0.1
Server port: 12345
Remote address: 0.0.0.0
Remote port: 0
Datagram sent

And now looks like it is correctly waiting for the server response.

Just to try to clarify what is happening, when the client socket is created, it must be bound to some local port. When local_endpoint is created without parameters, it generates an endpoint pointing to port 0 at no specific network interface. Here's the constructor implementation as in $BOOST_ROOT/boost/asio/ip/detail/impl/endpoint.ipp:

endpoint::endpoint()
  : data_()
{
  data_.v4.sin_family = BOOST_ASIO_OS_DEF(AF_INET);
  data_.v4.sin_port = 0;
  data_.v4.sin_addr.s_addr = BOOST_ASIO_OS_DEF(INADDR_ANY);
}

When the socket constructor receives this special endpoint, it asks for the operating system to choose a random port so that the socket can bind to. To see what port was that:

std::cout << "Local address: " << client_socket.local_endpoint().address() << std::endl;
std::cout << "Local port: " << client_socket.local_endpoint().port() << std::endl;

Example output:

Local address: 0.0.0.0
Local port: 59908