Question

This is long question. TL;DR: I don't know how to properly design programs using asio.

People have written GUI wrappers for CLI utilities. I am writing a "proxy" for such a utility that relays the commands issued by the GUI to another copy of the utility running on a remote computer (via another "proxy" on the remote computer).

I am currently using asio for socket I/O, but am using a simple loop in main() to perform blocking reads from stdin (async I/O not possible on stdin & friends on Windows). I have tried to decouple everything by using a simple pub/sub mechanism. Message subscribers run on their own threads and receive messages from a std::deque via a std::condition_variable. I currently have the following I/O related classes:

  • SocketServer: Runs on own thread, creates an asio::io_service, binds to & listens on a port, and accepts remote connections.
  • SocketClient: Creates an asio::io_service, connects to remote host & provides password.
  • SocketConnection: Gets created by SocketServer or SocketClient. Authenticates remote host (server-only), reads from socket, and writes to socket on receiving SocketOutputMessage (on separate thread).
  • ConsoleWriter: Runs on own thread, writes to stdout upon receiving ConsoleOutputMessage.

To me the major flaw in this design is that there are too many threads! SocketServer needs its own thread because of blocking call to asio::io_service::run(). Similarly, ConsoleWriter is on its own thread to ensure all writes to stdout occur on the same thread. I don't want yet another thread for SocketConnection but need it as a consequence of pub/sub. (I want message to always be dispatched on the receiver's thread)

Please could someone provide me with suggestions on how to streamline the above design (and hopefully reduce the no. of threads required)? To this end, are there any asio features I should know about? Please provide some sample code and/or links to relevant sites or SO answers if you can.

Cheers.

Was it helpful?

Solution

I recommend that you create two io_services, the first one (which you can dispatch in a thread) should be used for all socket communication.) And the second (dispatching on the main thread) should handle the console activity.

For example:

main()
{
  // Run the network service
  std::thread service([]() { net_service.run(); });
  // Now run the console service
  console_service.run();
}

To communicate between the two services, use the io_service::post() operation. This posts a handler onto the given io service which is executed in that context. For example.

client_socket.async_read_some(_data, [](...) {
  // Now you have the data and you want to print it..
  console_service.post([=]() { std::cout << "message " << _data << std::endl; });
});

So now when you read something from the client_socket, you post that data to the console service - note it's important that either you copy the data or if you want to avoid the copies, create some separate queue like you have and simply use the post() mechanism to notify the other io_service.

NOTE: The other answer mentions poll(), this will cause the context to spin as it will return immediately if there is nothing to complete.

OTHER TIPS

io_serivce.poll() just runs the current events and then returns, I call it repeatedly instead of having a thread just for that.

You could avoid having consolewriter on it's own thread by locking around the writing part, by some form of lock/mutex/spinnlock/...

If you use async_read() instead of read() you supply a callback that's called whenever there's data to be read. That way you let asio handle the threads and you don't need to worry about it.

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