Boost ASIO Question...

Started by
2 comments, last by beebs1 13 years, 6 months ago
Hiya :)

I'm using Boost ASIO, and using tcp::socket::async_read_some() to read from the sockets...

The client application is sending packets which begin with a 1 byte 'command', followed by 2 bytes to indicate the size of the packet data, followed by the packet data itself.

I'm pretty sure that async_read_some() can read just about any amount of data (except an empty packet?) and so I can't assume whole packets will be waiting in my input buffer.

Is this correct?

If so I'm guessing I'll have to add checks to see whether a whole packet has been read before I process it. Also that would mean it's possible for the 'size' bytes to be read in two seperate calls?

// Some pseudo-code... Seems OK? :)void onRead() {  if( input_buffer.size < 3 ) then keep_reading();  else {    // got a header at least...    command = input_buffer[0];    size = (int)input_buffer[1]; // bytes 1-2    if( input_buffer.size < size ) then keep_reading(); // don't have full packet    else {      process_packet();      input_buffer.clear();    }  }}


Thanks for any help or confirmation! :)

Edit:
Is it also possible that I will receive more than one packet in a read call?
Advertisement
Quote:Original post by beebs1
I'm pretty sure that async_read_some() can read just about any amount of data (except an empty packet?) and so I can't assume whole packets will be waiting in my input buffer.

Edit:
Is it also possible that I will receive more than one packet in a read call?
Yes, this is the case with TCP in general (not just boost::asio). Tcp is a stream protocol, and as such has no concept of packets. It is up to you to encode some sort of packet boundaries (i.e. prefix with length), and buffer enough data on the receive end to reconstruct your entire packet.

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

The nice thing about boost::asio is that it lets you do the traditional TCP stream logic as mentioned and as your code shows which is for great efficiency as well as letting you implement that type of logic easily with another API function called async_read.

Using async_read and assuming you have a fixed header style protocol (which makes it extremely simple, the alternative is still doable just a little more logic work) you can instead just do this:

- Post header read (wait for it)
- Post data read (wait for it)
- Process complete packet
- Loop

So, first, you'd post a 3 byte read into a buffer, I use std::vector for my data buffers but boost does have an array I believe some people use instead. I use a vector because I have other code that uses a vector that I didn't want to convert to a boost array simply for the sake of doing the same thing using a different class. Anyways, async_read will read all the data you are requesting so you don't have to treat the data you are getting as a stream but rather as packet chunks.

Once you have the 3 bytes, you'd process the size and then post another read of that length. Here, you'd just resize the vector to 3 + destination size and make sure to pass the correct pointers to the buffer locations to write to async_read. There are some gotchas you have to watch out for though. Since your size field is only 2 bytes, a user can only allocate up to 65kb of memory, but if you had a larger field, you'd want to cap it so people can't crash your server by sending large sizes in the field! You still might want to limit it if your protocol won't ever use larger packets though just to be safe. Don't forget you need to handle 0 bytes as well so don't just post a read without checking the size before going on to the processing stage!

So now, when you determine you have the final packet stored, you can just process it. When you are done, clear the buffer and then post the next read of 3 bytes to wait for the next header. The boost:;asio chat example actually uses two different handlers to accomplish this, but I just use one in my code. Either works though; I just like having minimal code which boost lets me compared to alternative solutions of doing it by hand.

This approach scarifies efficiency at the expense of being easy to implement and follow. You have well defined behavior and overall for a project on the scale you have mentioned in other threads, it should be just fine.

As I said in a reply to another of your threads, the learning curve here is mostly boost. You should still understand how TCP works, which it appears you do from this thread, so once you see how boost::asio simplifies the work, you can move on to writing more tailored code that can either be more efficient using async_read_some or easier to implement via async_read. For my light weight projects, I have been using this method I've talked about here and it's worked great. No more needing to track sliding buffers and trying to work with more data than I actually need!
Thanks very much. That was very helpful!!

This topic is closed to new replies.

Advertisement