Working with boost::asio::streambuf - c++

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#

Related

boost read_until stops at white space

I am trying to setup a tcp socketing server with boost. For some reason the function read_until seems to be stopping at white space rather then going all the way until the delimiter.
for example:
sent
┼N▀]»ü rx}q╠Cä≥è┘Y\║ï2æ╨╬ΣfV╠παÇ/S┬0è3à ╫VR∞{εoÆ?LeN≡╬.lÖnÖ1⌡&âm&ù╫ä╛'°≈L▀_ °çF¿P»2ß|╪+96#3kα≥
╟¬─╣╩í▄¢hú╤fûº╢5~AcbF┌Zd╒∞?╓)a.ƒ¿B■αZº=■uΣ╔nÜ┌╬▌╝>┌iE┌y≈ÿ≤┴Kå ²å£∩¢R>╒S(y╙cPjA▀▀Z2O╓? ÆÉ#τß╢ªy╗▒*Γ▓σ&K₧#╦╩∙⌠%ßΩ-x*Ü╞7ε_█zâ╡C
╧╩║╗Q■═TM╠<æ┤päi^▓'àiUóα<«3Çÿ ─╗E Σ]ππa╒εk»╣╕(╔╡╙ä╝y≡╥¡╠▌╪┼¡Ö
a|MC├₧\y╚üßσ√⌡ÿ±2<æq}ÿ┌Mzçα∩òΣÆ{end}
recived:
┼N▀]»ü
My code that reads from the socket is
string read_(tcp::socket& socket, CryptoPP::RSA::PrivateKey *privateKey) {
boost::asio::streambuf buf;
boost::asio::read_until(socket, buf, "{end}");
string data = boost::asio::buffer_cast<const char*>(buf.data());
std::cout << "recived:\n" << data << std::endl;
return data
}
solution:
string read_(tcp::socket& socket, CryptoPP::RSA::PrivateKey *privateKey) {
boost::asio::streambuf buf;
boost::asio::read_until(socket, buf, "{end}");
streambuf::const_buffers_type buf2 = buf.data();
string data(buffers_begin(buf2), buffers_begin(buf2) + buf.size());
std::cout << "recived:\n" << data << std::endl;
return data
}
You should output the characters in a safe way, e.g. by printing each byte as 2 hex digits:
auto read_(tcp::socket& socket, CryptoPP::RSA::PrivateKey * /*privateKey*/) {
std::vector<uint8_t> data;
boost::asio::read_until(
socket,
boost::asio::dynamic_buffer(data),
"{end}");
std::cout << "received:\n";
for (int ch : data) {
std::cout << " " << std::hex << std::setw(2) << std::setfill('0') << ch;
}
return data;
}
Note that I elected to:
omit the intermediate streambuf
used the opportunity to demonstrate that you can easily use more apt data structures (std::vector<uint8_t>), although you can, of course, replace that with std::string

Boost::asio::streambuf consume() doesn't empty the buffer

I have following implementation of a function reading serialized data.
The only problem is that buffer to which the data is written doesn't seem to overwrite the data.
Instead after each function call, buffer appends new data.
I read about consume() which I belive would make it work, but calling it doesn't empty the buffer.
Code below:
void Client::read_msg() {
boost::asio::async_read_until(socket, stream_buf, "\n", [this](boost::system::error_code ec, std::size_t) {
if (!ec) {
std::istream is(&stream_buf);
std::getline(is, read_msg_string);
ss << read_msg_string;
cereal::BinaryInputArchive iarchive(ss);
iarchive(txt);
std::cerr << txt.header << " " << txt.body;
stream_buf.consume(stream_buf.size());
this->read_msg();
} else {
socket.close();
}
});
}
A few ideas:
Reading a binary archive with a single getline is definitely a bad idea (you would never read past a byte having 0x0a (linefeed)).
close streams/archives before modifying the underlying buffers/streams
use the transferred size or keep buffer_size from before the read operations (I don't think this should matter, but it's one variable to eliminate)
So, for a start there would be
std::stringstream ss;
ss << &stream_buf;
{
cereal::BinaryInputArchive iarchive(ss);
iarchive(txt);
std::cerr << txt.header << " " << txt.body;
}
But I think it could just be
{
std::istream is(&stream_buf);
cereal::BinaryInputArchive iarchive(is);
iarchive(txt);
std::cerr << txt.header << " " << txt.body;
}
As far as I know, the streambuf operations shown will already call consume() as required.
If you expect trailing data to be present, you probably will not want to consume it, but rather close the socket (if you blindly ignore trailing data, the conversation partner will end up confused about what you have received)

Read until a string delimiter in boost::asio::streambuf

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")
];

Asio: Is there an automatically resizable buffer for receiving input?

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

C++ , return string from function; boost::asio read / write

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.