I'm trying to implement a single C++ application, that holds two processing loops. Currently the first processing loop (boost's io_service::run) blocks the execution of the second one.
Approaches utilizing threads or std::async approaches failed. (I don't have experience/background on multi-threading).
Is there an elegant way to run the io_service::run in an other thread, while still executing the callbacks upon incoming UDP datagrams?
Main-File:
class Foo
{
public:
Foo();
void callback(const int&);
private:
// ... (hopefully) non-relevant stuff...
};
int main()
{
Foo foo_obj;
// I need to run this function (blocking) but the constructor is blocking (io_server::run())
run();
return 0;
}
Foo::Foo(){
boost::asio::io_service io;
UDP_Server UDP_Server(io);
// Set function to be called on received message
UDP_Server.add_handler(std::bind(&Foo::callback, this, std::placeholders::_1));
// This function should be non-blocking
// -> tried several things, like threads, async, ... (unfortunately not successful)
io.run();
}
// realization of callback function here (see class definition)
Included custom "library":
class UDP_Server
{
public:
UDP_Server(boost::asio::io_service&);
void add_handler(std::function<void(int)>);
private:
// Function handle
std::function<void(int)> callbackFunctionHandle;
// Functions
void start_receive();
void handle_receive(const boost::system::error_code&, std::size_t);
// ... (hopefully) non-relevant stuff...
};
// Constructor
UDP_Server::UDP_Server(boost::asio::io_service& io_service)
: socket_(io_service, udp::endpoint(udp::v4(), UDP_PORT)){
}
// Store a callback function (class foo) to be called whenever a message is received
void UDP_Server::add_handler(std::function<void(int)> callbackFunction){
try
{
callbackFunctionHandle = callbackFunction;
start_receive();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
// Async receive
UDP_Server::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));
}
// When message is received
void UDP_Server::handle_receive(const boost::system::error_code& error,
std::size_t bytes_transferred)
{
if (!error || error == boost::asio::error::message_size)
{
// ... do smth. with the received data ...
// Call specified function in Foo class
callbackFunctionHandle(some_integer);
start_receive();
}
else{
// ... handle errors
}
}
have a look at what they did in here:
boost::asio::io_service io_service;
/** your code here **/
boost::thread(boost::bind(&boost::asio::io_service::run, &io_service));
ros::spin();
So you basically start the blocking call to io_service::run() in a separate thread from the ros::spin().
If you start that bound to a single cpu-node (in order to not waste 2 cpu-nodes with waiting commands) your scheduler might handle stuff.
Related
I am new to Asio, so I am a little confused about the control flow of asynchronous operations. Let's see this server:
class session
{
...
sendMsg()
{
bool idle = msgQueue.empty();
msgQueue.push(msg);
if (idle)
send();
}
send()
{
async_write(write_handler);
}
write_handler()
{
msgQueue.pop()
if (!msgQueue.empty())
send();
}
recvMsg()
{
async_read(read_handler);
}
read_handler()
{
...
recvMsg();
}
...
};
class server
{
...
start()
{
async_accept(accept_handler);
}
accept_handler()
{
auto client = make_shared<session>(move(socket));
client->recvMsg();
...
start();
}
...
};
int main()
{
io_context;
server srv(io_context, 22222);
srv.start();
io_context.run();
return 0;
}
In this case, all completion handlers accept_handler, read_handler, write_handler will be called in the thread calling io_context.run(), which is the main thread. If they will run in the same thread, it means they will run sequentially, not concurrently, right? And further, the msgQueue will be accessed sequentially, so there is no need a mutex lock for this queue, right?
I think async_* functions tell the operating system to do some work, and these work will run simultaneously in some other threads with their own buffers. Even if these work are done at the same time(say, at a point, a new connection requirement arrives, a new message from a exist client arrives, sending a message to a exist client is done), the completion handlers(accept_handler, read_handler, write_handler) will still be called sequentially. They will not run concurrently, am I correct?
Thank you so much for your help.
Yes. There's only one thread running the io_context, so all completion handlers will run on that one thread. Indeed this implies a strand (the implicit strand) of execution, namely, all handlers will execute sequentially.
See: https://www.boost.org/doc/libs/1_81_0/doc/html/boost_asio/overview/core/threads.html
and these work will run simultaneously in some other threads with their own buffers
They will run asynchronously, but not usually on another thread. There could be internal threads, or kernel threads, but also just hardware. Their "own" buffer is true, but dangerously worded, because in Asio the operations never own the buffer - you have to make sure it stays valid until the operation completes.
Note:
if there can be multiple threads running (or polling) the io service, you need to make sure access to IO objects is synchronized. In Asio this can be achieved with strand executors
not all IO operations may be active in overlapping fashion. You seem to be aware of this given the msgQueue in your pseudo code
Bonus
For bonus, let me convert your code into non-pseudo code showing an explicit strand per connection to be future proof:
Live On Coliru
#include <boost/asio.hpp>
#include <deque>
namespace asio = boost::asio;
using asio::ip::tcp;
using boost::system::error_code;
using namespace std::placeholders;
class session : public std::enable_shared_from_this<session> {
public:
session(tcp::socket s) : s(std::move(s)) {}
void start() {
post(s.get_executor(), [self = shared_from_this()] { self->recvMsg(); });
}
void sendMsg(std::string msg) {
post(s.get_executor(), [=, self = shared_from_this()] { self->do_sendMsg(msg); });
}
private:
//... all private members on strand
void do_sendMsg(std::string msg) {
bool was_idle = msgQueue.empty();
msgQueue.push_back(std::move(msg));
if (was_idle)
do_writeloop();
}
void do_writeloop() {
if (!msgQueue.empty())
async_write(s, asio::buffer(msgQueue.front()),
std::bind(&session::write_handler, shared_from_this(), _1, _2));
}
void write_handler(error_code ec, size_t) {
if (!ec) {
msgQueue.pop_front();
do_writeloop();
}
}
void recvMsg() {
//async_read(s, asio::dynamic_buffer(incoming),
//std::bind(&session::read_handler, shared_from_this(), _1, _2));
async_read_until(s, asio::dynamic_buffer(incoming), "\n",
std::bind(&session::read_handler, shared_from_this(), _1, _2));
}
void read_handler(error_code ec, size_t n) {
if (!ec) {
auto msg = incoming.substr(0, n);
incoming.erase(0, n);
recvMsg();
sendMsg("starting job for " + msg);
sendMsg("finishing job for " + msg);
sendMsg(" -- some other message --\n");
}
}
tcp::socket s;
std::string incoming;
std::deque<std::string> msgQueue;
};
class server {
public:
server(auto ex, uint16_t port) : acc(ex, tcp::v4()) {
acc.set_option(tcp::acceptor::reuse_address(true));
acc.bind({{}, port});
acc.listen();
}
void accept_loop() {
acc.async_accept(make_strand(acc.get_executor()),
std::bind(&server::accept_handler, this, _1, _2));
}
void accept_handler(error_code ec, tcp::socket s) {
if (!ec ){
std::make_shared<session>(std::move(s))->start();
accept_loop();
}
}
private:
tcp::acceptor acc;
};
int main() {
boost::asio::io_context ioc;
server srv(ioc.get_executor(), 22222);
srv.accept_loop();
ioc.run();
}
With a sample client
for a in foo bar qux; do (sleep 1.$RANDOM; echo "command $a")|nc 127.0.0.1 22222 -w2; done
Prints
starting job for command foo
finishing job for command foo
-- some other message --
starting job for command bar
finishing job for command bar
-- some other message --
starting job for command qux
finishing job for command qux
-- some other message --
I'm testing my application which contains a TCP client. To test that I've created a simple TCP server based on boost examples. The problem is that once each ~5 test invocations with valgrind the test fails to connect to local server. When not using valgrind all the tests pass on each invocation.
I can't find the cause of it. The server implementation:
class arcturus_mock
{
private:
boost::asio::io_service ios;
boost::asio::ip::tcp::socket socket;
boost::asio::ip::tcp::acceptor acceptor;
std::thread t;
public:
arcturus_mock(short port)
: acceptor(ios,
boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))
, socket(ios)
{
do_accept();
}
// A new thread is created to not to block on calling
// io_service::run function
void run()
{
t = std::thread([&ios = this->ios]() { ios.run(); });
}
void stop()
{
ios.stop();
t.join();
}
private:
void do_accept()
{
acceptor.async_accept(socket, [this](boost::system::error_code ec) {
if (!ec)
std::make_shared<arcturus_mock_session>(std::move(socket))
->start();
do_accept();
});
}
};
And the corresponding session:
class arcturus_mock_session : public std::enable_shared_from_this<arcturus_mock_session>
{
private:
boost::asio::ip::tcp::socket socket;
char data[1024];
public:
arcturus_mock_session(boost::asio::ip::tcp::socket &&used_socket)
: socket(std::move(used_socket))
{
}
void start()
{
using boost::asio::async_write;
async_write( ...
}
};
I run the tests using Catch2 framework. This is how the test case looks like:
TEST_CASE(" ... ")
{
arcturus_mock mock(1050);
mock.run();
SECTION(" ... ")
{
client c;
// That throws sometimes
REQUIRE_NOTHROW(c.connect_and_handle("localhost", 1050));
}
mock.stop();
}
Could the problem be caused by the thread which won't manage to create and start the server by the time the client connects to it?
It's a race condition.
When the server thread starts, there is not necessarily any work to be done (async_accept may not happen quickly enough). This means run() simply exits immediately, and the server doesn't run.
Either make async_accept precede the thread launch or use a io_service::work to keep the service occupied.
I have a protocol structure where one class takes care of protocol states (Protocol) and another class takes care of send and receiving messages (Comm).
I´m using boost:asio in asynchronous mode.
So I have the following code structure:
#include <string>
#include <iostream>
#include "boost/asio.hpp"
#include "boost/bind.hpp"
class Comm {
public:
Comm::Comm();
void SendMessage(std::string message, void (callback) (const boost::system::error_code& errorCode, std::size_t bytesTranferred));
private:
boost::asio::io_service ioService;
std::shared_ptr<boost::asio::ip::tcp::socket> mySocket;
};
Comm::Comm()
{
boost::asio::ip::tcp::resolver resolver(ioService);
boost::asio::ip::tcp::resolver::query query("192.168.0.1");
boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query);
mySocket->connect(*iterator);
}
void Comm::SendMessage(std::string message, void (callback) (const boost::system::error_code& errorCode, std::size_t bytesTranferred))
{
mySocket->async_send(boost::asio::buffer(message.c_str(), message.length()), boost::bind(&callback)); // <<< ERROR HERE
}
class Protocol {
public:
void SendMessage(std::string message);
void SendMessageHandler(const boost::system::error_code& errorCode, std::size_t bytesTranferred);
private:
Comm channel;
};
void Protocol::SendMessage(std::string message)
{
channel.SendMessage(message, &SendMessageHandler); // <<< ERROR HERE
}
void Protocol::SendMessageHandler(const boost::system::error_code& errorCode, std::size_t bytesTranferred)
{
if (!errorCode)
std::cout << "Send OK" << std::endl;
else
std::cout << "Send FAIL." << std::endl;
}
As shown, I need that the callback of the async_send to be a non-static function of the caller´s class, so I have to pass the callback function in SendMessage and use it as a parameter in async_send.
These both statements are not compiling. I´ve tried variations but I can´t find what what´s going on here.
Help appreciated.
Try something like this using binding to class method:
void Comm::SendMessage(std::string message, boost::function< void(const boost::system::error_code& , std::size_t) > callback )
{
mySocket->async_send(boost::asio::buffer(message.c_str(), message.length()), callback);
}
...//later
channel.SendMessage(message, boost::bind(&Protocol::SendMessageHandler, this) );
Note/more importantly you have amount unfixable bugs here:
You are taking std::string message by value several times - it will copy the content.
Comm::SendMessage uses local message object, which will be destroyed before async operation will complete (boost::asio::buffer will not copy content).
It will be hard to use 2 or more Comm objects, since each have its own ioService (you will not able to run them all at same time)
No shared_ptr or any other capability to control object lifetime: your SendMessageHandler can be called when Protocol already destroyed.
Protocol does not control write parallelism, and multiple SendMessage calls can lead to write mixed buffers into sockets, this can/will send complete trash over network.
More fatal/minor issues, no point to search for them.
Consider taking one of the asio examples as base usage pattern.
I am a bit lost in a construct of libraries, which I have to tangle together. I need help to indtroduce some timers into this construct.
I have the following:
com.cpp which has main and includes com.hpp
com.hpp which includes a host.h and needed boost includes and defines a class comClient
host.c with included host.h
wrapper.cpp with included com.hpp and some needed boost includes
Now, my com.cpp is creating a comClient and uses it for asynch communication on the com-port. Using boost::asio::serial_port and boost::asio::io_service.
I need to work with some timers, in order to catch when a paket needed too long to transmit.
When creating an instance of comClient, the paket-timer should be initialised.
Using asynch_read_some in a private function of comClient, I call a private handler of comClient, then this handler calls a function of host.c, which calls to the wrapper.cpp a function to restart the timer.
This is the function to init the timer:
//wrapper.cpp
void IniPacketTimer(void *pCHandle){
boost::asio::io_service io;
boost::asio::deadline_timer t(io, boost::posix_time::milliseconds(25));
t.async_wait(&hostOnTimeout(pCHandle));
io.run();
}
This would be the command chain in short:
//comClient.cpp
main{
comClient cc();
}
//comClient.hpp
class comClient(boost::asio::io_service& io_service){
comClient(){
hostInit();
aread();
}
private:
aread( call aread_done)
areaddone(call hostNewData())
}
//host.c
hostInit(){
IniPacketTimer()
}
hostNewData(){
resetTimer
}
//wrapper.cpp
resetTimer(){
t.expires_from_now
}
Questions:
How can I provide an asynchronous timer, which does not affect the asynch read/write operations on my serial port, but triggers execution of a function when the deadline is hit?
Should I use the already existing io_service or is it ok, if I just create another?
Why do I get an error C2102 '&' expects L-Value for my line t.async_wait?
You problem is not clear and since you don't post real code it is quite hard to guess what your problem is.
Especially your threading is not clear but for asio very important.
Below is an example that will compile but not run. I hope it gives you an hint on how to proceed.
It will open a serial port and a timer. Whenever the timer expires it will start a new one. It is a stripped version of code I used some time ago so maybe it will help you.
#include <boost/asio.hpp>
#include <boost/asio/serial_port.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <vector>
class SerialCommunication
{
public:
SerialCommunication(boost::asio::io_service& io_service, const std::string& serialPort)
: m_io_service(io_service)
, m_serialPort(m_io_service)
, m_timeoutTimer(m_io_service, boost::posix_time::milliseconds(5))
{
configureSerialPort(serialPort);
}
void configureSerialPort(const std::string& serialPort)
{
if(m_serialPort.is_open())
{
m_serialPort.close();
m_timeoutTimer.cancel();
}
boost::system::error_code ec;
m_serialPort.open(serialPort, ec);
if(m_serialPort.is_open())
{
// start Timer
m_timeoutTimer.async_wait(boost::bind(&SerialCommunication::TimerExpired, this, _1));
header_sync();
}
}
void header_sync()
{
m_serialPort.async_read_some(boost::asio::buffer(&m_header.back(), 1),
boost::bind(&SerialCommunication::header_sync_complete, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void header_sync_complete(
const boost::system::error_code& error, size_t bytes_transferred)
{
// stripped
read_payload(&m_payload[0], 0);
}
void read_payload(uint8_t* buffer, uint8_t length)
{
m_serialPort.async_read_some(boost::asio::buffer(buffer, length),
boost::bind(&SerialCommunication::payload_read_complete, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void payload_read_complete(
const boost::system::error_code& error, size_t bytes_transferred)
{
// stripped
// timer cancel and reset
m_timeoutTimer.cancel();
m_timeoutTimer.expires_at(boost::posix_time::microsec_clock::local_time() +
boost::posix_time::milliseconds(5));
m_timeoutTimer.async_wait(boost::bind(&SerialCommunication::TimerExpired, this, _1));
memset(&m_header[0], 0, 3);
header_sync();
}
void TimerExpired(const boost::system::error_code& e)
{
m_timeoutTimer.expires_at(m_timeoutTimer.expires_at() + boost::posix_time::milliseconds(5));
m_timeoutTimer.async_wait(boost::bind(&SerialCommunication::TimerExpired, this, _1));
}
boost::asio::io_service& m_io_service;
boost::asio::deadline_timer m_timeoutTimer;
boost::asio::serial_port m_serialPort;
std::vector<uint8_t> m_header;
std::vector<uint8_t> m_payload;
};
int main()
{
boost::asio::io_service io_service;
SerialCommunication cc(io_service, "/dev/ttyS0");
io_service.run();
return 0;
}
I'm trying to implement a simple client/server in ASIO.
I'd like the following on the serverside:
onConnect()
onDisconnect()
onMessageRecieved(char* data)
sendMessage(char* data)
and on the client side:
onConnect()
onDisconnect()
onMessageRecieved(char* data)
sendMessage(char* data)
I didn't realise things would be so complicated.
Here's the simple echo server which I'm working off of:
#include <cstdlib>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
class session
{
public:
session(boost::asio::io_service& io_service)
: socket_(io_service)
{
}
tcp::socket& socket()
{
return socket_;
}
void start()
{
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));
}
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
{
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
{
delete this;
}
}
private:
tcp::socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
class server
{
public:
server(boost::asio::io_service& io_service, short port)
: io_service_(io_service),
acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
{
session* new_session = new session(io_service_);
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();
new_session = new session(io_service_);
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
else
{
delete new_session;
}
}
private:
boost::asio::io_service& io_service_;
tcp::acceptor acceptor_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: async_tcp_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;
}
I can telnet into this server and everything is echoed.
Now I'd like to wrap up this code in onConnect(), onDisconnect(), onMessageReceived(char* data), etc. Similar to the way things are done in Node.js!
Has anyone got any pointers in this regard?
onMessageReceived() can be called from handle_read.
onConnect() can be called from start.
onDisconnect() can be called in the destructor of the session class.
For the bounty questions:
The io_service.run() can be placed in its own thread.
As per the documentation
Certain guarantees are made on when the handler may be invoked, in particular that a handler can only be invoked from a thread that is currently calling run() on the corresponding io_service object.
Asynchronous sending and receiving can be handled by this single thread. This simplifies thread safety because all the callbacks will be running in succession. This is probably the simplest way of using boost asio.
For calls coming from outside of the run() thread, you can schedule a callback (e.g. deadline_timer), from the 'outside thread' for immediate calling to simplify your thread safety handling. e.g.
boost::asio::deadline_timer timer(io_service);
timer.expires_from_now(boost::posix_time::seconds(0));
timer.async_wait(boost::bind(&MyClass::MyCallback, this, boost::asio::placeholders::error);
The io_service object will call the handler for you in a thread-safe fashion as soon as it has a chance. This way, your asio code can behave as if there was only a single thread in the entire system.
If multiple threads are required or preferred (e.g. Take advantage of multi-core) you may call run() on multiple thread. Handlers will have to be re-entrant. You may also want to use a strand for certain operations.
Otherwise, regular thread safety rules applies.