Boost::Asio::Read is not populating buffer - c++

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;

Related

Asio two way communication with peristence socket

I have this requirement where my app have to connect to another app via sockets and will have to maintain persistent connection for quiet long time. My app will be a TCP client and the other is a TCP server. My app will send commands and the server will respond accordingly.
The problem am facing right now is how to read the whole data from server a string and return for app which will issue the next command. Reading synchronously (with asio::read) looked like a good option up until I observed socket hanging up until I terminate the server. Looking at the documentation I found that the library is correctly working.
his 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:
1. The supplied buffers are full. That is, the bytes transferred is equal to the sum of the buffer sizes.
2. An error occurred.
The problem is I don't know correct buffer size as the response from the server varies. So If I put a too small buffer it returns fine but missing some data. If I put too big it will hang forever until server quits.
So I thought I would do the async reading. It works only once and I don't know how to make it fetch data until whole data it read.
here is the relevant async code
#define ASIO_STANDALONE 1
#include <iostream>
#include <asio.hpp>
int main()
{
asio::io_context context;
size_t reply_length;
size_t length = 1024;
std::vector<char> buffer;
//create socket
asio::ip::tcp::socket socket(context);
socket.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 8088));
std::string dataOut = "list --files"; //some command to write
std::error_code error;
asio::write(socket, asio::buffer(dataOut), error);
if (!error)
{
std::cout << "Receiving...!" << std::endl;
buffer.resize(length);
asio::async_read(socket, asio::buffer(buffer), [&buffer, &context](const asio::error_code &ec, std::size_t bytes_transferred) {
std::copy(buffer.begin(), buffer.end(), std::ostream_iterator<char>(std::cout, ""));
std::cout << "\nRead total of:" << bytes_transferred << "\n";
context.run();
});
}
else
{
std::cout << "send failed: " << error.message() << std::endl;
}
context.run();
}
Searching didn't help much solving my issue.
So my question is, how can I read all the data in a persistent socket with asio? Am not using boost.
You need to loop async_read calls. If you don't want your client to hang on read operation you can define the smallest possible buffer i.e. 1 byte.
Define function which takes socket, buffer and two additional parameters according to async_read's handler signature, and this function calls itself with async_read to make the loop of async_read calls - it reads until some error occures:
void onRead (
asio::ip::tcp::socket& socket,
std::array<char,1>& buf,
const system::error_code& ec,
std::size_t bytes)
{
if (ec)
{
if (ec == asio::error::eof && bytes == 1)
std::cout << buf[0];
return;
}
std::cout << buf[0];
asio::async_read(socket,asio::buffer(buf),
std::bind(onRead, std::ref(socket), std::ref(buf),
std::placeholders::_1, // error code
std::placeholders::_2)); // transferred bytes
}
and the changes in main:
std::array<char,1> buf;
asio::write(socket, asio::buffer(dataOut), error);
if (!error)
{
std::cout << "Receiving...!" << std::endl;
asio::async_read(socket, asio::buffer(buf),
std::bind(onRead, std::ref(socket), std::ref(buf),
std::placeholders::_1,
std::placeholders::_2));
context.run();
}
else
{
std::cout << "send failed: " << error.message() << std::endl;
}
(I am using Boost, so you should replace system::error_code on asio::error_code).

Boost.Asio: Reading input from SSL-Socket results in incomplete data

I have a class called NIUserSession which handles a SSL-encrypted connection with a client. This is it, although I removed everything which is not relevant for my question:
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;
class NIUserSession
{
public:
/*....*/
void readUntil(std::string until);
/*....*/
private:
/*....*/
void readHandler(const boost::system::error_code& error, std::size_t bytes_transferred);
ssl_socket socket_;
boost::asio::streambuf readBuffer;
/*....*/
};
void NIUserSession::readUntil(std::string until)
{
boost::asio::async_read_until(this->socket_, this->readBuffer, until, boost::bind(&NIUserSession::readHandler,
this,
boost::asio::placeholders::error(),
boost::asio::placeholders::bytes_transferred()));
}
void NIUserSession::readHandler(const boost::system::error_code &error, std::size_t bytes_transferred)
{
if(!error)
{
std::cout << "Bytes transfered: " << bytes_transferred << std::endl;
this->readBuffer.commit(bytes_transferred);
std::istream istrm(&this->readBuffer);
std::string message;
istrm >> message;
std::cout << "Message: " << message << std::endl;
} else {
// ...
}
}
Now a client connects, ssl handshake, etc., and then this line is executed from within NIUserSession:
this->readUntil("<EOF>");
The server should now wait until the client sends a message and ends it with <EOF>.
From my client application, I send the following (JSON) data:
The server outputs:
Bytes transfered: 169
Message: {
As you can see, 169 bytes are transfered but only 1 character appears in the output. I think I am doing something wrong with the boost::asio::streambuf (I never used it before).
This line:
istrm >> message;
reads a string delimited by whitespace from the stream. That first "{" is a string delimited by whitespace. You just need to keep reading from istrm.
Instead of using operator>>, you can copy the indicated number of bytes to a string by using boost::asio::buffers_begin() to get a random access iterator, and then constructing the string with two iterators. That would look something like this (untested):
const auto bufferIterator = boost::asio::buffers_begin(this->readBuffer.data());
const std::string message(bufferIterator, bufferIterator + bytes_transferred);
this->readBuffer.consume(bytes_transferred);

Websockets with c++ asio library weird behavior

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.

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.

Boost::asio::async_read doesn't stop on condition

I have some issue with boost::asio::async_read
Here's my code
void TCPConnection::listenForRead() {
boost::asio::async_read(m_socket,
boost::asio::buffer(m_inbound_data),
boost::asio::transfer_at_least(64),
boost::bind(&TCPConnection::handle_read,
shared_from_this(),
boost::asio::placeholders::error)
);
}
And Here's the handler :
void TCPConnection::handle_read(const boost::system::error_code& error) {
if (error) {
std::cout << "Error read: " << error.category().name() << " -- " << error.value() << std::endl;
} else {
std::string archive_data(&m_inbound_data[0], m_inbound_data.size());
std::cout << "Message received: " << archive_data << std::endl;
listenForRead();
}
}
With
std::vector<char> m_inbound_data;
I get an infinite loop on the console when a client connect:
"Message received: " //no trace of message
If i print the data length, it is always at 0.
I connect with : telnet localhost 4242
Anyone know why ? should it not wait for at least 64 char ?
Boost.Asio will never resize your buffer.
When you create a buffer from an std::vector<char>, the size of the buffer is the size of the vector.
If you don't give it a size, it will be a zero-length buffer.
The transfer_at_least functor returns true if either at least N bytes are in the buffer or the buffer is full. In the case of a zero length buffer, it's always full, so it always returns true.
To go along with dauphic's answer:
You may initialize a char array like you did:
char data[64]
Or if you still want to use a vector you can initialize the vector to a certain size:
std::vector<char> data(64)
or
std::vector<char> data;
data.resize(64);