
Hi Gavin,
Yep, I ran a few tests where I ensured that two complete messages were received into a boost::asio::streambuf and confirmed that read_until '[' returned the SOP first index, read_until ']' returned the first EOP index, I then consume()'d those characters and repeated the call to read_until and confirmed it returned immediately based on the streambuf contents. So this all works as described by the documentation.
Yep. Just be careful if you're mixing calls to read_until with calls to read -- read_until may fetch a larger amount of data into the streambuf (beyond the delimiter), while read will always wait for new data, even if the streambuf isn't empty. So you need to explicitly check the streambuf before calling read().
Of course if you're only dealing with purely delimited data then this shouldn't be an issue, as you'll only be using [async_]read_until.
That is an interesting warning. I hadn't really thought about whether I could mix the blocking and non-blocking read commands (but would have assumed I should not!). The modified chat application uses using only the async versions, i.e., async_read_until, and async_write.
Once I finish my variation on the chat client/server, I'd be happy to post the code. At a minimum it would provide code for people on the list to review/comment on, and any final version of the code would benefit anyone interested in reading streams containing a different style of packet than that used in the boost example chat client/server.
Asio has a dedicated mailing list (https://lists.sourceforge.net/lists/listinfo/asio-users), which I believe the library maintainer pays closer attention to than this list; it may be worthwhile asking there.
Oh, good point. My code started in Boost.Iostreams, but now its in the Boost.Asio camp :)
Maybe you could even get it included in the official docs. :)
Yes, that was my hope. You can never have too many examples!
(I have some working streambuf code but it's not really in consumable-example form.)
Ok, so here's a streambuf question for you. In my attempt to modify the chat server as much as possible, I initially modified the buffering to use a streambuf member variable ... but that fails, since a streambuf is non-copyable. My solution was to use a shared_ptr<streambuf>> - see the chat_message.hpp code below. Given that the shared_ptr is reference counted, when the server 'delivers' a read message to multiple clients, the shared pointer reference count on a new message read from a client will increment as its copied to each of the clients connected to a session, and then decrement as each server client handler write pops the message off its write queue. At least I assume that is what is going on (I just finished modifying the server and its working ok), I'll use the debugger and trace the server code tomorrow to check. Do you see anything wrong with using a shared_ptr<streambuf> member variable? I wanted to use a streambuf so that I could pass it directly to the async_read_until and async_write functions (saving the buffer() conversions used in the original code). Cheers, Dave // // chat_message.hpp // ~~~~~~~~~~~~~~~~ // // Copyright (c) 2003-2014 Christopher M. Kohlhoff (chris at kohlhoff dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // // Chat client/server message coder/decoder. // // This is based on the Boost chat client/server example modified // to use a different encoding/decoding method. // // The encoded message to send or receive is stored in a // std::shared_ptr<boost::asio::streambuf> buffer_ // a shared pointer is used, since streambufs are non-copyable. // #ifndef CHAT_MESSAGE_HPP #define CHAT_MESSAGE_HPP #include <boost/asio.hpp> #include <iostream> #include <string> class chat_message { public: // Protocol codes // // * const char is used rather than an enum hack, so // that the data type is consistent for stream // insertion (otherwise a cast would be required) // // * the codes are public, so that async_read_until can be // used with chat_message::SOP and chat_message::EOP. // // * Note: g++ 4.8.3 does not support inline initialization, // so the values are assigned below. // // * Encoding/decoding and the use of the escape mask // // -------------------------------------- // | Protocol code || Masked Code | // |---------------------||-------|-------| // | Name | ASCII | code || ASCII | code | // |------|-------|------||-------| ------| // | SOP | '[' | 0x5B || '{' | 0x7B | // | EOP | ']' | 0x5D || '}' | 0x7D | // | ESC | '\' | 0x5C || '|' | 0x7C | // -------------------------------------- // // The XOR mask of 0x20 converts the MSB nibble // from 0x50 to 0x70. This ensures that the SOP // EOP and ESC codes *never* appear in the data // stream as data, they only appear as SOP, EOP, // or ESC. This means that async_read_until does // not have to check for one, or two character // protocol codes. // static const char SOP; static const char EOP; static const char ESC; static const char MASK; chat_message(int max_length = 512) : buffer_(new boost::asio::streambuf(max_length)) { } // Stream buffer access const boost::asio::streambuf& buffer() const { return *buffer_;} // Stream buffer access boost::asio::streambuf& buffer() {return *buffer_;} // Encode to streambuf bool encode(const std::string &message); // Decode from streambuf bool decode(std::string &message); private: std::shared_ptr<boost::asio::streambuf> buffer_; }; // Protocol codes const char chat_message::SOP = 0x5B; const char chat_message::EOP = 0x5D; const char chat_message::ESC = 0x5C; const char chat_message::MASK = 0x20; // Encode to streambuf bool chat_message::encode(const std::string &message) { size_t size = message.size(); std::ostream os(buffer_.get()); // Start-of-packet os << '['; // Encode message for (size_t i = 0; i < size; i++) { char c = message[i]; switch (c) { case SOP: case EOP: case ESC: os << (char)ESC; os << (char)(c ^ MASK); break; default: os << message[i]; break; } } // End-of-packet os << ']'; return os.good(); } // Decode from streambuf // * the code uses is.get(char &c) rather than is >> c, so // that all ASCII values are decoded bool chat_message::decode(std::string &message) { size_t size = buffer_->size(); std::istream is(buffer_.get()); // Start-of-packet char c; is.get(c); assert (c == '['); size--; // Decode message buffer bool done = false; while(!done && size-- && is.get(c)) { switch (c) { case SOP: // Should not occur assert (false); break; case ESC: is >> c; message.push_back(c ^ MASK); break; case EOP: done = true; break; default: message.push_back(c); break; } } return done; } #endif // CHAT_MESSAGE_HPP