Asio: Is there an automatically resizable buffer for receiving input?
I don't know in advance the size to be received, so I send this quantity in a header.
I've looked at http://www.boost.org/doc/libs/1_59_0/doc/html/boost_asio/example/cpp03/chat/chat_message.hpp for an example using a header, but this example assumes the specification of a maximum body size.
Looking at the asio::buffer class, I must provide some underlying buffer, thus not flexible. Instead I've looked towards the asio::streambuf class, but using it as below gives segmentation/memory errors.
I try to give a maximum size to read only HEADER_LEN bytes i.e. the header.
Is this approach wrong?
void do_recv_header()
{
asio::streambuf buf(HEADER_LEN);
asio::async_read(*g_selected_conn, buf, [this, &buf](const system::error_code& ec, std::size_t bytes_transferred)
{
if (ec != 0) {
std::cout << "async_read() error: " << ec.message() << " (" << ec.value() << ") " << std::endl;
remove_closed_conn(g_selected_conn);
SetEvent(g_wait_event);
}
else {
std::istream is(&buf);
int body_len;
is >> body_len;
std::cout << body_len << std::endl;
do_recv_body(body_len);
}
});
}
boost::asio::streambuf is an automatically resizable buffer class. This buffer type is often used when initiating a read operation whose completion is predicated on the data's content, and not necessarily the size of the data. For example, one may use boost::asio::read_until() to read until a newline, without knowing or specifying the how much data may be read.
In the case of an application protocol with a fixed size header that contains the length of the body, and the header is followed by a variable length body, consider using a buffer type, such as std::vector<>. This will provide the same level of flexibility as boost::asio::streambuf, while simplifying some of the bookkeeping:
std::vector<char> buffer;
// Read header.
buffer.resize(protocol::header_size);
boost::asio::read(socket, boost::asio::buffer(buffer));
// Extract body size from header, resize buffer, then read
// body.
auto body_size = parse_header(buffer);
buffer.resize(body_size);
boost::asio::read(socket, boost::asio::buffer(buffer));
process_body(buffer);
Not how resizing the vector indicates how much data will be read in the read operations. When using streambuf, one has to manage the input and output sequences directly with these operations:
boost::asio::streambuf streambuf;
// Read header into the streambuf's output sequence.
auto bytes_transferred = boost::asio::read(socket,
streambuf.prepare(protocol::header_size));
// Commit read data from output sequence into the input
// sequence.
streambuf.commit(bytes_transferred);
// Extract body size from header. This would likely
// consume all of the streambuf's input sequence.
auto body_size = parse_header(streambuf);
// Clear the input sequence.
streambuf.consume(streambuf.size());
// Ready body into the streambuf's output sequence.
bytes_transferred = boost::asio::read(socket,
streambuf.prepare(body_size));
// Commit read data from output sequence into the input
// sequence.
streambuf.commit(bytes_transferred);
// Extract all of stream into the body.
process_body(streambuf);
Here is a complete example demonstrating this approach:
#include <array> // std::array
#include <functional> // std::bind
#include <iostream> // std::cout, std::endl
#include <vector> // std::vector
#include <boost/asio.hpp>
// This example is not interested in the handlers, so provide a noop function
// that will be passed to bind to meet the handler concept requirements.
void noop() {}
// The application protocol will consists of a fixed-size header
// containing a std::size_t with the length of the following
// variable length body. To keep it simple, some details
// are ommitted, such as endian handling.
namespace protocol {
enum
{
header_size = sizeof(std::size_t)
};
} // namespace protocol
std::vector<char> build_header(const std::string& body)
{
std::vector<char> buffer(protocol::header_size);
auto body_size = body.size();
std::memcpy(&buffer[0], &body_size, sizeof body_size);
return buffer;
}
std::size_t parse_header(const std::vector<char>& buffer)
{
return *reinterpret_cast<const std::size_t*>(&buffer[0]);
}
int main()
{
using boost::asio::ip::tcp;
// Create all I/O objects.
boost::asio::io_service io_service;
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 0));
tcp::socket socket1(io_service);
tcp::socket socket2(io_service);
// Connect the sockets.
acceptor.async_accept(socket1, std::bind(&noop));
socket2.async_connect(acceptor.local_endpoint(), std::bind(&noop));
io_service.run();
io_service.reset();
// Write a message from socket1 to socket2.
std::string test_message = "this is a test message";
{
auto header = build_header(test_message);
// Gather header and body into a single buffer.
std::array<boost::asio::const_buffer, 2> buffers = {{
boost::asio::buffer(header),
boost::asio::buffer(test_message)
}};
// Write header and body to socket.
boost::asio::write(socket1, buffers);
}
// Read from socket2.
{
// Use a vector to allow for re-sizing based on the
// amount of data needing to be read. This also reduces
// on the amount of reallocations if the vector is reused.
std::vector<char> buffer;
// Read header.
buffer.resize(protocol::header_size);
boost::asio::read(socket2, boost::asio::buffer(buffer));
// Extract body size from header, resize buffer, then read
// body.
auto body_size = parse_header(buffer);
buffer.resize(body_size);
boost::asio::read(socket2, boost::asio::buffer(buffer));
// Verify body was read.
assert(std::equal(begin(buffer), end(buffer),
begin(test_message)));
std::cout << "received: \n"
" header: " << body_size << "\n"
" body: ";
std::cout.write(&buffer[0], buffer.size());
std::cout << std::endl;
}
}
Output:
received:
header: 22
body: this is a test message
Related
Here is my server class, which renders an async event to send a string to my client, when connected.
The message is definitely dispatched to the client, as the writehandler is invoked successfully without any errors:
class Server {
private:
void writeHandler(ServerConnection connection, const boost::system::error_code &error_code,
std::size_t bytes_transferred) {
if (!(error_code)) {
std::cout << "SENT "<<bytes_transferred <<" BYTES"<< std::endl;
}
}
void renderWriteEvent(ServerConnection connection, const std::string& str) {
std::cout << "RENDERING WRITE EVENT" << std::endl;
connection->write = str;
boost::asio::async_write(connection->socket, boost::asio::buffer(connection->write),
boost::bind(&Server::writeHandler, this, connection,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
};
Now on the client side, after successfully connecting to the server, I call
void renderRead(){
std::cout<<"Available Bytes: "<<socket.available()<<std::endl;
std::string foo;
boost::system::error_code error_code;
std::size_t x = socket.read_some(boost::asio::buffer(foo), error_code);
std::cout<<error_code.message()<<std::endl;
std::cout<<"Bytes read: "<<x<<std::endl;
std::cout<<"Available Bytes: "<<socket.available()<<std::endl;
std::cout<<foo<<std::endl;
//boost::asio::async_read(socket, boost::asio::buffer(read_string), boost::bind(&Client::readHandler, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
which outputs "Available Bytes: 12"
Then, in calling boost::asio::read, I get 0 bytes read, and no error. I don't understand what's wrong. After the read, the number of bytes available for reading in the socket stream is still printed to be 12
A key point here is that read_some() doesn't allocate any memory, it fills memory that is provided to it. For your code, this means ASIO will only replace the data already existing inside of foo, and it will never exceed these bounds.
But you have std::string foo;, which is a default-constructed string, aka an empty string.
So ASIO is populating the buffer you are passing just fine. However, you are passing it a buffer with no room in it. ASIO fills it as much as possible: 0 bytes.
You can test this for yourself by adding the following to your code:
std::string foo;
std::cout << "Available room in buffer: "<< foo.size() << std::endl;
The fix would be to pass a buffer with memory already allocated. You could initialize the string with a length, but using a raw block of bytes that you interpret later as a string_view is more explicit.
constexpr std::size_t buffer_size = 32;
std::array<char, buffer_size> foo;
std::size_t x = socket.read_some(boost::asio::buffer(foo), error_code);
//...
std::string_view message(foo.data(), x);
std::cout << message << std::endl;
I would like to use the very convenient Boost async_read_until to read a message until I get the \r\n\r\n delimiter.
I like using this delimiter because it's easy to debug with telnet and make multiline commands. I just signal end of command by two new lines.
I call async_read_until like this:
void do_read()
{
boost::asio::async_read_until(m_socket,
m_input_buffer,
"\r\n\r\n",
std::bind(&player::handle_read, this, std::placeholders::_1, std::placeholders::_2));
}
And my handler looks like this at the moment:
void handle_read(boost::system::error_code ec, std::size_t nr)
{
std::cout << "handle_read: ec=" << ec << ", nr=" << nr << std::endl;
if (ec) {
std::cout << " -> emit on_disconnect\n";
} else {
std::istream iss(&m_input_buffer);
std::string msg;
std::getline(iss, msg);
std::cout << "dump:\n";
std::copy(msg.begin(), msg.end(), std::ostream_iterator<int>(std::cout, ", "));
std::cout << std::endl;
do_read();
}
}
I wanted to use std::getline just like the example, but on my system this keeps the \r character. As you can see, if I connect to the server and write hello plus two CRLF, I get this dump server side:
handle_read: ec=system:0, nr=9
dump:
104, 101, 108, 108, 111, 13,
^^^ \r here
By the way, this will also keep the next new line in the buffer. So I think that std::getline will not do the job for me.
I search a convenient and efficient way to read from the boost::asio::streambuf until I get this \r\n\r\n delimiter. Since I use async_read_until once at a time, when the handler is called, the buffer is supposed to have the exact and full data isn't it? What do you recommend to read until I get \r\n\r\n?
The async_read_until() operation commits all data read into the streambuf's input sequence, and the bytes_transferred value will contain the number of bytes up to and including the first delimiter. While the operation may read more data beyond the delimiter, one can use the bytes_transferred and delimiter size to extract only the desired data. For example, if cmd1\r\n\r\ncmd2 is available to be read from a socket, and an async_read_until() operation is initiated with a delimiter of \r\n\r\n, then the streambuf's input sequence could contain cmd1\r\n\r\ncmd2:
,--------------- buffer_begin(streambuf.data())
/ ,------------ buffer_begin(streambuf.data()) + bytes_transferred
/ / - delimiter.size()
/ / ,------ buffer_begin(streambuf.data()) + bytes_transferred
/ / / ,-- buffer_end(streambud.data())
cmd1\r\n\r\ncmd2
As such, one could extract cmd1 into a string from the streambuf via:
// Extract up to the first delimiter.
std::string command{
boost::asio::buffers_begin(streambuf.data(),
boost::asio::buffers_begin(streambuf.data()) + bytes_transferred
- delimiter.size()};
// Consume through the first delimiter.
m_input_buffer.consume(bytes_transferred);
Here is a complete example demonstrating constructing std::string directly from the streambuf's input sequence:
#include <functional> // std::bind
#include <iostream>
#include <boost/asio.hpp>
const auto noop = std::bind([]{});
int main()
{
using boost::asio::ip::tcp;
boost::asio::io_service io_service;
// Create all I/O objects.
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 0));
tcp::socket socket1(io_service);
tcp::socket socket2(io_service);
// Connect sockets.
acceptor.async_accept(socket1, noop);
socket2.async_connect(acceptor.local_endpoint(), noop);
io_service.run();
io_service.reset();
const std::string delimiter = "\r\n\r\n";
// Write two commands from socket1 to socket2.
boost::asio::write(socket1, boost::asio::buffer("cmd1" + delimiter));
boost::asio::write(socket1, boost::asio::buffer("cmd2" + delimiter));
// Read a single command from socket2.
boost::asio::streambuf streambuf;
boost::asio::async_read_until(socket2, streambuf, delimiter,
[delimiter, &streambuf](
const boost::system::error_code& error_code,
std::size_t bytes_transferred)
{
// Verify streambuf contains more data beyond the delimiter. (e.g.
// async_read_until read beyond the delimiter)
assert(streambuf.size() > bytes_transferred);
// Extract up to the first delimiter.
std::string command{
buffers_begin(streambuf.data()),
buffers_begin(streambuf.data()) + bytes_transferred
- delimiter.size()};
// Consume through the first delimiter so that subsequent async_read_until
// will not reiterate over the same data.
streambuf.consume(bytes_transferred);
assert(command == "cmd1");
std::cout << "received command: " << command << "\n"
<< "streambuf contains " << streambuf.size() << " bytes."
<< std::endl;
}
);
io_service.run();
}
Output:
received command: cmd1
streambuf contains 8 bytes.
To answer your questions first:
the buffer is supposed to have the exact and full data isn't it?
Yes, it will have all the data including "\r\n\r\n"
What do you recommend to read until I get \r\n\r\n?
What you are doing is fine enough. You just need to ignore the additional '\r' at the end of each command. This you can either do while reading from the stream or let it be handled by the command processor (or anything which does the command processing for you). My recommendation would be to defer the removal of additional '\r' to the command processor.
You probably need something on the lines of :
#include <iostream>
#include <string>
#include <sstream>
void handle_read()
{
std::stringstream oss;
oss << "key : value\r\nkey2: value2\r\nkey3: value3\r\n\r\n";
std::string parsed;
while (std::getline(oss, parsed)) {
// Check if it'a an empty line.
if (parsed == "\r") break;
// Remove the additional '\r' here or at command processor code.
if (parsed[parsed.length() - 1] == '\r') parsed.pop_back();
std::cout << parsed << std::endl;
std::cout << parsed.length() << std::endl;
}
}
int main() {
handle_read();
return 0;
}
If your protocol allows you to send empty commands, then you will have to change the logic and have a lookout for 2 consecutive empty new lines.
What do you actually wish to parse?
Of course, you could just use knowledge from your domain and say
std::getline(iss, msg, '\r');
At a higher level, consider parsing what you need:
std::istringstream linestream(msg);
std::string command;
int arg;
if (linestream >> command >> arg) {
// ...
}
Even better, consider a parser generator:
std::string command;
int arg;
if (qi::phrase_parse(msg.begin(), msg.end(), command_ >> qi::int_, qi::space, command, arg))
{
// ...
}
Where command_ could be like
qi::rule<std::string::const_iterator> command_ = qi::no_case [
qi::lit("my_cmd1") | qi::lit("my_cmd2")
];
I have written a basic client-server application in C++ using asio library. The client sends messages from the console to the server.
If I run it on localhost on either linux or windows, it works great. However, when I run it on my actual server, I get a strange behavior. Each time I send a message, then immediately after another message is sent that contains garbage or is empty. This sometimes happens, sometimes doesn't. But it does most of the times. I tried using a different port.
For example if I send messages 1, 2, and 3 this is what I see in the server's console:
What could I be doing wrong ?
server.cpp - Almost same code as seen here
#define ASIO_STANDALONE
#include <iostream>
#include <asio.hpp>
using asio::ip::tcp;
const std::size_t max_length = 2048;
const unsigned short PORT = 15562;
class Session
: public std::enable_shared_from_this<Session>
{
public:
Session(tcp::socket server_socket)
: _session_socket(std::move(server_socket))
{
}
void start()
{
do_read();
}
private:
void do_read()
{
auto self(shared_from_this()); // shared_ptr instance to this
// Start an asynchronous read.
// This function is used to asynchronously read data from the stream socket.
_session_socket.async_read_some(asio::buffer(_data, max_length),
[this, self](std::error_code error, std::size_t length)
{
if (!error)
{
std::cout << "Data RECEIVED: " << std::endl;
std::cout << _data << std::endl;
do_write(length);
}
});
}
void do_write(std::size_t length)
{
auto self(shared_from_this()); // shared_ptr instance to this
// Start an asynchronous write.
// This function is used to asynchronously write data to the stream socket.
strncpy(_data, "Hi, from the server", max_length);
asio::async_write(_session_socket, asio::buffer(_data, length),
[this, self](std::error_code error, std::size_t /*length*/)
{
if (!error)
{
do_read();
}
});
}
tcp::socket _session_socket;
char _data[max_length];
};
class server
{
public:
server(asio::io_service &io_service, const tcp::endpoint &endpoint)
: _server_socket(io_service),
_server_acceptor(io_service, endpoint)
{
}
void do_accept()
{
// Start an asynchronous accept.
// This function is used to asynchronously accept a new connection into a socket.
_server_acceptor.async_accept(_server_socket,
[this](std::error_code error)
{
// Accept succeeded
if (!error)
{
// Create a session
auto session = std::make_shared<Session>(
std::move(_server_socket));
session->start();
}
// Continue to accept more connections
do_accept();
});
}
private:
tcp::acceptor _server_acceptor;
tcp::socket _server_socket;
};
int main()
{
try
{
asio::io_service io_service; // io_service provides functionality for sockets, connectors, etc
tcp::endpoint endpoint(tcp::v4(), PORT); // create an endpoint using a IP='any' and the specified PORT
server server(io_service, endpoint); // create server on PORT
server.do_accept();
std::cout << "Server started on port: " << PORT << std::endl;
io_service.run();
}
catch (std::exception &e)
{
std::cerr << "Exception: " << e.what() << "\n"; // Print error
}
return 0;
}
client.cpp - Almost same code as seen here
#define ASIO_STANDALONE
#include <iostream>
#include <asio.hpp>
using asio::ip::tcp;
int main(int argc, char *argv[])
{
asio::io_service io_service;
tcp::socket socket(io_service);
tcp::resolver resolver(io_service);
// Connect
asio::connect(socket, resolver.resolve({"localhost", "15562"}));
for (int i = 0; i < 10; ++i)
{
std::cout << "Enter message to sent to server:" << std::endl;
char client_message[2048];
std::cin.getline(client_message, 2048);
// Send message to server
asio::write(socket, asio::buffer(client_message, 2048));
char server_message[2048];
// Read message from server
asio::read(socket, asio::buffer(server_message, 2048));
std::cout << "Reply is: " << std::endl;
std::cout << server_message << std::endl;
}
return 0;
}
std::cin.getline(client_message, 2048);
Gets a line of input from the user. In this case "1". This will be politely NULL terminated, but without looking you have no idea how much data was actually provided by the user.
asio::write(socket, asio::buffer(client_message, 2048))
Writes the entire 2048 bytes of client_message into the socket. So in goes '1', a NULL, and 2046 more bytes of unknown contents. All of this will be read by the server.
How this causes at least some of the OP's deviant behaviour:
Some of that 2048 bytes of data wind up in one packet. The rest winds up in another packet. The server reads the first packet and processes it. A few milliseconds later the second packet arrives. The first packet as a 1 and null in it, so cout prints 1 and discards the rest because that's what cout does with char *. The second packet has god-knows-what in it. cout will try to interpret it the way it would any other null terminated string. It will print random garbage until it finds a null, the cows come home, or the program crashes.
This needs to be fixed. Quick hack fix:
std::cin.getline(client_message, 2048);
size_t len = strlen(client_message)
asio::write(socket, asio::buffer(client_message, len+1))
Now only the user's input string and a null will be sent. Consider using std::string and std::getline instead of the char array and iostream::getline
But because many messages may be put into the same packet by the TCP stack, you need to know when a message begins and ends. You can't count on one message one packet.
Typical solutions are
read-a-byte read-a-byte read-a-byte-byte-byte until a protpcol-defined terminator is reached. Slow and painful, but sometimes the best solution. Buffering packets in a std::stringstream while waiting for a terminator that may not have arrived yet can ease this pain.
I prefer prepending the length of the message to the message in a fixed size data type. Receiver reads for a the size of the length, then reads length bytes. Say you send an unsigned 32 bit length field. Receiver reads 32 bits to get the length, then reads length bytes for the message. When sending binary numbers over a network watch out for different endian among receivers. To avoid differing endians, make sure your protocol specifies what endian to use. Industry standard is to always send in big endian, but most processors you are likely to encounter these days are little endian. You make the call.
I'm fuzzy on the specifics of asio::buffer. You want to get the length (as a uint32_t) and the message (as a std::string) into the output stream. This might be as simple as
std::getline(cin, client_message);
uint32_t len = client_message.length();
asio::write(socket, asio::buffer(len, sizeof(len)))
asio::write(socket, asio::buffer(client_message.c_str(), len+1))
There may be a better way built into asio, and the above may be total craptastic nonsense. Please consult an asio expert on how to optimize this.
The receiver reads the message something like:
uint32_t len;
asio::read(socket, asio::buffer(len, sizeof(len)));
asio::read(socket, asio::buffer(server_message, len));
std::cout << "Reply is: " << std::endl;
std::cout << server_message << std::endl;
The asynch version should be somewhat similar.
Looking for a boost::asio (and with himself boost) decided to write asynchronous server. To store incoming data I use boost::asio::streambuf.
Here I have a problem. When I receive a second message from the client and subsequent I see that in the buffer contains a data from previous messages.
Although I call Consume method at the input buffer. What's wrong?
class tcp_connection
// Using shared_ptr and enable_shared_from_this
// because we want to keep the tcp_connection object alive
// as long as there is an operation that refers to it.
: public boost::enable_shared_from_this<tcp_connection>
{
...
boost::asio::streambuf receive_buffer;
boost::asio::io_service::strand strand;
}
...
void tcp_connection::receive()
{
// Read the response status line. The response_ streambuf will
// automatically grow to accommodate the entire line. The growth may be
// limited by passing a maximum size to the streambuf constructor.
boost::asio::async_read_until(m_socket, receive_buffer, "\r\n",
strand.wrap(boost::bind(&tcp_connection::handle_receive, shared_from_this()/*this*/,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
}
void tcp_connection::handle_receive(const boost::system::error_code& error,
std::size_t bytes_transferred)
{
if (!error)
{
// process the data
/* boost::asio::async_read_until remarks
After a successful async_read_until operation,
the streambuf may contain additional data beyond the delimiter.
An application will typically leave that data in the streambuf for a
subsequent async_read_until operation to examine.
*/
/* didn't work
std::istream is(&receive_buffer);
std::string line;
std::getline(is, line);
*/
// clean up incomming buffer but it didn't work
receive_buffer.consume(bytes_transferred);
receive();
}
else if (error != boost::asio::error::operation_aborted)
{
std::cout << "Client Disconnected\n";
m_connection_manager.remove(shared_from_this());
}
}
Either using a std::istream and reading from it, such as by std::getline(), or explicitly invoking boost::asio::streambuf::consume(n), will remove data from the input sequence.
If the application is performing either of these and subsequent read_until() operations results in duplicated data in receive_buffer's input sequence, then the duplicated data is likely originating from the remote peer. If the remote peer is writing to the socket and directly using a streambuf's input sequence, then the remote peer needs to explicitly invoke consume() after each successful write operation.
As noted in the documentation, successful read_until() operations may contain additional data beyond the delimiter, including additional delimiters. For instance, if "a#b#" is written to a socket, a read_until() operation using '#' as a delimiter may read and commit "a#b#" to the streambuf's input sequence. However, the operation will indicate that the amount of bytes transferred is that up to and including the first delimiter. Thus, bytes_transferred would be 2 and streambuf.size() would be 4. After 2 bytes have been consumed, the streambuf's input sequence would contain "b#", and a subsequent call to read_until() will return immediately, as the streambuf already contains the delimiter.
Here is a complete example demonstrating streambuf usage for reading and writing, and how the input sequence is consumed:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
// This example is not interested in the handlers, so provide a noop function
// that will be passed to bind to meet the handler concept requirements.
void noop() {}
std::string make_string(boost::asio::streambuf& streambuf)
{
return {buffers_begin(streambuf.data()),
buffers_end(streambuf.data())};
}
int main()
{
using boost::asio::ip::tcp;
boost::asio::io_service io_service;
// Create all I/O objects.
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 0));
tcp::socket server_socket(io_service);
tcp::socket client_socket(io_service);
// Connect client and server sockets.
acceptor.async_accept(server_socket, boost::bind(&noop));
client_socket.async_connect(acceptor.local_endpoint(), boost::bind(&noop));
io_service.run();
// Write to server.
boost::asio::streambuf write_buffer;
std::ostream output(&write_buffer);
output << "a#"
"b#";
write(server_socket, write_buffer.data());
std::cout << "Wrote: " << make_string(write_buffer) << std::endl;
assert(write_buffer.size() == 4); // Data not consumed.
// Read from the client.
boost::asio::streambuf read_buffer;
// Demonstrate consuming via istream.
{
std::cout << "Read" << std::endl;
auto bytes_transferred = read_until(client_socket, read_buffer, '#');
// Verify that the entire write_buffer (data pass the first delimiter) was
// read into read_buffer.
auto initial_size = read_buffer.size();
assert(initial_size == write_buffer.size());
// Read from the streambuf.
std::cout << "Read buffer contains: " << make_string(read_buffer)
<< std::endl;
std::istream input(&read_buffer);
std::string line;
getline(input, line, '#'); // Consumes from the streambuf.
assert("a" == line); // Note getline discards delimiter.
std::cout << "Read consumed: " << line << "#" << std::endl;
assert(read_buffer.size() == initial_size - bytes_transferred);
}
// Write an additional message to the server, but only consume 'a#'
// from write buffer. The buffer will contain 'b#c#'.
write_buffer.consume(2);
std::cout << "Consumed write buffer, it now contains: " <<
make_string(write_buffer) << std::endl;
assert(write_buffer.size() == 2);
output << "c#";
assert(write_buffer.size() == 4);
write(server_socket, write_buffer.data());
std::cout << "Wrote: " << make_string(write_buffer) << std::endl;
// Demonstrate explicitly consuming via the streambuf.
{
std::cout << "Read" << std::endl;
auto initial_size = read_buffer.size();
auto bytes_transferred = read_until(client_socket, read_buffer, '#');
// Verify that the read operation did not attempt to read data from
// the socket, as the streambuf already contained the delimiter.
assert(initial_size == read_buffer.size());
// Read from the streambuf.
std::cout << "Read buffer contains: " << make_string(read_buffer)
<< std::endl;
std::string line(
boost::asio::buffers_begin(read_buffer.data()),
boost::asio::buffers_begin(read_buffer.data()) + bytes_transferred);
assert("b#" == line);
assert(read_buffer.size() == initial_size); // Nothing consumed.
read_buffer.consume(bytes_transferred); // Explicitly consume.
std::cout << "Read consumed: " << line << std::endl;
assert(read_buffer.size() == 0);
}
// Read again.
{
std::cout << "Read" << std::endl;
read_until(client_socket, read_buffer, '#');
// Read from the streambuf.
std::cout << "Read buffer contains: " << make_string(read_buffer)
<< std::endl;
std::istream input(&read_buffer);
std::string line;
getline(input, line, '#'); // Consumes from the streambuf.
assert("b" == line); // Note "b" is expected and not "c".
std::cout << "Read consumed: " << line << "#" << std::endl;
std::cout << "Read buffer contains: " << make_string(read_buffer)
<< std::endl;
}
}
Output:
Wrote: a#b#
Read
Read buffer contains: a#b#
Read consumed: a#
Consumed write buffer, it now contains: b#
Wrote: b#c#
Read
Read buffer contains: b#
Read consumed: b#
Read
Read buffer contains: b#c#
Read consumed: b#
Read buffer contains: c#
I get a compile error, additionally I cannot boost::asio::read buf without giving it array elements.
std::string eport::read_data (void)
{
io_service io; // create the I/O service that talks to the serial device
serial_port port (io, PORT); // create the serial device, note it takes the io service and the port name
error_code ec; // address used for error checking
std::string buf [100]; // data with crc on end
try
{
read (port, buffer (buf), ec);
std::cout << "eport::read: result: " << buf << std::endl;
}
catch (error_code &ec)
{
std::cout << "eport::read: ERROR: " << ec << std::endl;
return "error";
}
std::cout << "eport::read: SUCCESS" << std::endl;
return buf;
The error:
eport.cc:83:9: error: could not convert ‘(std::string*)(& buf)’ from ‘std::string* {aka std::basic_string<char>*}’ to ‘std::string {aka std::basic_string<char>}’
Does the function need to be cast as const char* ? I am not sure what is wrong. Any help is appreciated, thank you.
UPDATED CODE
This is my code. I hope it can help someone because asio lacks good examples on the web. I know my write function could be written better, and this code has not been tested so I'm not sure if I'm doing this right or not. Thanks.
#include "../include/main.H"
#include <boost/asio.hpp> // asynchronous input/output
#include <boost/crc.hpp> // cyclic redundancy code (for data checking)
using namespace::boost::system;
using namespace::boost::asio;
const char *PORT = "/dev/ttyS0";
// serial port communication setup
serial_port_base::baud_rate BAUD (9600); // what baud rate do we communicate at (default is 9600)
serial_port_base::character_size C_SIZE (8); // how big is each "packet" of data (default is 8 bits)
serial_port_base::flow_control FLOW (serial_port_base::flow_control::none); // what flow control is used (default is none)
serial_port_base::parity PARITY (serial_port_base::parity::none); // what parity is used (default is none)
serial_port_base::stop_bits STOP (serial_port_base::stop_bits::one); // how many stop bits are used (default is one)
int eport::initialize (void)
{
io_service io; // create the I/O service that talks to the serial device
serial_port port (io, PORT); // create the serial device, note it takes the io service and the port name
// set serial port options
port.set_option (BAUD);
port.set_option (C_SIZE);
port.set_option (FLOW);
port.set_option (PARITY);
port.set_option (STOP);
return 0;
}
int eport::write_data (std::string data)
{
io_service io; // create the I/O service that talks to the serial device
serial_port port (io, PORT); // create the serial device, note it takes the io service and the port name
error_code ec; // address used for error checking
boost::crc_32_type crcresult; // used for communication checking
char buf [1024]; // buffer to hold data
int crc; // holds crc value
std::ostringstream convert; // used to convert int to string
std::string data_crc; // data with crc on end
std::stringstream ss; // used to add strings
strncpy (buf, data.c_str(), sizeof(buf)); // put data into buffer
buf [sizeof(buf) - 1] = 0; // make sure the last element has a null
crcresult.process_bytes (buf, sizeof(buf)); // get crc value from buffer contents
crc = crcresult.checksum(); // put crc value into integer
convert << crc; // convert integer to string
ss << data << convert.str (); // add crc string to data string
data_crc = ss.str (); // data string with crc appended to be used in reading / writing
std::cout << "eport::write: data with crc: " << data_crc << std::endl;
std::cout << "eport::write: writing: " << data_crc << std::endl;
write (port, buffer (data_crc, sizeof(data_crc)), ec); // write data with crc to serial device
if (ec) // if error code is true, print and return
{
std::cout << "eport::write: ERROR: " << ec << std::endl;
return -1;
}
std::cout << "eport::write: SUCCESS" << std::endl;
return crc;
}
std::string eport::read_data (void)
{
io_service io; // create the I/O service that talks to the serial device
serial_port port (io, PORT); // create the serial device, note it takes the io service and the port name
error_code ec; // address used for error checking
streambuf sb; // asio stream buffer to hold read data
std::string buf; // read buffer will be put into this string
size_t transferred = read (port, sb, ec); // read data from serial device
buf.resize (transferred); // resize the string to the read data size
sb.sgetn (&buf[0], buf.size ()); // stores characters from the stream to the array
std::cout << "eport::read: result: " << buf << std::endl;
if (ec)
{
std::cout << "eport::read: ERROR: " << ec << std::endl;
return "error";
}
std::cout << "eport::read: SUCCESS" << std::endl;
return buf;
}
The most generic way would be use a asio::streambuf
streambuf sb;
size_t transferred = read (port, sb, ec);
According to the docs:
This function is used to read a certain number of bytes of data from a stream. The call will block until one of the following conditions is true:
The supplied buffer is full (that is, it has reached maximum size).
An error occurred.
This operation is implemented in terms of zero or more calls to the stream's read_some function.
Then, copy it to a string:
std::string buf;
buf.resize(transferred);
sb.sgetn(&buf[0], buf.size());
Alternatively, preallocate a buffer of the expected size:
std::string buf(100u, '\0');
size_t transferred = read (port, buffer(buf), ec);
buf.resize(transferred);
For more complicated scenarios, use read_until:
streambuf sb;
size_t transferred = read_until(port, sb, "\r\n", ec);
This will read until "\r\n" was encountered (note: may read more than that, but won't invoke read_some again after seeing the delimiter).
Even more complicated stop conditions could use the overload that takes a MatchCondition functor.
Note on exception handling
If you pass ec to receive the error_code there will be no exceptions thrown
buf is an array of std::string. You should change your prototype or return just one string. buf[0] for example.
Most possibly what you want is:
std::string buf; // No [100]
There are issues with your code that you will need to answer, more specifically, how do you know the number of characters that will be sent to your read function?
However, the general answer to your question is to use a character array, and then return this as the std::string:
std::string eport::read_data (void)
{
io_service io; // create the I/O service that talks to the serial device
serial_port port (io, PORT); // create the serial device, note it takes the io service and the port name
error_code ec; // address used for error checking
char buf [100]; // data with crc on end
try
{
read (port, buf, ec);
std::cout << "eport::read: result: " << buf << std::endl;
}
catch (error_code &ec)
{
std::cout << "eport::read: ERROR: " << ec << std::endl;
return "error";
}
std::cout << "eport::read: SUCCESS" << std::endl;
return buf;
}
The std::string constructor will take care of copying the buf at the end to a std::string.
Now, if there is a way to determine the number of characters read, then the function has to be written differently. Most read functions have a parameter specifying the maximum number of characters to read, and somewhere it is returned the number of characters that are read.
Assuming you could rewrite (or call) a different read function that has both of these properties, the code would look like this:
std::string eport::read_data (void)
{
io_service io; // create the I/O service that talks to the serial device
serial_port port (io, PORT); // create the serial device, note it takes the io service and the port name
error_code ec; // address used for error checking
char buf [100]; // data with crc on end
int numCharsRead = 0;
try
{
numCharsRead = read2 (port, buf, 100, ec);
std::cout << "eport::read: result: " << buf << std::endl;
}
catch (error_code &ec)
{
std::cout << "eport::read: ERROR: " << ec << std::endl;
return "error";
}
std::cout << "eport::read: SUCCESS" << std::endl;
return std::string(buf, numCharsRead);
}
Note the difference in the return. A std::string is constructed from the character array, but only up to numCharsRead characters.