Monday, January 15, 2007

Unbuffered socket iostreams

Boost.Asio includes an iostreams-based interface to TCP sockets, ip::tcp::iostream, for simple use cases. However, like the file iostreams provided by the standard library, ip::tcp::iostream buffers input and output data. This can lead to problems if you forget to explicitly flush the stream. For example, consider the following code to perform an HTTP request:
ip::tcp::iostream stream("www.boost.org", "http");
stream << "GET / HTTP/1.0\r\n"
<< "Host: www.boost.org\r\n"
<< "\r\n";

std::string response_line;
std::getline(stream, response_line);
...
The code will be stuck on the getline() call waiting for the response, because the request will still be sitting stream's output buffer. The correct code looks like this:
ip::tcp::iostream stream("www.boost.org", "http");
stream << "GET / HTTP/1.0\r\n"
<< "Host: www.boost.org\r\n"
<< "\r\n"
<< std::flush;
The std::flush will cause the stream to send the entire contents of its output buffer at that point.

Boost.Asio now supports an alternative solution: turn off the stream's output buffering. This is accomplished as follows:
ip::tcp::iostream stream("www.boost.org", "http");
stream.rdbuf()->pubsetbuf(0, 0);
stream << "GET / HTTP/1.0\r\n"
<< "Host: www.boost.org\r\n"
<< "\r\n";
Now you can send and receive to your heart's content, without having to worry about whether your message is stuck in the output buffer, but be warned: an unbuffered stream is a lot less efficient in terms of system calls. Don't use this feature if you care about performance.

5 comments:

Anonymous said...

Is it not possible to tie it to itself (or something similar) so that it automagically flushes on a read operation?

chris said...

Interesting idea, I didn't think of that. Using tie() is not exactly the same as an unbuffered stream, but it does give the expected behaviour for request-response style protocols (like HTTP, SMTP etc). Perhaps it should also be set by default, hmmm... something to think about.

jemfinch said...

Just a note to say that I had the same thought that anonymous did, and think it's an entirely reasonable default for the stream to flush its write buffer when a read is attempted. In fact, I was surprised that it didn't :)

chris said...

In fact, I did think about it and decided it should be on by default. Consequently my revised TR2 proposal (N2175) does specify that the stream should be tied to itself.

However, it seems I forgot to actually implement it! :-/

ansonthegnome said...

Now, it would be nice if we could do async reads from socket iostreams. The example is nice, but it only covers one-off sends and receives. As far as I can tell, there is no way to get the iostream ease of use for reading from or writing to a socket unless you do it completely synchronously, since the underlying basic_socket_streambuf doesn't support anything but an asynchronous connect().

Such functionality would also simplify one form of async_read(), it seems a bit strange that the first argument is something like an ip::tcp::socket and the second argument is a basic_stream_buf, which is itself a basic_socket.