I am trying to get the daytime6 server example (Asynchronous UDP daytime server) in boost working. I compile the below program using
g++ -std=c++11 -g -Wall -pedantic udp_server.cpp -o udp_server -lboost_system
I start the udp_server. I can see port number 13 (UDP) being opened using netstat command.
However If I trying to client to the server using netcat
nc -u localhost 13
it doesn't seem to give any reply.However I can get the asynchronous TCP daytime server to work fine.
#include <ctime>
#include <iostream>
#include <string>
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::udp;
std::string make_daytime_string()
{
using namespace std; // For time_t, time and ctime;
time_t now = time(0);
return ctime(&now);
}
class udp_server
{
public:
udp_server(boost::asio::io_service& io_service)
: socket_(io_service, udp::endpoint(udp::v4(), 13))
{
start_receive();
}
private:
void start_receive()
{
socket_.async_receive_from(
boost::asio::buffer(recv_buffer_), remote_endpoint_,
boost::bind(&udp_server::handle_receive, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_receive(const boost::system::error_code& error,
std::size_t /*bytes_transferred*/)
{
if (!error || error == boost::asio::error::message_size)
{
boost::shared_ptr<std::string> message(
new std::string(make_daytime_string()));
socket_.async_send_to(boost::asio::buffer(*message), remote_endpoint_,
boost::bind(&udp_server::handle_send, this, message,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
start_receive();
}
}
void handle_send(boost::shared_ptr<std::string> message,
const boost::system::error_code& error,
std::size_t bytes_transferred)
{
}
udp::socket socket_;
udp::endpoint remote_endpoint_;
boost::array<char, 1> recv_buffer_;
};
int main()
{
try
{
boost::asio::io_service io_service;
udp_server server(io_service);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
The following command does not send a message:
$ nc -u localhost 13
Instead, netcat will wait, reading from stdin until end-of-file. Upon receiving end-of-file, it will send the message it reads to localhost on port 13 using UDP.
On the other hand, the following command:
$ echo 'msg' | nc -u localhost 13
writes "msg" and end-of-file to netcat's stdin, resulting in netcat sending a UDP datagram containing "msg" to localhost on port 13.
The asynchronous UDP daytime server example responds to any message it receives with the current date and time:
class udp_server
{
public:
udp_server(...)
{
start_receive();
}
private:
void start_receive()
{
socket_.async_receive_from(...,
boost::bind(&udp_server::handle_receive, ...));
}
void handle_receive(...)
{
message = make_daytime_string();
socket_.async_send_to(boost::asio::buffer(message), ...);
}
};
As the first command does not write a message, the udp_server never receives a message to which it can respond. The latter command causes a message to be written, and the udp_server responds with the date and time.
The asynchronous TCP daytime server writes a message upon accepting a connection, then closes the connection. When using TCP, netcat will attempt to connect to the destination immediately. In the case of the tcp_server, netcat will establish a TCP connection, receive the date and time, detect that the remote peer has closed the connection and exit.
Related
I am attempting to use boost::asio to implement a simple device discovery protocol. Basically I want to send a broadcast message (port 9000) with 2 byte payload. Then read the response from the device (assuming currently it exists). In wireshark I can see the broadcast is been sent and that the device is responding. However, in my example code I get that the bytes returned is 0 in the UDP read, not 30 bytes of data.
No. Time Source Destination Protocol Length
1 0.00000 192.168.0.20 255.255.255.255 UDP 44 52271 -> 9000 Len = 2
2 0.00200 192.168.0.21 192.168.0.20 UDP 72 9000 -> 52271 Len = 30
Should I be reading from a different endpoint than broadcastEndpoint? How do I get the end point?
I am new to asio and trying to teach my self, but I cannot work what I have done wrong.
#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <iostream>
class udp_find {
public:
udp_find(boost::asio::io_context& service, unsigned int port)
: broadcastEndpoint_(boost::asio::ip::address_v4::broadcast(), port),
socket_(service)
{
socket_.open(boost::asio::ip::udp::v4());
socket_.set_option(boost::asio::ip::udp::socket::reuse_address(true));
socket_.set_option(boost::asio::socket_base::broadcast(true));
boost::array<unsigned int, 2> data = {255, 255};
socket_.async_send_to(
boost::asio::buffer(data, 2), broadcastEndpoint_,
boost::bind(&udp_find::handle_send, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_receive(const boost::system::error_code& error,
std::size_t bytes_transferred)
{
std::cout << "Received Data" << bytes_transferred << std::endl;
}
void handle_send(const boost::system::error_code& error, std::size_t bytes_transferred)
{
std::cout << "Sent Data" << bytes_transferred << std::endl;
socket_.async_receive_from(
boost::asio::buffer(buffer_), broadcastEndpoint_,
boost::bind(&udp_find::handle_receive, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
private:
boost::asio::ip::udp::socket socket_;
boost::array<char, 128> buffer_;
boost::asio::ip::udp::endpoint broadcastEndpoint_;
};
int main()
{
boost::asio::io_context service;
udp_find(service, 9000);
service.run();
}
Your first problem is Udefined Behaviour.
You start asynchronous operations on a temporary object of type udp_find. The object is destructed immediately after construction, so it doesn't exist anymore even before you start any of the async work (service.run()).
That is easily fixed by making udp_find a local variable instead of a temporary:
udp_find op(service, 9000);
Now sending works for me. You will want to test that receiving works as well. In my netstat output it appears that the UDP socket is bound to an ephemeral port. Sending a datagram to that port makes the test succeed for me.
You might want to actually bind/connect to the broadcast address before receiving (the endpoint& parameter to async_receive_from is not for that, I think it is an output parameter).
I have to write a program that initializes a array of TCP sockets, and use async i/o to read data using a thread pool. Im new to async io, thread pools, shared_ptrs. What I now have is a working program with one socket. Heres the clipping:
boost::shared_ptr< asio::ip::tcp::socket > sock1(
new asio::ip::tcp::socket( *io_service )
);
boost::shared_ptr< asio::ip::tcp::acceptor > acceptor( new asio::ip::tcp::acceptor( *io_service ) );
asio::ip::tcp::endpoint endpoint(asio::ip::tcp::v4(), portNum);
acceptor->open( endpoint.protocol() );
acceptor->set_option( asio::ip::tcp::acceptor::reuse_address( false ) );
acceptor->bind( endpoint );
acceptor->listen();
I am stuck in getting similar code for an "array of sockets", that is, I want to have acceptor[], that are binded to endpoint[]. I must pass around pointers to the acceptors an sockets, so shared_ptr comes in, and am unable to get it right.
for (i=0; i<10; i++) {
// init socket[i] with *io_service
// init endpoint[i]
// init acceptor[i] with *io_service
acceptor[i]->listen()
}
(btw, do I really need an socket[] array for this porpose?) Can someone please help me?
Here is a full example for using Boost ASIO to implement a TCP echo server listening to multiple ports, with a thread pool to distribute work across multiple cores. It is based on this example from the Boost documentation (providing a single-threaded TCP echo server).
Session class
The session class represents a single active socket connection with a client. It reads from the socket and then writes the same data into the socket to echo it back to the client. The implementation uses the async_... functions provided by Boost ASIO: These functions register a callback at the I/O service that will be triggered when the I/O operation has finished.
session.h
#pragma once
#include <array>
#include <memory>
#include <boost/asio.hpp>
/**
* A TCP session opened on the server.
*/
class session : public std::enable_shared_from_this<session> {
using endpoint_t = boost::asio::ip::tcp::endpoint;
using socket_t = boost::asio::ip::tcp::socket;
public:
session(boost::asio::io_service &service);
/**
* Start reading from the socket.
*/
void start();
/**
* Callback for socket reads.
*/
void handle_read(const boost::system::error_code &ec,
size_t bytes_transferred);
/**
* Callback for socket writes.
*/
void handle_write(const boost::system::error_code &ec);
/**
* Get a reference to the session socket.
*/
socket_t &socket() { return socket_; }
private:
/**
* Session socket
*/
socket_t socket_;
/**
* Buffer to be used for r/w operations.
*/
std::array<uint8_t, 4096> buffer_;
};
session.cpp
#include "session.h"
#include <functional>
#include <iostream>
#include <thread>
using boost::asio::async_write;
using boost::asio::buffer;
using boost::asio::io_service;
using boost::asio::error::connection_reset;
using boost::asio::error::eof;
using boost::system::error_code;
using boost::system::system_error;
using std::placeholders::_1;
using std::placeholders::_2;
session::session(io_service &service) : socket_{service} {}
void session::start() {
auto handler = std::bind(&session::handle_read, shared_from_this(), _1, _2);
socket_.async_read_some(buffer(buffer_), handler);
}
void session::handle_read(const error_code &ec, size_t bytes_transferred) {
if (ec) {
if (ec == eof || ec == connection_reset) {
return;
}
throw system_error{ec};
}
std::cout << "Thread " << std::this_thread::get_id() << ": Received "
<< bytes_transferred << " bytes on " << socket_.local_endpoint()
<< " from " << socket_.remote_endpoint() << std::endl;
auto handler = std::bind(&session::handle_write, shared_from_this(), _1);
async_write(socket_, buffer(buffer_.data(), bytes_transferred), handler);
}
void session::handle_write(const error_code &ec) {
if (ec) {
throw system_error{ec};
}
auto handler = std::bind(&session::handle_read, shared_from_this(), _1, _2);
socket_.async_read_some(buffer(buffer_), handler);
}
Server class
The server class creates an acceptor for each given port. The acceptor will listen to the port and dispatch a socket for each incoming connection request. The waiting for an incoming connection is again implemented with a async_... function.
server.h
#pragma once
#include <vector>
#include <boost/asio.hpp>
#include "session.h"
/**
* Listens to a socket and dispatches sessions for each incoming request.
*/
class server {
using acceptor_t = boost::asio::ip::tcp::acceptor;
using endpoint_t = boost::asio::ip::tcp::endpoint;
using socket_t = boost::asio::ip::tcp::socket;
public:
server(boost::asio::io_service &service, const std::vector<uint16_t> &ports);
/**
* Start listening for incoming requests.
*/
void start_accept(size_t index);
/**
* Callback for when a request comes in.
*/
void handle_accept(size_t index, std::shared_ptr<session> new_session,
const boost::system::error_code &ec);
private:
/**
* Reference to the I/O service that will call our callbacks.
*/
boost::asio::io_service &service_;
/**
* List of acceptors each listening to (a different) socket.
*/
std::vector<acceptor_t> acceptors_;
};
server.cpp
#include "server.h"
#include <functional>
#include <boost/asio.hpp>
using std::placeholders::_1;
using std::placeholders::_2;
using boost::asio::io_service;
using boost::asio::error::eof;
using boost::system::error_code;
using boost::system::system_error;
server::server(boost::asio::io_service &service,
const std::vector<uint16_t> &ports)
: service_{service} {
auto create_acceptor = [&](uint16_t port) {
acceptor_t acceptor{service};
endpoint_t endpoint{boost::asio::ip::tcp::v4(), port};
acceptor.open(endpoint.protocol());
acceptor.set_option(acceptor_t::reuse_address(false));
acceptor.bind(endpoint);
acceptor.listen();
return acceptor;
};
std::transform(ports.begin(), ports.end(), std::back_inserter(acceptors_),
create_acceptor);
for (size_t i = 0; i < acceptors_.size(); i++) {
start_accept(i);
}
}
void server::start_accept(size_t index) {
auto new_session{std::make_shared<session>(service_)};
auto handler =
std::bind(&server::handle_accept, this, index, new_session, _1);
acceptors_[index].async_accept(new_session->socket(), handler);
}
void server::handle_accept(size_t index, std::shared_ptr<session> new_session,
const boost::system::error_code &ec) {
if (ec) {
throw system_error{ec};
}
new_session->start();
start_accept(index);
}
Main
The main function creates the server for a series of ports.
For this example, the ports are set to 5000,...,5010. It then spawns a series of threads for each CPU core that calls the run function of the I/O service provided by Boost ASIO. The I/O service is capable of handling such a multi-threading scenario, dispatching work among the threads that have called its run function (reference):
Multiple threads may call the run() function to set up a pool of threads from which the io_context may execute handlers. All threads that are waiting in the pool are equivalent and the io_context may choose any one of them to invoke a handler.
server_main.cpp
#include "server.h"
#include <numeric>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
int main() {
std::vector<uint16_t> ports{};
// Fill ports with range [5000,5000+n)
ports.resize(10);
std::iota(ports.begin(), ports.end(), 5000);
boost::asio::io_service service{};
server s{service, ports};
// Spawn thread group for running the I/O service
size_t thread_count = std::min(
static_cast<size_t>(boost::thread::hardware_concurrency()), ports.size());
boost::thread_group tg{};
for (size_t i = 0; i < thread_count; ++i) {
tg.create_thread([&]() { service.run(); });
}
tg.join_all();
return 0;
}
You could compile the server for example with g++ -O2 -lboost_thread -lpthread {session,server,server_main}.cpp -o server. If you run the server with clients that send it random data, you would get output such as:
Thread 140043413878528: Received 4096 bytes on 127.0.0.1:5007 from 127.0.0.1:40856
Thread 140043405485824: Received 4096 bytes on 127.0.0.1:5000 from 127.0.0.1:42556
Thread 140043388700416: Received 4096 bytes on 127.0.0.1:5005 from 127.0.0.1:58582
Thread 140043388700416: Received 4096 bytes on 127.0.0.1:5001 from 127.0.0.1:40192
Thread 140043388700416: Received 4096 bytes on 127.0.0.1:5003 from 127.0.0.1:42508
Thread 140043397093120: Received 4096 bytes on 127.0.0.1:5008 from 127.0.0.1:37808
Thread 140043388700416: Received 4096 bytes on 127.0.0.1:5006 from 127.0.0.1:35440
Thread 140043397093120: Received 4096 bytes on 127.0.0.1:5009 from 127.0.0.1:58306
Thread 140043405485824: Received 4096 bytes on 127.0.0.1:5002 from 127.0.0.1:56300
You can see the server handling multiple ports, with work being distributed among the worker threads (not necessarily restricting each thread to a specific port).
Im following the tutorials at the boost official web site http://www.boost.org/doc/libs/1_55_0/doc/html/boost_asio/tutorial/tutdaytime1.html.
The program is working perfectly if i connect to "localhost" or "127.0.0.1" on the same machine. But if i run the client on another computer with the same network it fails to connect to the server. Why is this happening? and what would i have to do to get the client to run on another network?
Error: connect: No connection could be made because the target machine actively refused it.
Client:
#include <iostream>
#include <boost/array.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
int main()
{
try
{
boost::asio::io_service io_service;
tcp::resolver resolver(io_service);
char* serverName = "localhost";
tcp::resolver::query query(serverName, "daytime");
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
tcp::socket socket(io_service);
while(true)
{
boost::asio::connect(socket, endpoint_iterator);
for (;;)
{
boost::array<char, 128> buf;
boost::system::error_code error;
size_t len = socket.read_some(boost::asio::buffer(buf), error);
if (error == boost::asio::error::eof)
break; // Connection closed cleanly by peer.
else if (error)
throw boost::system::system_error(error); // Some other error.
std::cout.write(buf.data(), len);
std::cout <<"\n";
}
}
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
Server:
#include <iostream>
#include <string>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
int main()
{
try
{
boost::asio::io_service io_service;
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 13));
for (;;)
{
tcp::socket socket(io_service);
acceptor.accept(socket);
std::string message = "This is the Server!";
boost::system::error_code ignored_error;
boost::asio::write(socket, boost::asio::buffer(message), ignored_error);
}
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
I would guess your problem might be that you return on the first error. Resolving gives you an iterator on a number of endpoints. You try the first of those and if it does not work out you give up instead of letting the iterator go on.
Again, i am by no means an expert in boost::asio and far less in its TCP world but resolve may return more than one endpoint (for example IPv4 and IPv6) and possibly only one of them does not work out here.
For testing you could create the endpoint yourself by first creating a ip::address object, using its from_string() method to give it the address of the server (works only on your local network of course) and then using it for your endpoint:
boost::asio::ip::address address;
address.from_string("the.servers.ip.here");
boost::asio::ip::tcp::endpoint endpoint(address, 13);
boost::asio::connect(socket, endpoint);
And see if that works. If not, it probably is a problem on the server side.
To run the server and client on separate networks, Make the client connect to the servers external ip address. This is obvious but external ip addresses constantly change so to solve this problem you can go to www.noip.com and create a name that links to your ip address. This way in the client all you have to do is specify a name instead of an ip address.
most likely firewall issue, if you are using windows for server check windows firewall, if you are using linux, check the iptables.
I want to send unsolicited messages over an SSL connection. Meaning that the server sends a message not based on a request from a client, but because some event happened that the client needs to know about.
I just use the SSL server example from the boost site, added a timer that sends 'hello' after 10 seconds, everything works fine before the timer expires (the server echo's everything), the 'hello' is also received, but after that the application crashes on the next time a text is sent to the server.
For me even more strange is the fact that when I disable the SSL code, so use a normal socket and do the same using telnet, it works FINE and keeps on working fine!!!
I ran into this problem for the second time now, and I really do not have an idea why this is happening the way it happens.
Below is the total source that I altered to demonstrate the problem. Compile it without the SSL define and use telnet and everything works OK, define SSL and use openssl, or the client SSL example from the boost website and the thing crashes.
#include <cstdlib>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
//#define SSL
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;
class session
{
public:
session(boost::asio::io_service& io_service,
boost::asio::ssl::context& context)
#ifdef SSL
: socket_(io_service, context)
#else
: socket_(io_service)
#endif
{
}
ssl_socket::lowest_layer_type& socket()
{
return socket_.lowest_layer();
}
void start()
{
#ifdef SSL
socket_.async_handshake(boost::asio::ssl::stream_base::server,
boost::bind(&session::handle_handshake, this,
boost::asio::placeholders::error));
#else
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
boost::shared_ptr< boost::asio::deadline_timer > timer(new boost::asio::deadline_timer( socket_.get_io_service() ));
timer->expires_from_now( boost::posix_time::seconds( 10 ) );
timer->async_wait( boost::bind( &session::SayHello, this, _1, timer ) );
#endif
}
void handle_handshake(const boost::system::error_code& error)
{
if (!error)
{
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
boost::shared_ptr< boost::asio::deadline_timer > timer(new boost::asio::deadline_timer( socket_.get_io_service() ));
timer->expires_from_now( boost::posix_time::seconds( 10 ) );
timer->async_wait( boost::bind( &session::SayHello, this, _1, timer ) );
}
else
{
delete this;
}
}
void SayHello(const boost::system::error_code& error, boost::shared_ptr< boost::asio::deadline_timer > timer) {
std::string hello = "hello";
boost::asio::async_write(socket_,
boost::asio::buffer(hello, hello.length()),
boost::bind(&session::handle_write, this,
boost::asio::placeholders::error));
timer->expires_from_now( boost::posix_time::seconds( 10 ) );
timer->async_wait( boost::bind( &session::SayHello, this, _1, timer ) );
}
void handle_read(const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error)
{
boost::asio::async_write(socket_,
boost::asio::buffer(data_, bytes_transferred),
boost::bind(&session::handle_write, this,
boost::asio::placeholders::error));
}
else
{
std::cout << "session::handle_read() -> Delete, ErrorCode: "<< error.value() << std::endl;
delete this;
}
}
void handle_write(const boost::system::error_code& error)
{
if (!error)
{
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
std::cout << "session::handle_write() -> Delete, ErrorCode: "<< error.value() << std::endl;
delete this;
}
}
private:
#ifdef SSL
ssl_socket socket_;
#else
boost::asio::ip::tcp::socket socket_;
#endif
enum { max_length = 1024 };
char data_[max_length];
};
class server
{
public:
server(boost::asio::io_service& io_service, unsigned short port)
: io_service_(io_service),
acceptor_(io_service,
boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)),
context_(boost::asio::ssl::context::sslv23)
{
#ifdef SSL
context_.set_options(
boost::asio::ssl::context::default_workarounds
| boost::asio::ssl::context::no_sslv2
| boost::asio::ssl::context::single_dh_use);
context_.set_password_callback(boost::bind(&server::get_password, this));
context_.use_certificate_chain_file("server.crt");
context_.use_private_key_file("server.key", boost::asio::ssl::context::pem);
context_.use_tmp_dh_file("dh512.pem");
#endif
start_accept();
}
std::string get_password() const
{
return "test";
}
void start_accept()
{
session* new_session = new session(io_service_, context_);
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
void handle_accept(session* new_session,
const boost::system::error_code& error)
{
if (!error)
{
new_session->start();
}
else
{
delete new_session;
}
start_accept();
}
private:
boost::asio::io_service& io_service_;
boost::asio::ip::tcp::acceptor acceptor_;
boost::asio::ssl::context context_;
};
int main(int argc, char* argv[])
{
try
{
boost::asio::io_service io_service;
using namespace std; // For atoi.
server s(io_service, 7777 /*atoi(argv[1])*/);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
I use boost 1.49 and OpenSSL 1.0.0i-fips 19 Apr 2012. I tried investigating this problem as much as possible, the last time I had this problem (a couple of months ago), I received an error number that I could trace to this error message: error: decryption failed or bad record mac.
But I have no idea what is going wrong and how to fix this, any suggestions are welcome.
The problem is multiple concurrent async read and writes. I were able to crash this program even with raw sockets (glibc detected double free or corruption). Let's see what happens after session starts (in braces I put number of concurrent scheduled async reads and writes):
schedule async read (1, 0)
(assume that data comes) handle_read is executed, it schedules async write (0, 1)
(data are written) handle_write is executed, it schedules async read (1, 0)
Now, it could loop over 1. - 3. without any problem indefinitely. But then timer expires...
(assume, that no new data come from client, so there is still one async read scheduled) timer expires, so SayHello is executed, it schedules async write, still no problem (1, 1)
(data from SayHello are written, but still no new data comes from client) handle_write is executed, it schedules async read (2, 0)
Now, we are done. If any new data from client will come, part of them could be read by one async read and part by another. For raw sockets, it might even seem to work (despite possibility, that there might be 2 concurrent writes scheduled, so echo on client side might look mixed). For SSL this might corrupt incoming data stream, and this is probably what happens.
How to fix it:
strand will not help in this case (it is not concurrent handler executions, but scheduled async reads and writes).
It is not enough, if async write handler in SayHello does nothing (there will be no concurrent reads then, but still concurrent writes might occur).
If you really want to have two diffident kind of writes (echo and timer), you have to implement some kind of queue of messages to write, to avoid mixing writes from echo and timer.
General remark: it was simple example, but using shared_ptr instead of delete this is much better way of handling memory allocation with boost::asio. It will prevent from missing errors resulting in memory leak.
This code is identical to the original udp async echo server, but with a different socket.
The response is transmitted and showing in wireshark, but then an ICMP Port Unreachable error is sent back to the server. I'm trying to understand why because everything looks correct.
You can copy this code directly into a source file e.g. server.cpp. and then compile with
gcc server.cpp -lboost_system
Run it with a command like: ./a.out 35200
#include <cstdlib>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::udp;
class server
{
public:
server(boost::asio::io_service& io_service, short port)
: io_service_(io_service),
socket_(io_service, udp::endpoint(udp::v4(), port)),
socket2_(io_service, udp::endpoint(udp::v4(),0))
{
socket_.async_receive_from(
boost::asio::buffer(data_, max_length), sender_endpoint_,
boost::bind(&server::handle_receive_from, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_receive_from(const boost::system::error_code& error,
size_t bytes_recvd)
{
if (!error && bytes_recvd > 0)
{
// use a different socket... random source port.
socket2_.async_send_to(
boost::asio::buffer(data_, bytes_recvd), sender_endpoint_,
boost::bind(&server::handle_send_to, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
socket_.async_receive_from(
boost::asio::buffer(data_, max_length), sender_endpoint_,
boost::bind(&server::handle_receive_from, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
}
void handle_send_to(const boost::system::error_code& /*error*/,
size_t /*bytes_sent*/)
{
// error_code shows success when checked here. But wireshark shows
// an ICMP response with destination unreachable, port unreachable when run on
// localhost. Haven't tried it across a network.
socket_.async_receive_from(
boost::asio::buffer(data_, max_length), sender_endpoint_,
boost::bind(&server::handle_receive_from, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
private:
boost::asio::io_service& io_service_;
udp::socket socket_;
udp::socket socket2_;
udp::endpoint sender_endpoint_;
enum { max_length = 1024 };
char data_[max_length];
};
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: async_udp_echo_server <port>\n";
return 1;
}
boost::asio::io_service io_service;
using namespace std; // For atoi.
server s(io_service, atoi(argv[1]));
io_service.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
The reason I need this is because I have multiple threads receiving data from an input queue that is fed with a UDP server. Now I want those threads to be able to send responses directly but I can't get it working.
If I use the original socket (i.e. socket_) in the async_send_to call then it works.
Ok... here is the test client that doesn't work with the code above (but works with the original version from the asio examples).
#!/usr/bin/python
import socket, sys, time, struct
textport = "35200"
host = "localhost"
if len(sys.argv) > 1:
host = sys.argv[1]
print "Sending Data"
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
port = int(textport)
s.connect((host, port))
s.sendall("Hello World")
#s.shutdown(1)
print "Looking for replies; press Ctrl-C or Ctrl-Break to stop."
while 1:
buf = s.recv(1200)
if not len(buf):
break
print "Received: %s" % buf
It's got me baffled. But at least I can use the C++ UDP client and it works.
You shouldn't pend an asynchronous send and then close the socket. The destructor for socket runs at the end of the block, closing the socket, which prevents the send from ever occurring.
Ok, a completely different possibility.
Are you running netfilter? Do you have a conntrack rule?
A reply from the same port would match CONNECTED in the conntrack module, while a reply from a new port would appear to be a new connection. If incoming UDP packets which don't match CONNECTED have a REJECT action, it would explain the behavior, as well as why the exact same code could work for Sam.
Here we go. I'm answering my own question again. The problem relates to my python code
which was a sample I grabbed from someone else.
This version works a whole heap better and reads the result correctly. And, is using the correct API sendto recvfrom which is what you would normally use with udp packets.
#!/usr/bin/python
import socket, sys, time, struct
textport = "35200"
host = "localhost"
if len(sys.argv) > 1:
host = sys.argv[1]
print "Sending Data"
port = int(textport)
addr = (host, port)
buf = 1024
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto("hello World", addr)
print "Looking for replies; press Ctrl-C or Ctrl-Break to stop."
while 1:
data,addr = s.recvfrom(buf)
if not data:
print "Client has exited!"
break
else:
print "\nReceived: '", data,"'"
# Close socket
s.close()
The other thing is, that the as Ben has pointed out in his answer that at one point I was creating a socket that was later being deleted as the function went out of scope and it still had pending I/O. I have decided that there is little benefit in my case to use asynchronous I/O as it unnecessarily complicates the code and won't affect performance much.
Edit
Your python client code looks suspicious, I don't think you should be doing a connect or a send using a UDP socket. Try this:
#!/usr/bin/python
import socket, sys, time, struct
port = 10000
host = "localhost"
addr = (host,port)
if len(sys.argv) > 1:
host = sys.argv[1]
print "Sending Data"
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto("Hello World",addr)
print "Looking for replies; press Ctrl-C or Ctrl-Break to stop."
while 1:
data,addr = s.recvfrom(1200)
if not data:
break
print "Received: %s" % data
it works for me using your server.cpp
macmini:stackoverflow samm$ ./client.py
Sending Data
Looking for replies; press Ctrl-C or Ctrl-Break to stop.
Received: Hello World
original answer below.
host unreachable is what I would expect if the client that sent the message does not have the sender_endpoint_ port open. When I compiled your server.cc and use the Boost.Asio blocking udp echo client example, it works just fine
macmini:stackoverflow samm$ g++ server.cpp -lboost_system -o server
macmini:stackoverflow samm$ g++ client.cpp -lboost_system -o client
macmini:stackoverflow samm$ ./server 10000
in another shell
macmini:stackoverflow samm$ ./client 127.0.0.1 10000
Enter message: hello
Reply is: hello
macmini:stackoverflow samm$ ./client 127.0.0.1 10000
Enter message: goodbye
Reply is: goodbye
macmini:stackoverflow samm$