Boost asynchronous read and write weird data from streambuffer - c++

I'm using boost to asynchronously read and write to my microcontroller. I have my microcontroller rigged so that it reads the data sent by the asynchronous write and echoes it back to the computer, where the computer reads it via an asynchronous read on a single thread. I'm sending over "15" to the microcontroller. Every first send after plugging the microcontroller in it works well, but after this it will sporadically "read" from the serial port "f" and "?f15". Whenever f or ?f15 is sent over, 7 bytes are transferred in the callback, which makes very little sense to me, since f is just a single ascii value. Here is my clientside Serial port wrapper code:
void Serial::async_write(std::string string){
std::cout << "String size:" << string.size() << std::endl;
// char stringToChar[string.size() + 1];
// strcpy(stringToChar, string.c_str());
// this->async_write(stringToChar);
boost::asio::async_write(*port_, boost::asio::buffer(string, string.length()), boost::bind(&Serial::async_write_handler, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void Serial::async_write_buffer(std::vector<char> data){
int num = data.size();
std::cout << num << std::endl;
boost::asio::mutable_buffer buf(&data, data.size());
boost::asio::async_write(*port_, boost::asio::buffer(data, data.size()), boost::bind(&Serial::async_write_handler, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void Serial::async_write_handler(const boost::system::error_code &e, std::size_t bytes_written){
std::cout << "Data written" << std::endl;
//b_->consume(bytes_written);
}
void Serial::async_read_handler(const boost::system::error_code &e, std::size_t bytes_read){
if(!(*e)){
std::cout << "bytes read in async read handler:" << bytes_read << std::endl;
if(bytes_read > 0){
b_->commit(bytes_read);
std::istream* instream = new std::istream(b_);
std::string streamtostring;
*instream >> streamtostring;
std::cout << "size of input buffer:" << b_->size() << std::endl;
std::cout << "Read: " <<std::endl;
b_->consume(bytes_read);
std::cout << streamtostring << std::endl;
}
else{
std::cout << "No bytes read" << std::endl;
}
}
else{
std::cout << "Error occurred!" << std::endl;
std::cerr << e.message() << std::endl;
}
}
void Serial::async_read_until(std::string delim){
boost::system::error_code e;
boost::asio::async_read_until(*port_, *b_, delim, boost::bind(&Serial::async_read_handler, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
and here is the code that calls it in main.cpp:
int main(){
boost::asio::io_service io;
Serial::Serial serial(PORT, &io, 9600);
if(!serial.is_open()){
serial.open(PORT);
}
std::string s = "15";
serial.async_write(s);
serial.async_read_until("\n");
// const char arr[] = {'2', '5', '5'};
// serial.async_write(arr);
// std::string s = "50089q503320232500202";
// std::vector<char> data(s.begin(), s.end());
// serial.async_write_buffer(data);
io.run();
}
Now on the microcontroller side, I have it place each of the incoming data bytes into a stackArray of chars, where they are then popped out one by one into a char array that is 1 more character long than that of the stack array. Since the asynchronous read reads until a newline, I insert a newline at the very end of the character array. I then send it off across the stream.
#include <StackArray.h>
StackArray<int> binary;
int ledPin = 13;
int numberOfExecs = 0;
byte data = 0;
void setup() {
Serial.begin(9600);
//binary.setPrinter(Serial);
pinMode(ledPin, OUTPUT);
}
void blink(int times, int duration){
for(int i = 0; i < times; i++){
digitalWrite(ledPin, HIGH);
delay(duration);
digitalWrite(ledPin, LOW);
delay(duration);
}
}
void loop() {
//get number of bytes waiting in the serial buffer
int bytesWaiting = Serial.available();
//create array of character values
StackArray<char> letterVals;
//Set the printer for the stack array to serial
letterVals.setPrinter(Serial);
//while serial is available, push each byte of data to the stack array
while(Serial.available() > 0){
byte data = Serial.read();
char c = data;
//Serial.println(c);
letterVals.push(c);
// convertToBinary(data, binary);
// printToLED(binary);
}
//Get the number of elements in the stack array
int numElements = letterVals.count();
//indicate how many elements there are on the led
blink(numElements, 1000);
// blink(1, 5000);
//length of array
int len = numElements + 1;
//create array to send back data
char sendback[len];
if(bytesWaiting > 0){
for(int i = len - 2; i >= 0; i--){
//pop each character into its original position
int asciiVal = letterVals.pop();
//blink(asciiVal, 350);
//blink(20, 20);
sendback[i] = asciiVal;
}
}
//set last character to newline
sendback[len - 1] = 10;
//if there are no bytes available to read, send off data
if(bytesWaiting > 0){
Serial.println(sendback);
}
}
Does anyone know why random f's and ?f's keep appearing? Thanks.

This may be the result of the client code invoking undefined behavior. Specifically, the code fails to meet a lifetime requirement for boost::asio::async_write()'s buffers parameter:
[...] ownership of the underlying memory blocks is retained by the caller, which must guarantee that they remain valid until the handler is called.
In both Serial::async_write() and Serial::async_write_buffer(), the underlying memory provided as the buffer is owned by an object whose lifetime ends once the function returns. As neither of these functions makes no guarantee that they will not return until async_write's completion handler has been invoked, the lifetime of the temporary violates a requirement of async_write(), resulting in in undefined behavior.
void Serial::async_write(std::string string)
{
boost::asio::async_write(
...,
boost::asio::buffer(string, string.length()),
...);
} // `string`'s lifetime ends.
void Serial::async_write_buffer(std::vector<char> data)
{
boost::asio::async_write(
...,
boost::asio::buffer(data, data.size()),
...);
} // `data`'s lifetime ends.

Related

Boost::Asio::Read is not populating buffer

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;

Ping(ICMP) multiple destinations parallely Using Boost.asio

I have modified ICMP pinging implementation (https://think-async.com/Asio/asio-1.18.0/src/examples/cpp03/icmp/ping.cpp) to ping multiple destination concurrently instead of sequentially as shown in the example. I tried with std::thread and std::async(along with futures).
But it works as expected only when all the destination are not reachable. Is it not possible to do it concurrently? I had disabled re-pinging on result/timeout in the pinger class
const char* ping(const char* destination)
{
asio::io_context io_context;
pinger p(io_context, destination);
io_context.run();
return p.get();
}
int main()
{
std::future<const char*> a1 = std::async(std::launch::async, ping, "10.2.7.196");
std::future<const char*> a2 = std::async(std::launch::async, ping, "10.2.7.19");
std::cout<<a1.get()<<std::endl;
std::cout<<a2.get()<<std::endl;
}
You wouldn't need std::async¹.
But from the little bit of code you show I can can guess² that your error is returning raw char const*. The chance is considerable that they refer to data inside pinger that - obviously - isn't valid anymore when the future is completed (pinger would be out of scope).
A typical way for this to happen is if you stored output in a std::string member and returned that from get() using .c_str().
A reason why it would "work" for unreachable targets would be if get() simply returned a string literal like return "unreachable", which would NOT have the lifetime problem described above.
Ditching The Crystal Ball
So, imagining a correct way to return results:
Live On Wandbox³
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
namespace asio = boost::asio;
#include "icmp_header.hpp"
#include "ipv4_header.hpp"
using asio::steady_timer;
using asio::ip::icmp;
namespace chrono = asio::chrono;
class pinger {
public:
pinger(asio::io_context& io_context, const char* destination)
: resolver_(io_context), socket_(io_context, icmp::v4()),
timer_(io_context), sequence_number_(0), num_replies_(0) {
destination_ = *resolver_.resolve(icmp::v4(), destination, "").begin();
start_send();
start_receive();
}
std::string get() { auto r = _output.str(); _output.str(""); return r; }
private:
void start_send() {
std::string body("\"Hello!\" from Asio ping.");
// Create an ICMP header for an echo request.
icmp_header echo_request;
echo_request.type(icmp_header::echo_request);
echo_request.code(0);
echo_request.identifier(get_identifier());
echo_request.sequence_number(++sequence_number_);
compute_checksum(echo_request, body.begin(), body.end());
// Encode the request packet.
asio::streambuf request_buffer;
std::ostream os(&request_buffer);
os << echo_request << body;
// Send the request.
time_sent_ = steady_timer::clock_type::now();
socket_.send_to(request_buffer.data(), destination_);
// Wait up to five seconds for a reply.
num_replies_ = 0;
timer_.expires_at(time_sent_ + chrono::seconds(5));
timer_.async_wait(boost::bind(&pinger::handle_timeout, this));
}
void handle_timeout() {
if (num_replies_ == 0)
_output << "Request timed out";
//// Requests must be sent no less than one second apart.
//timer_.expires_at(time_sent_ + chrono::seconds(1));
//timer_.async_wait(boost::bind(&pinger::start_send, this));
}
void start_receive() {
// Discard any data already in the buffer.
reply_buffer_.consume(reply_buffer_.size());
// Wait for a reply. We prepare the buffer to receive up to 64KB.
socket_.async_receive(reply_buffer_.prepare(65536),
boost::bind(&pinger::handle_receive, this,
boost::placeholders::_2));
}
void handle_receive(std::size_t length) {
// The actual number of bytes received is committed to the buffer so
// that we can extract it using a std::istream object.
reply_buffer_.commit(length);
// Decode the reply packet.
std::istream is(&reply_buffer_);
ipv4_header ipv4_hdr;
icmp_header icmp_hdr;
is >> ipv4_hdr >> icmp_hdr;
// We can receive all ICMP packets received by the host, so we need to
// filter out only the echo replies that match the our identifier and
// expected sequence number.
if (is && icmp_hdr.type() == icmp_header::echo_reply &&
icmp_hdr.identifier() == get_identifier() &&
icmp_hdr.sequence_number() == sequence_number_) {
// If this is the first reply, interrupt the five second timeout.
if (num_replies_++ == 0)
timer_.cancel();
// Print out some information about the reply packet.
chrono::steady_clock::time_point now = chrono::steady_clock::now();
chrono::steady_clock::duration elapsed = now - time_sent_;
_output
<< length - ipv4_hdr.header_length() << " bytes from "
<< ipv4_hdr.source_address()
<< ": icmp_seq=" << icmp_hdr.sequence_number()
<< ", ttl=" << ipv4_hdr.time_to_live() << ", time="
<< chrono::duration_cast<chrono::milliseconds>(elapsed).count();
}
//start_receive();
}
static unsigned short get_identifier() {
#if defined(ASIO_WINDOWS)
return static_cast<unsigned short>(::GetCurrentProcessId());
#else
return static_cast<unsigned short>(::getpid());
#endif
}
std::ostringstream _output;
icmp::resolver resolver_;
icmp::endpoint destination_;
icmp::socket socket_;
steady_timer timer_;
unsigned short sequence_number_;
chrono::steady_clock::time_point time_sent_;
asio::streambuf reply_buffer_;
std::size_t num_replies_;
};
std::string ping1(const char* destination) {
asio::io_context io_context;
pinger p(io_context, destination);
io_context.run();
return p.get();
}
#include <list>
#include <iostream>
int main(int argc, char** argv) {
std::list<std::future<std::string> > futures;
for (char const* arg : std::vector(argv+1, argv+argc)) {
futures.push_back(std::async(std::launch::async, ping1, arg));
}
for (auto& f : futures) {
std::cout << f.get() << std::endl;
}
}
As you can see I made the list of destinations command line parameters. Therefore, when I run it like:
sudo ./sotest 127.0.0.{1..100} |& sort | uniq -c
I get this output:
1 32 bytes from 127.0.0.12: icmp_seq=1, ttl=64, time=0
1 32 bytes from 127.0.0.16: icmp_seq=1, ttl=64, time=0
7 32 bytes from 127.0.0.44: icmp_seq=1, ttl=64, time=0
1 32 bytes from 127.0.0.77: icmp_seq=1, ttl=64, time=1
1 32 bytes from 127.0.0.82: icmp_seq=1, ttl=64, time=1
1 32 bytes from 127.0.0.9: icmp_seq=1, ttl=64, time=0
88 Request timed out
I'm not actually sure why so many time out, but the point is correct code now. This code runs and completes UBSan/ASan clean. See below for the fix discovered later, though
Now, Let's Drop The Future
The futures are likely creating a lot of overhead. As is the fact that you have an io_service per ping. Let's do it all on a single one.
#include <list>
#include <iostream>
int main(int argc, char** argv) {
asio::io_context io_context;
std::list<pinger> pingers;
for (char const* arg : std::vector(argv+1, argv+argc)) {
pingers.emplace_back(io_context, arg);
}
io_context.run();
for (auto& p : pingers) {
std::cout << p.get() << std::endl;
}
}
Note that the synchronization point here is io_context.run(), just like before, except now it runs all the pings in one go, on the main thread.
Correcting Cancellation
So, I noticed now why so many pings were misrepresented as unreachable.
The reason is because handle_receive needs to filter out ICMP replies that are not in response to our ping, so if that happens we need to continue start_receive() until we get it:
void start_receive() {
// Discard any data already in the buffer.
reply_buffer_.consume(reply_buffer_.size());
// Wait for a reply. We prepare the buffer to receive up to 64KB.
socket_.async_receive(reply_buffer_.prepare(65536),
boost::bind(&pinger::handle_receive, this,
boost::asio::placeholders::error(),
boost::asio::placeholders::bytes_transferred()));
}
void handle_receive(boost::system::error_code ec, std::size_t length) {
if (ec) {
if (ec == boost::asio::error::operation_aborted) {
_output << "Request timed out";
} else {
_output << "error: " << ec.message();
}
return;
}
// The actual number of bytes received is committed to the buffer so
// that we can extract it using a std::istream object.
reply_buffer_.commit(length);
// Decode the reply packet.
std::istream is(&reply_buffer_);
ipv4_header ipv4_hdr;
icmp_header icmp_hdr;
is >> ipv4_hdr >> icmp_hdr;
// We can receive all ICMP packets received by the host, so we need to
// filter out only the echo replies that match the our identifier and
// expected sequence number.
if (is && icmp_hdr.type() == icmp_header::echo_reply &&
icmp_hdr.identifier() == get_identifier() &&
icmp_hdr.sequence_number() == sequence_number_) {
// If this is the first reply, interrupt the five second timeout.
if (num_replies_++ == 0)
timer_.cancel();
// Print out some information about the reply packet.
chrono::steady_clock::time_point now = chrono::steady_clock::now();
chrono::steady_clock::duration elapsed = now - time_sent_;
_output
<< length - ipv4_hdr.header_length() << " bytes from "
<< ipv4_hdr.source_address()
<< ": icmp_seq=" << icmp_hdr.sequence_number()
<< ", ttl=" << ipv4_hdr.time_to_live() << ", time="
<< chrono::duration_cast<chrono::milliseconds>(elapsed).count();
} else start_receive();
}
Now, handle_timeout can be simplified to:
void handle_timeout() {
if (num_replies_ == 0) {
socket_.cancel(); // _output is set in response to error_code
}
}
In fact, we might simplify to remove num_replies altogether, but I'll leave this as an exorcism for the reader
Full Demo
Live On Wandbox
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
namespace asio = boost::asio;
#include "icmp_header.hpp"
#include "ipv4_header.hpp"
using asio::steady_timer;
using asio::ip::icmp;
namespace chrono = asio::chrono;
class pinger {
public:
pinger(asio::io_context& io_context, const char* destination)
: resolver_(io_context), socket_(io_context, icmp::v4()),
timer_(io_context), sequence_number_(0), num_replies_(0) {
destination_ = *resolver_.resolve(icmp::v4(), destination, "").begin();
start_send();
start_receive();
}
std::string get() { auto r = _output.str(); _output.str(""); return r; }
private:
void start_send() {
std::string body("\"Hello!\" from Asio ping.");
// Create an ICMP header for an echo request.
icmp_header echo_request;
echo_request.type(icmp_header::echo_request);
echo_request.code(0);
echo_request.identifier(get_identifier());
echo_request.sequence_number(++sequence_number_);
compute_checksum(echo_request, body.begin(), body.end());
// Encode the request packet.
asio::streambuf request_buffer;
std::ostream os(&request_buffer);
os << echo_request << body;
// Send the request.
time_sent_ = steady_timer::clock_type::now();
socket_.send_to(request_buffer.data(), destination_);
// Wait up to five seconds for a reply.
num_replies_ = 0;
timer_.expires_at(time_sent_ + chrono::seconds(5));
timer_.async_wait(boost::bind(&pinger::handle_timeout, this));
}
void handle_timeout() {
if (num_replies_ == 0) {
socket_.cancel(); // _output is set in response to error_code
}
}
void start_receive() {
// Discard any data already in the buffer.
reply_buffer_.consume(reply_buffer_.size());
// Wait for a reply. We prepare the buffer to receive up to 64KB.
socket_.async_receive(reply_buffer_.prepare(65536),
boost::bind(&pinger::handle_receive, this,
boost::asio::placeholders::error(),
boost::asio::placeholders::bytes_transferred()));
}
void handle_receive(boost::system::error_code ec, std::size_t length) {
if (ec) {
if (ec == boost::asio::error::operation_aborted) {
_output << "Request timed out";
} else {
_output << "error: " << ec.message();
}
return;
}
// The actual number of bytes received is committed to the buffer so
// that we can extract it using a std::istream object.
reply_buffer_.commit(length);
// Decode the reply packet.
std::istream is(&reply_buffer_);
ipv4_header ipv4_hdr;
icmp_header icmp_hdr;
is >> ipv4_hdr >> icmp_hdr;
// We can receive all ICMP packets received by the host, so we need to
// filter out only the echo replies that match the our identifier and
// expected sequence number.
if (is && icmp_hdr.type() == icmp_header::echo_reply &&
icmp_hdr.identifier() == get_identifier() &&
icmp_hdr.sequence_number() == sequence_number_) {
// If this is the first reply, interrupt the five second timeout.
if (num_replies_++ == 0)
timer_.cancel();
// Print out some information about the reply packet.
chrono::steady_clock::time_point now = chrono::steady_clock::now();
chrono::steady_clock::duration elapsed = now - time_sent_;
_output
<< length - ipv4_hdr.header_length() << " bytes from "
<< ipv4_hdr.source_address()
<< ": icmp_seq=" << icmp_hdr.sequence_number()
<< ", ttl=" << ipv4_hdr.time_to_live() << ", time="
<< chrono::duration_cast<chrono::milliseconds>(elapsed).count();
} else start_receive();
}
static unsigned short get_identifier() {
#if defined(ASIO_WINDOWS)
return static_cast<unsigned short>(::GetCurrentProcessId());
#else
return static_cast<unsigned short>(::getpid());
#endif
}
std::ostringstream _output;
icmp::resolver resolver_;
icmp::endpoint destination_;
icmp::socket socket_;
steady_timer timer_;
unsigned short sequence_number_;
chrono::steady_clock::time_point time_sent_;
asio::streambuf reply_buffer_;
std::size_t num_replies_;
};
#include <list>
#include <iostream>
int main(int argc, char** argv) {
asio::io_context io_context;
std::list<pinger> pingers;
for (char const* arg : std::vector(argv+1, argv+argc)) {
pingers.emplace_back(io_context, arg);
}
io_context.run();
for (auto& p : pingers) {
std::cout << p.get() << std::endl;
}
}
Now the output of e.g. time sudo ./sotest 127.0.0.{1..100} 18.0.0.1 is as expected:
32 bytes from 127.0.0.1: icmp_seq=1, ttl=64, time=8
32 bytes from 127.0.0.2: icmp_seq=1, ttl=64, time=8
32 bytes from 127.0.0.3: icmp_seq=1, ttl=64, time=8
32 bytes from 127.0.0.4: icmp_seq=1, ttl=64, time=8
...
32 bytes from 127.0.0.98: icmp_seq=1, ttl=64, time=0
32 bytes from 127.0.0.99: icmp_seq=1, ttl=64, time=0
32 bytes from 127.0.0.100: icmp_seq=1, ttl=64, time=0
Request timed out
¹ in fact that is rarely/never the right tool
² using my crystal ball
³ obviously we have no permissions to craft ICMP packets, let alone send them on Wandbox

Making my function which calls async_read asynchronous Boost::asio

I am building an networking application, and being a newbie to Boost asio and networking as a whole had this doubt which might be trivial. I have this application which reads from a file and calls apis accordingly. I am reading json (example):
test.json
{
"commands":
[
{
"type":"login",
"Username": 0,
"Password": "kk"
}
]
}
My main program looks like this :
int main() {
ba::io_service ios;
tcp::socket s(ios);
s.connect({{},8080});
IO io;
io.start_read(s);
io.interact(s);
ios.run();
}
void start_read(tcp::socket& socket) {
char buffer_[MAX_LEN];
socket.async_receive(boost::asio::null_buffers(),
[&](const boost::system::error_code& ec, std::size_t bytes_read) {
(void)bytes_read;
if (likely(!ec)) {
boost::system::error_code errc;
int br = 0;
do {
br = socket.receive(boost::asio::buffer(buffer_, MAX_LEN), 0, errc);
if (unlikely(errc)) {
if (unlikely(errc != boost::asio::error::would_block)) {
if (errc != boost::asio::error::eof)
std::cerr << "asio async_receive: error " << errc.value() << " ("
<< errc.message() << ")" << std::endl;
interpret_read(socket,nullptr, -1);
//close(as);
return;
}
break; // EAGAIN
}
if (unlikely(br <= 0)) {
std::cerr << "asio async_receive: error, read " << br << " bytes" << std::endl;
interpret_read(socket,nullptr, br);
//close(as);
return;
}
interpret_read(socket,buffer_, br);
} while (br == (int)MAX_LEN);
} else {
if (socket.is_open())
std::cerr << "asio async_receive: error " << ec.value() << " (" << ec.message() << ")"
<< std::endl;
interpret_read(socket,nullptr, -1);
//close(as);
return;
}
start_read(socket);
});
}
void interpret_read(tcp::socket& s,const char* buf, int len) {
if(len<0)
{
std::cout<<"some error occured in reading"<<"\n";
}
const MessageHeaderOutComp *obj = reinterpret_cast<const MessageHeaderOutComp *>(buf);
int tempId = obj->TemplateID;
//std::cout<<tempId<<"\n";
switch(tempId)
{
case 10019: //login
{
//const UserLoginResponse *obj = reinterpret_cast<const UserLoginResponse *>(buf);
std::cout<<"*********[SERVER]: LOGIN ACKNOWLEDGEMENT RECEIVED************* "<<"\n";
break;
}
}
std::cout << "RX: " << len << " bytes\n";
if(this->input_type==2)
interact(s);
}
void interact(tcp::socket& s)
{
if(this->input_type == -1){
std::cout<<"what type of input you want ? option 1 : test.json / option 2 : manually through command line :";
int temp;
std::cin>>temp;
this->input_type = temp;
}
if(this->input_type==1)
{
//std::cout<<"reading from file\n";
std::ifstream input_file("test.json");
Json::Reader reader;
Json::Value input;
reader.parse(input_file, input);
for(auto i: input["commands"])
{
std::string str = i["type"].asString();
if(str=="login")
this->login_request(s,i);
}
std::cout<<"File read completely!! \n Do you want to continue or exit?: ";
}
}
The sending works fine, the message is sent and the server responds in a correct manner, but what I need to understand is why is the control not going to on_send_completed (which prints sent x bytes). Neither it prints the message [SERVER]: LOGIN ACKNOWLEDGEMENT RECEIVED, I know I am missing something basic or am doing something wrong, please correct me.
login_request function:
void login_request(tcp::socket& socket,Json::Value o) {
/*Some buffer being filled*/
async_write(socket, boost::asio::buffer(&info, sizeof(info)), on_send_completed);
}
Thanks in advance!!
From a cursory scan it looks like you redefined buffer_ that was already a class member (of IO, presumably).
It's hidden by the local in start_read, which is both UB (because the lifetime ends before the async read operation completes) and also makes it so the member _buffer isn't used.
I see a LOT of confusing code though. Why are you doing synchronous reads from within completion handlers?
I think you might be looking for the composed-ooperation reads (boost::asio::async_read and boost::asio::async_until)

recv() char array size

I'm working on implementing a C++ client server chat program to learn more / practice socket programming. I'm using winsockV2.
Briefly,
the client program connects to a server, who stores the client socket in a vector
client program sends messages for the server to distribute to other clients in the vector.
The problem I think I'm running into is that the clients and server are receiving the message and storing it in a char message[256] and if the message is shorter than 256, strange chars are displayed when I std::cout << message; which I'm being told is uninitialized memory. Here's an example of the output:
k:message from client to other client╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠(■o
Is there some way of creating a character array of the size of the received message? i.e
char recvMessage[4096];
int s = recv(socket, recvMessage, sizeof(recvMessage),0);
char recvOutput[strlen(recvMessage)] = recvMessage;
std::cout << recvOutput << std::endl;
Otherwise what is your solution for recv'ing messages which you do not know the length of?
If I'm being a complete idiot, please be kind, I came from PHP. classes are below:
SVR.CPP
See receiveMessages() and distributeMessages() functions
#include "stdafx.h"
#include "svr.h"
svr::svr()
{
//WSA Business I don't understand
WORD wVersionRequested;
WSADATA wsaData;
int err;
/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
/* Tell the user that we could not find a usable */
/* Winsock DLL. */
printf("WSAStartup failed with error: %d\n", err);
}
//End of WSA Business
//get addressSize
addressSize = sizeof(address);
//set address data members
address.sin_family = AF_INET;
address.sin_port = htons(444);
address.sin_addr.s_addr = INADDR_ANY;
//init sListen
sListen = socket(AF_INET, SOCK_STREAM, 0);
bind(sListen, (sockaddr*)&address, addressSize);
}
svr::~svr()
{
}
void svr::start()
{
std::thread newConnThread(&svr::newConnection, this);
newConnThread.join();
}
void svr::receiveMessages(int clientIndex)
{
std::cout << "\tsvr::recv thread started for client index:" << clientIndex << std::endl;
//create char arr
char recvMessage[256];
//forever
while (true)
{
//receive message and input it to recvMessage char arr.
recv(clients[clientIndex], recvMessage, sizeof(recvMessage), 0);
//if message is not null, send out to other clients
if (recvMessage != NULL)
{
std::cout << "\t\tINFO:Received message of length: " << std::strlen(recvMessage) << " size: " << sizeof(recvMessage) << " : " << recvMessage << std::endl;
distributeMessages(recvMessage, clientIndex);
}
}
}
//distributes messages to all clients in vector. called by receiveMessages function, normally in rMessages thread.
void svr::distributeMessages(std::string message, int clientIndex)
{
for (unsigned int i = 0; i < clients.size(); i++)
{
if (clientIndex != i)
{
send(clients[i], message.c_str(), message.length(), 0);
}
else
{
//would have sent to self, not useful.
}
}
}
//accepts new connections and adds sockets to vector.
void svr::newConnection()
{
//mark for accept, unsure of somaxconn value;
listen(sListen, SOMAXCONN);
std::cout << "\tSERVER: awaiting new connections..." << std::endl;
while (true)
{
//accept connection and push on to vector.
clients.push_back(accept(sListen, (sockaddr*)&address, &addressSize));
//responds to new clients.
const char *message = "Hi, you've successfully connected!";
int clientIndex = clients.size() - 1;
int sent = send(clients[clientIndex], message, 33, 0);
//start new receiveMessage thread
std::thread newClient(&svr::receiveMessages, this, clientIndex);
//detach here, let newConn thread operate without depending on receiveMessages
newClient.detach();
}
std::cout << "\tSERVER: no longer listening for new connections" << std::endl;
}
CLI.CPP
See cSend() and cRecv() functions
#include "stdafx.h"
#include "cli.h"
cli::cli(char *ip)
{
//WSA
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
// Use the MAKEWORD(lowbyte,highbyte) macro declared in windef.h
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
std::cout << "WSAStartup failed with the error: " << err;
}
}
//get addressSize
addressSize = sizeof(address);
//set address struct data members
address.sin_family = AF_INET;
address.sin_port = htons(444);
//if ip empty, prompt user;
if (ip == NULL)
{
std::string ipInput;
std::cout << "\n\tConnect to which IP: ";
std::cin >> ipInput;
address.sin_addr.s_addr = inet_addr(ipInput.c_str());
}
else
{
address.sin_addr.s_addr = inet_addr(ip);
}
sock = socket(AF_INET, SOCK_STREAM, 0);
std::cout << "\n\tYour username: ";
std::cin >> uname;
}
cli::~cli()
{
}
void cli::start()
{
try
{
//hold string
char message[33];
std::cout << "\n\tcli::start() called";
int conRet;
//connects to server socket & receives a message, stores in it message variable
conRet = connect(sock, (sockaddr*)&address, (int)addressSize);
recv(sock, message, sizeof(message), 0);
std::cout << "\n\tSERVER: " << message;
//starts threads, pass this for object scope.
std::thread sendThread(&cli::cSend, this);
std::thread recvThread(&cli::cRecv, this);
//this function (start) will return/end when send and recv threads end.
sendThread.join();
recvThread.join();
}
catch (std::exception e)
{
std::cerr << e.what() << std::endl;
}
}
void cli::cSend()
{
std::cout << "\n\tcli::send thread started";
//char arr for sending str;
std::string getLine;
while (true)
{
std::cout << "\n\t" << uname << ":" << std::flush;
//set to "" because i suspected the value remains in the string after a loop.
std::string message = "";
//get input, put it in message
std::getline(std::cin, message);
//get full message
std::string fullMessage = uname + ":" + message;
//get constant int, size of fullMessage
const int charArrSize = fullMessage.length();
std::cout << "\t\tINFO: Sending character array of length: " << charArrSize << " size: " << sizeof(fullMessage.c_str()) << " : " << fullMessage.c_str() << std::endl;
//sends it
send(sock, fullMessage.c_str(), charArrSize, 0);
}
}
void cli::cRecv()
{
std::cout << "\n\tcli::recv thread started";
//initialize arr to 0, will hopefully help avoid the weird chars in the cout
char recvMessage[256]{ '\0' };
while (true)
{
recv(sock, recvMessage, sizeof(recvMessage), 0);
std::cout << "\t\tINFO:Received message of length: " << std::strlen(recvMessage) << " size: " << sizeof(recvMessage) << " : " << recvMessage << std::endl;
std::cout << recvMessage << std::endl;
}
}
what is your solution for recv'ing messages which you do not know the
length of?
recv() tells you the length of the message it received. You don't have to wonder what it is. That's recv()'s return value.
int s = recv(socket, recvMessage, sizeof(recvMessage),0);
See -- there you go. It's right here in front of you. It's s. Of course if there was an error s would be negative and you need to check for that. But, ignoring that little detail, your worries are over: s is the length of your message you just received.
char recvOutput[strlen(recvMessage)] = recvMessage;
That's not going to work. What is strlen() doing here? strlen() computes the size of the string, expecting the string to be an old-fashioned, C-style character string that's terminated by a \0 byte. recv() does not terminate anything it receives with a \0 byte. Instead, it returns the actual character count.
And, besides, this won't work anyway. You can't initialize an array this way.
Your obvious intent here, apparently, is to expect to receive a text string as message. Well, since your language of choice is C++, and you tagged your question as such, the logical conclusion is that you should be using what C++ gives you to deal with text strings: the std::string class:
std::string recvOutput{recvMessage, recvMessage+s};
There you go. Mission accomplished. Since you already known the length of the received message in s, as we've determined before (and after double-checking that s is not negative), you can simply use std::string's existing constructor that initializes the new string given an iterator, or a pointer, to the start and the end of string.
When dealing with low-level operating system interfaces, like sockets, you have no choice but to use primitive data types, like plain char arrays and buffers, because that's the only thing that the operating system understands. But, with the rich set of templates and classes offered by the C++ library, your code should switch to using C++ classes and templates at the first opportunity, in order to be able to use all those resources. As such, as soon as you've determined how big is the text string recv() just came up with, just stuff it into a std::string before figuring out what to do with it.

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.