Triggering writes with Boost::asio - c++

I have some software that I would like to make a TCP client. I don't know if this is the best architecture, but in my software I spawn a thread that will be used for the Network I/O. If there is a better architecture, I'd appreciate some pointers and advice.
Both threads have a refernce to the boost::asio::io_service object and a Session object that encapsulates the socket object. The sesson object is roughly as follows:
class Session
{
public:
Session(
boost::asio::io_service & io_service,
std::string const & ip_address,
std::string const & port)
: io_service_(io_service),
resolver_(io_service),
socket_(io_service),
ip_address_(ip_address),
port_(port),
{}
virtual void start();
virtual ~Session();
virtual void stop();
void write(std::string const & msg);
void handle_resolve(
const boost::system::error_code & error,
boost::asio::ip::tcp::resolver::iterator endpoint_itr);
void handle_connect(
const boost::system::error_code & error,
boost::asio::ip::tcp::resolver::iterator endpoint_itr);
void handle_close();
void handle_write(const boost::system::error_code & error);
private:
boost::asio::io_service & io_service_;
boost::asio::ip::tcp::resolver resolver_;
boost::asio::ip::tcp::socket socket_;
std::string ip_address_;
std::string port_;
};
In the I/O thread run-loop, the start() method of the session object is called which connects to the server. (This works, btw). Then, the thread sits in a loop calling the run() method on the I/O service object [io_service_.run()] to trigger events.
The main thread calls the write() method of the session when it wants to send data, and the session object calls boost::async_write with the data to write and then a callback method that is a member of the session object (handle_write).
While I have the I/O thread connecting to the server, I cannot get the handle_write method to be triggered. I have verified that the main thread is calling into the session object and executing async_write() on the socket. It is just that the callback is never triggered. I also don't see any data on the server side or over the wire with tcpdump.
Any idea where my problem might be? Is there a better way to organize the architecture? Most of all, I don't want to block the main thread doing I/O.
Here is the code that spawns the io thread from the main thread (apologies for the spacing):
boost::asio::io_service io_service;
boost::shared_ptr<Session> session_ptr;
boost::thread io_thread;
....
session_ptr.reset(
new Session::Session(
io_service,
std::string("127.0.0.1"),
std::string("17001")));
// spawn new thread for the network I/O endpoint
io_thread = boost::thread(
boost::bind(
&Session::start,
session_ptr_.get()));
The code for the start() method is as follows:
void Session::start()
{
typedef boost::asio::ip::tcp tcp;
tcp::resolver::query query(
tcp::v4(),
ip_address_,
port_);
resolver_.async_resolve(
query,
boost::bind(
&Session::handle_resolve,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::iterator));
while(1){ // improve this later
io_service_.run();
}
}
The callback for the resolver:
void Session::handle_resolve(
const boost::system::error_code & error,
boost::asio::ip::tcp::resolver::iterator endpoint_itr)
{
if (!error)
{
boost::asio::ip::tcp::endpoint endpoint = *endpoint_itr;
socket_.async_connect(
endpoint,
boost::bind(
&Session::handle_connect,
this,
boost::asio::placeholders::error,
++endpoint_itr));
}
else
{
std::cerr << "Failed to resolve\n";
std::cerr << "Error: " << error.message() << std::endl;
}
}
The callback for connect:
void Session::handle_connect(
const boost::system::error_code & error,
boost::asio::ip::tcp::resolver::iterator endpoint_itr)
{
typedef boost::asio::ip::tcp tcp;
if (!error)
{
std::cerr << "Connected to the server!\n";
}
else if (endpoint_itr != tcp::resolver::iterator())
{
socket_.close();
socket_.async_connect(
*endpoint_itr,
boost::bind(
&Session::handle_connect,
this,
boost::asio::placeholders::error,
++endpoint_itr));
}
else
{
std::cerr << "Failed to connect\n";
}
}
The write() method that the main thread can call to send post an asychronous write.
void Session::write(
std::string const & msg)
{
std::cout << "Write: " << msg << std::endl;
boost::asio::async_write(
socket_,
boost::asio::buffer(
msg.c_str(),
msg.length()),
boost::bind(
&Session::handle_write,
this,
boost::asio::placeholders::error));
}
And finally, the write completion callback:
void Session::handle_write(
const boost::system::error_code & error)
{
if (error)
{
std::cout << "Write complete with errors !!!\n";
}
else
{
std::cout << "Write complete with no errors\n";
}
}

Looks like your io service will run out of work after connect, after which you just call io_service::run again? It looks like run is being called in the while loop, however I can't see a call to reset anywhere. You need to call io::service::reset before you call run on the same io_service again.
Structurally, it would be better to add work to the io_service, then you don't need to call it in the loop and the run will exit once you call io_service::stop.

this portion of your code
boost::asio::io_service io_service;
boost::shared_ptr<Session> session_ptr;
boost::thread io_thread;
....
session_ptr.reset(
new Session::Session(
io_service,
std::string("127.0.0.1"),
std::string("17001")));
// spawn new thread for the network I/O endpoint
io_thread = boost::thread(
boost::bind(
&Session::start,
session_ptr_.get()));
is a red flag to me. Your io_service object is possibly going out of scope and causing strange behavior. An io_service is not copyable, so passing it to your Session as a non-const reference is probably not what you are hoping to achieve.
samm#macmini ~> grep -C 2 noncopyable /usr/include/boost/asio/io_service.hpp
#include <boost/asio/detail/epoll_reactor_fwd.hpp>
#include <boost/asio/detail/kqueue_reactor_fwd.hpp>
#include <boost/asio/detail/noncopyable.hpp>
#include <boost/asio/detail/select_reactor_fwd.hpp>
#include <boost/asio/detail/service_registry_fwd.hpp>
--
*/
class io_service
: private noncopyable
{
private:
--
/// Class used to uniquely identify a service.
class io_service::id
: private noncopyable
{
public:
--
/// Base class for all io_service services.
class io_service::service
: private noncopyable
{
public:
If you're basing your code off the HTTP client example, you should note the io_service is in scope all the time inside of main(). As Ralf pointed out, your io_service is also likely running out of work to do after the connect handler, which is why you've kludged it to invoke run() inside of a loop
while(1){ // improve this later
io_service_.run();
}
again, note that the HTTP client example does not do this. You need to start another async operation inside of the connect handler, either a read or write depending on what your application needs.

Related

Boost Beast Websocket: Application Data After Close Notify

I am using boost::beast as well as boost::asio to implement a websocket client with SSL support. My WebsocketClient class has the following members:
boost::asio::io_context& io_context;
boost::asio::ssl::context& ssl_context;
std::optional<tcp::resolver> resolver;
std::optional<websocket::stream<beast::ssl_stream<beast::tcp_stream>>> ws;
std::promise<void> promise_;
The io_context and ssl_context are constructed and passed by reference from main. The resolver and ws members are initialized via:
resolver.emplace(boost::asio::make_strand(io_context));
ws.emplace(boost::asio::make_strand(io_context), ssl_context);
After calling a WebsocketClient method "run" which triggers a sequence which calls connect, handshake, etc. we enter an async loop. Also of note, we set the value of promise before entering the "on_read" loop. This promise is returned to the caller of run in main which allows the application to progress once the initial WebsocketClient::run call is made.
void WebsocketClient::on_read(
beast::error_code ec,
std::size_t bytes_transferred) {
boost::ignore_unused(bytes_transferred);
if (ec) {
fail(ec, "read");
reconnect(ec);
} else {
// business logic
ws_->async_read(
buffer_,
beast::bind_front_handler(
&WebsocketClient::on_read,
this)
);
}
}
The WebsocketClient works well for several minutes until I reach an exception:
read: application data after close notify
Upon reaching this error, the WebsocketClient proceeds to:
void WebsocketClient::reconnect(beast::error_code ec) {
ws.emplace(boost::asio::make_strand(io_context), ssl_context);
promise_ = std::promise<void>();
resolver_.emplace(boost::asio::make_strand(io_context));
on_disconnect_cb(ec);
}
The function "on_disconnect_cb" is passed in during WebsocketClient initialization as a lambda function. This function simply calls the WebsocketClient::run function again in attempt to reconnect.
To summarize my questions:
Why am I receiving this "read: application data after close notify" error?
Why does the application fail to reconnect after calling WebsocketClient::reconnect?
In the process of debugging I have concluded that the application makes no additional progress after calling:
resolver->async_resolve(
host,
port,
beast::bind_front_handler(
&WebsocketClient::on_resolve,
this)
);
in the WebsocketClient::run function. This fails only in the case of a reconnect and run works as expected on first call. Therefore I expect the issue to be related to incorrectly resetting some of the components of the WebsocketClient.
Edit:
As requested in the comments, I'd like to provide a sketch of the "main" routine. This is indeed a sketch but should cover how the websocket is being used during the program lifecycle:
class App {
public:
App(boost::asio::io_context& io_context,
boost::asio::ssl::context& ssl_context) : {
ws.emplace(boost::asio::io_context& io_context,
boost::asio::ssl::context& ssl_context,
[this]() {
// on_read callback for ws
logic();
},
[this]() {
// on_disconnect callback for ws
on_disconnect();
}
);
}
void run() {
// call underlying websocket connect
}
void logic() {
// do stuff with inbound message
}
void on_disconnect() {
// destroy existing websocket
// If *this contains a value, destroy that value as if by value().T::~T()
ws.reset();
// Initialize new WebsocketClient
ws.emplace(io_context,
ssl_context,
[this](){
logic();
},
[this](){
on_disconnect();
};
);
// call App::run again to restart
run();
}
private:
boost::asio::io_context& io_context;
boost::asio::ssl::context& ssl_context;
std::optional<WebsocketClient> ws;
};
int main() {
// spinup io_context and detach thread that polls
App app(io_context, ssl_contex);
// triggers WebsocketClient::connect
app.run();
}

Udp server from boost not working on multithreading, but work only on the main thread

I got a async udp server with boost::asio
but the problem is:
if I launch it on a thread, the server won't work
but if I launch it on the main thread (blocking with the service) it's working...
I've try to do it with a fork but not working eiser
class Server {
private:
boost::asio::io_service _IO_service;
boost::shared_ptr<boost::asio::ip::udp::socket> _My_socket;
boost::asio::ip::udp::endpoint _His_endpoint;
boost::array<char, 1000> _My_Buffer;
private:
void Handle_send(const boost::system::error_code& error, size_t size, std::string msg) {
//do stuff
};
void start_send(std::string msg) {
_My_socket->async_send_to(boost::asio::buffer(msg), _His_endpoint,
boost::bind(&Server::Handle_send, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred, msg));
};
void Handle_receive(const boost::system::error_code& error, size_t size) {
//do stuff
};
void start_receive(void) {
_My_socket->async_receive_from(
boost::asio::buffer(_My_Buffer), _His_endpoint,
boost::bind(&Server::Handle_receive, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
public:
Server(int port):
_IO_service(),
_My_socket(boost::make_shared<boost::asio::ip::udp::socket>(_IO_service, \
boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), port)))
{
start_receive();
};
void Launch() {
_IO_service.run();
};
};
the objective is to call the Server::Launch in the background.
First of all you have undefined behaviour in start_send.
async_send_to returns immediately, so msg as local variable is destroyed when start_send returns. When you call async_send_to you must ensure that msg is not destroyed before asynchronous operation is completed. What is described in documentation -
Although the buffers object may be copied as necessary, ownership of
the underlying memory blocks is retained by the caller, which must
guarantee that they remain valid until the handler is called.
You can resolve it by many ways, the easiest is to use string as data members (as buffer for sending data):
class Server {
//..
std::string _M_toSendBuffer;
//
void start_send(std::string msg) {
_M_toSend = msg; // store msg into buffer for sending
_My_socket->async_send_to(boost::asio::buffer(_M_toSend), _His_endpoint,
boost::bind(&Server::Handle_send, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred,
_M_toSend));
};
Another solution is to wrap msg into smart pointer to extend its lifetime:
void Handle_send(const boost::system::error_code& error, size_t size,
boost::shared_ptr<std::string> msg) {
//do stuff
};
void start_send(std::string msg) {
boost::shared_ptr<std::string> msg2 = boost::make_shared<std::string>(msg); // [1]
_My_socket->async_send_to(boost::asio::buffer(*msg2), _His_endpoint,
boost::bind(&Server::Handle_send, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred,
msg2)); // [2]
};
in [1] line we create shared_ptr which taks msg content, then in [2] line reference counter of shared_ptr is increased when bind is called, so string lifetime is extended and it is destroyed after handler is called.
Regarding your not-working verion based on thread. You didn't show the code where Launch is called, but maybe you just don't join this thread?
Server s(3456);
boost::thread th(&Server::Launch,&s);
th.join(); // are you calling this line?
or perhaps your code doesn't work by UB in start_send.

Operation canceled boost asio async_receive_from

I have an UDP Server set up with boost/asio (I copied the example and just changed a few things). Below is the code:
udp_server.hpp
using boost::asio::ip::udp;
class udp_server {
public:
udp_server(boost::asio::io_service&, int);
private:
boost::array<char, 256> recBuffer;
udp::socket socket_;
udp::endpoint remote_endpoint_;
void start_receive();
void handle_receive(const boost::system::error_code&, std::size_t);
void handle_send(boost::shared_ptr<std::string> /*message*/,
const boost::system::error_code& /*error*/,
std::size_t /*bytes_transferred*/)
{}
};
and udp_server.cpp
udp_server::udp_server( boost::asio::io_service& io_service,
int port)
: socket_(io_service, udp::endpoint(udp::v4(), port)) {
serverNotifications.push_back("UDP Server class initialized.");
start_receive();
}
void udp_server::start_receive() {
socket_.async_receive_from(
boost::asio::buffer(recBuffer),
remote_endpoint_,
boost::bind(&udp_server::handle_receive,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
serverNotifications.push_back("Starting to receive UDP Messages.");
}
void udp_server::handle_receive(const boost::system::error_code& error,
std::size_t size) {
serverNotifications.push_back("RecFrom: " + remote_endpoint_.address().to_string());
if (!error) {
// I do data stuff here
} else {
errors.push_back("Handle Receive error: " + error.message());
}
}
After initializing the Server with:
try {
udp_server server(io_service, ApplData.PORT, (size_t)ApplData.BUFLEN);
} catch (std::exception& e) {
// error handling
}
and running it with io_service.run() in a seperate try catch in another function I get some problems:
My Callback function handle_receive gets called without any UDP message getting send in the whole network (aka only my laptop without connection)
error.message() returns "Operation canceled"
remote_endpoint_.address().to_string() returns "acfc:4000:0:0:7800::%2885986016" which I can't identify as something useful
Also I recognized that my io_service is stopping all the time, but in my understanding it should run all the time, right?
I already thought about referencing this in the callback function bind with a shared_from_this ptr, but since I have a real instance of the udp_server class until I leave my program I can't think of a good reason to do that.
Can someone explain thy this failure occurs, what these errors tell me about my code or what I can do to avoid them?
Nevermind, Rubberduck debugging was enough. I just read the line
but since I have a real instance of the udp_server class until I leave my program I can't think of a good reason to do that.
and noticed, that I actually didn't have this and this was the error.

Boost Asio delayed write tcp socket

I have a simple TCP server with one thread with the asio loop, and a thread pool to do the computation. I'm able to listen to connections write something to it in the main thread. But I can't wait the answer of the worker thread because the connection is immediately closed after getting accepted.
I tried using a deadline timer but for some reason it gets called immediately with "Aborted operation" error.
The whole process I want to achieve is:
Accept connection
write something
send a task to the worker pool
wait for the answer from the worker (I'm using a thread safe queue to read message from the worker pool)
write the answer to the socket
close connection
Here is my code
class tcp_connection
: public boost::enable_shared_from_this<tcp_connection>
{
public:
typedef boost::shared_ptr<tcp_connection> pointer;
static pointer create(boost::asio::io_service& io_service)
{
return pointer(new tcp_connection(io_service));
}
tcp::socket& socket()
{
return socket_;
}
void start()
{
message_ = "Write me in 5 sec";
boost::asio::deadline_timer t(service_, boost::posix_time::seconds(5));
t.async_wait(boost::bind(&tcp_connection::writeAfter, shared_from_this(), boost::asio::placeholders::error));
}
private:
tcp_connection(boost::asio::io_service& io_service)
: service_(io_service), socket_(io_service)
{
}
void writeAfter(const boost::system::error_code&) {
std::cout << "writing to socket" << std::endl;
boost::asio::async_write(socket_, boost::asio::buffer(message_),
boost::bind(&tcp_connection::handle_write, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_write(const boost::system::error_code& /*error*/,
size_t /*bytes_transferred*/)
{
}
boost::asio::io_service &service_;
tcp::socket socket_;
std::string message_;
};
EDIT: Debug log
#asio|1462018696.996630|0*1|deadline_timer#0x7ffd9dd40228.async_wait
#asio|1462018696.996675|0|deadline_timer#0x7ffd9dd40228.cancel
#asio|1462018696.996694|0*2|socket#0x7ffd9dd403e0.async_accept
#asio|1462018696.996714|0*3|deadline_timer#0x7ffd9dd40408.async_wait
#asio|1462018696.996736|>1|ec=system:125
As we can see the cancel is called on the timer but I don't have a single cancel in my code so I don't know why it is called.
Thank you very much for your help
Are you still listening for other connections after you create a tcp_connection?
Since you haven't called async_read or async_read_some for your new connection, io_service.run() for that thread may simply have completed...
If you start the deadline timer in the tcp_connection constructor, it should keep io_service.run() going and send the message.
I found why.
There was a mess with shared pointers and my connection object was destroyed. So was the TCP object. The connection was close.
Thanks #kenba for your help !

Correct use of Boost::asio inside of a separate thread

I am writing a DLL plugin for the Orbiter space simulator, which allows for UDP communication with an external system. I've chosen boost::asio for the task, as it allows me to abstract from the low-level stuff.
The "boundary conditions" are as follows:
I can create any threads or call any API functions from my DLL
I can modify the data inside of the simulation only inside the callback passed to my DLL (each frame), due to lack of other thread safety.
Hence, I chose the following architecture for the NetworkClient class I'm using for communications:
Upon construction, it initializes the UDP socket (boost::socket+boost::io_service) and starts a thread, which calls io_service.run()
Incoming messages are put asyncronously into a queue (thread-safe via CriticalSection)
The callback processing function can pull the messages from queue and process it
However, I have run into some strange exception upon running the implementation:
boost::exception_detail::clone_impl > at memory location 0x01ABFA00.
The exception arises in io_service.run() call.
Can anyone point me, please, am I missing something? The code listings for my classes are below.
NetworkClient declaration:
class NetworkClient {
public:
NetworkClient(udp::endpoint server_endpoint);
~NetworkClient();
void Send(shared_ptr<NetworkMessage> message);
inline bool HasMessages() {return incomingMessages.HasMessages();};
inline shared_ptr<NetworkMessage> GetQueuedMessage() {return incomingMessages.GetQueuedMessage();};
private:
// Network send/receive stuff
boost::asio::io_service io_service;
udp::socket socket;
udp::endpoint server_endpoint;
udp::endpoint remote_endpoint;
boost::array<char, NetworkBufferSize> recv_buffer;
// Queue for incoming messages
NetworkMessageQueue incomingMessages;
void start_receive();
void handle_receive(const boost::system::error_code& error, std::size_t bytes_transferred);
void handle_send(boost::shared_ptr<std::string> /*message*/, const boost::system::error_code& /*error*/, std::size_t /*bytes_transferred*/) {}
void run_service();
NetworkClient(NetworkClient&); // block default copy constructor
};
Methods implementation:
NetworkClient::NetworkClient(udp::endpoint server_endpoint) : socket(io_service, udp::endpoint(udp::v4(), 28465)) {
this->server_endpoint = server_endpoint;
boost::thread* th = new boost::thread(boost::bind(&NetworkClient::run_service,this));
start_receive();
}
void NetworkClient::start_receive()
{
socket.async_receive_from(boost::asio::buffer(recv_buffer), remote_endpoint,
boost::bind(&NetworkClient::handle_receive, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)
);
}
void NetworkClient::run_service()
{
this->io_service.run();
}
There's nothing wrong with your architecture that I can see. You should catch exceptions thrown from io_service::run(), that is likely the source of your problem.
void NetworkClient::run_service()
{
while(1) {
try {
this->io_service.run();
} catch( const std::exception& e ) {
std::cerr << e.what << std::endl;
}
}
}
You'll also want to fix whatever is throwing the exception.