Minimal ASIO TCP client

Without a client, we can't even check if the ASIO TCP server we have written in the previous post actually works.

Here are the include files we'll need for our client:
#include <iostream>
#include <boost/array.hpp>
#include <boost/asio.hpp>

It should be clear why we need Boost ASIO and the standard iostream, boost array is not strictly a necessity, but helps us to keep the code simpler. We'll show how and why at the due time.

We need to provide the client the host name and the port for establish a connection. Host name is going to be passed from the command line by program arguments, the port number is, for this simple test app, just a fixed constant defined in this way:
namespace
{
  const char* HELLO_PORT_STR = "50013";
}
Note that, differently from the server, here we need it defined as a string.

Details on how the host name is extracted from the arguments are irrelevant, assumes it is passed to the current function as a const char* input parameter named host.

All the function body is included in a try-catch block like this:
try
{
  // ...
}
catch(std::exception& e)
{
  std::cerr << e.what() << std::endl;
}
Let's see now the real code:
boost::asio::io_service io_service; // 1

boost::asio::ip::tcp::resolver resolver(io_service); // 2
boost::asio::ip::tcp::resolver::query query(host, HELLO_PORT_STR); // 3
boost::asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
boost::asio::ip::tcp::resolver::iterator end; // 4

boost::asio::ip::tcp::socket socket(io_service);
boost::system::error_code error = boost::asio::error::host_not_found;
while(error && endpoint_iterator != end) // 5
{
  socket.close();
  socket.connect(*endpoint_iterator++, error);
}
if(error) // 6
  throw boost::system::system_error(error);

for(;;) // 7
{
  boost::array<char, 4> buf; // 8
  size_t len = socket.read_some(boost::asio::buffer(buf), error); // 9

  if(error == boost::asio::error::eof) // 10
    break; // Connection closed cleanly by peer
  else if(error)
    throw boost::system::system_error(error);

  std::cout << "[!]"; // 11
  std::cout.write(buf.data(), len); // 12
}
1. As usual with ASIO, we need an I/O service object to do our job.
2. Then we create a resolver, that will provide us a connection to the server.
3. To actually get the connection, we create a resolver::query object for the host-port we are interested in, then we use the resolve() method of our resolver to get an iterator to the resulting connections.
4. As a common idiom states, when we need an "end" iterator to terminate a loop, we generate an iterator of that type calling the default ctor.
5. This loop is not complex, but it is not the most linear piece of code you could see around. The idea is that we want to loop on all the endpoints retrieved by our query on the resolver until we find a working one. So we loop until we find one that is not giving an error when we use it as an endpoint for our socket. The code works smoothly from the second iteration on, the issue is the first one: since there is no previous iteration, we should assume it, initializing error as it was a bad endpoint, and closing the socket in any case, just for homogeneity.
6. If we end the previous loop with an error, no socket connection succeeded, so we throw an error.
7. Another loop, here we have a socket connection to the server, we plan to read from it till we get an EOF.
8. The chosen array size is so ridiculously small to better show how it works.
9. We call read_some() on the socket, to put the retrieved data in our tiny buffer, and iterate the reading until the end. The passed chunck of memory is converted to an asio::buffer object - using a boost::array we spare ourself the bore of passing also the size of the memory block, the asio::buffer is smart enough to get it directly from boost::array.
10. We check the error set by read_some(), EOF actually means that the communication has been correctly completed. If we have a real error, we throw an exception.
11. A bit of fun: we show the actual joins in the data communication.
12. We output the chunk of data currently read from the socket.

Post based on a page from the official boost::asio tutorial. You could get more information in The Boost C++ Libraries online book by Boris Schäling. Chapter seven is about ASIO.

3 comments:

  1. Hello, I am trying to use Boost Asio along with MicroManager. The problem is that BOTH the DeviceBase.h include file form MicroManager and Boost Asio include winsock, winsock2 and several other include files and keep giving me errors of duplication. Any idea of how to solve this?

    Thanks!

    ReplyDelete
  2. Luis, you shit, link it as a static library and use ifdefs.

    ReplyDelete
    Replies
    1. Ehmmm ... I don't know if thank you for suggesting a solution to Luis or blame you for the language. Or you are friends and it is meant as a playful comment.
      BTW, sorry, I didn't see the comment before, I left the blog unattended for a long while and I missed it.

      Delete