Do i need boost strand - c++11

I modified echo example but I have issues understanding boost strand.
Do I need or not?
I yes please be kind and give code for.
I searched examples but I can not understand (Im just stupid..)
Thank you
#include <cstdlib>
#include <iostream>
#include <memory>
#include <utility>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include "mingw.thread.h"
#include <boost/bind.hpp>
class session
: public std::enable_shared_from_this<session>
{
public:
session(boost::asio::ip::tcp::socket socket)
: socket_(std::move(socket))
{
}
void start()
{
input_buffer_str = "[2J[H[A\r\n*ServerV8*\r\nSCAN_BADGE:\r\n";
out_msg =input_buffer_str;
do_write(out_msg.length());
}
private:
void do_read()
{
// std::cout << "do_read" <<"" <<std::endl;
auto self(shared_from_this());
//async_read_until for Telnet client
async_read_until(socket_,input_buffer_,'\r',
[this, self](boost::system::error_code ec, std::size_t length)
{
if (!ec)
{
do_write(length);
}
else
{
// std::cout<<ec <<"Disconection?"<<std::endl;
input_buffer_str.clear();
out_msg.clear();
input_buffer_.consume(length);
// Some Cleanup
}
});
}
// async_write back to Telnet client based on received
// will be result on SQL Query
void do_write(std::size_t length)
{
auto self(shared_from_this());
std::istream(&input_buffer_) >> input_buffer_str;
input_buffer_.consume(length);
std::cout << input_buffer_str <<std::endl;
if (input_buffer_str == "c") close();
if (input_buffer_str == "a") out_msg = "test_a\r\n";
if (input_buffer_str == "t")
{
Sleep(30000);
out_msg = "test_t\r\n";
}
if(input_buffer_str.length()>0 && out_msg.length()>0)
{
input_buffer_str.clear();
boost::asio::async_write(socket_, boost::asio::buffer(out_msg, out_msg.length()),
[this, self](boost::system::error_code ec, std::size_t length)
{
if (!ec) do_read();
else
{
out_msg.clear();
input_buffer_.consume(length);
// Some Cleanup
}
out_msg.clear();
});
}else do_read();
}
// close() Telnet client will be when user pushes F2 (Handheld scanner)
void close()
{
std::cout <<"Closing..."<<std::endl;
boost::system::error_code ec;
input_buffer_str.clear();
out_msg.clear();
socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
Sleep(250);
socket_.close(ec);
// delete data_;
if (ec)
{
std::cout<<ec << std::endl;
// An error occurred.
}
std::cout <<"Closed..."<<std::endl;
}
boost::asio::streambuf input_buffer_;
std::string input_buffer_str;
std::string out_msg ;
boost::asio::ip::tcp::socket socket_;
//enum { max_length = 1024 };
//char data_[max_length];
//std::string ClearScreen= "[2J[H[A"; "\r\n*ServerV8*\r\nSCAN_BADGE:\r\n";
};
Copy paste from example
class server
{
public:
server(boost::asio::io_context& io_context, short port)
: acceptor_(io_context, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))
{
do_accept();
}
private:
void do_accept()
{
acceptor_.async_accept(
[this](boost::system::error_code ec, boost::asio::ip::tcp::socket socket)
{
if (!ec)
{
std::make_shared<session>(std::move(socket))->start();
}
do_accept();
});
}
boost::asio::ip::tcp::acceptor acceptor_;
};
added Threads Some queries to sql might take depends on Network
int main(int argc, char* argv[])
{
auto thread_count = std::thread::hardware_concurrency(); // for multi core
boost::thread_group m_Threads;
boost::asio::io_context m_io_context;
server srv(m_io_context, 23);
for(unsigned int i = 0; i < thread_count; ++i)
m_Threads.create_thread( [&](){m_io_context.run();});
m_Threads.join_all();
return 0;
}

Related

Boost Server breaking the messages from the client

If i send messages locally from the same system than the boost server receives messages properly.
When the client is a remote application on other system and sending messages through TCP\IP than randomly some messages break(Line Enter).
Like if the client has sent "THIS IS A MESSAGE" the server will read it as
"THIS IS A ME
SSAGE"
This is the Server class.
#pragma once
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/write.hpp>
#include <iostream>
#include <global.h>
#include <memory>
#include <fstream>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>
#include <queue>
using boost::asio::ip::tcp;
class session
: public std::enable_shared_from_this<session>
{
public:
session(tcp::socket socket)
: socket_(std::move(socket))
{
}
void start()
{
auto self(shared_from_this());
// dispatch not strictly necessary for single-threaded contexts
dispatch(
socket_.get_executor(),
[this, self]
{
do_read();
});
}
private:
void handleCommand()
{
enqueueAnswer();
}
void enqueueAnswer()
{
if (stdqueAnswers.size() == 1)
{
do_write();
}
}
void do_read()
{
auto self(shared_from_this());
socket_.async_read_some(boost::asio::buffer(data_, max_length),
[this, self](boost::system::error_code ec, std::size_t length)
{
if (!ec)
{
if (length > 0) {
// In case the message has a leading 1 than we have to send a answer back to the client.
if (data_[0] == '1') {
std::string stdstrCmd(data_);
stdstrCmd.erase(0, 2);
wavefrontAccess->ReceiveCommandExternalGet(stdstrCmd);
handleCommand();
}
else
{
std::string strData(data_, length);
if(!strData.empty() || strData.find_first_not_of(' ') != std::string::npos)
{
// There's a non-space.
commandsQueue.push(strData); // this is std Queue
}
}
}
do_read();
}
});
}
void do_write()
{
if (stdqueAnswers.empty())
return;
auto self(shared_from_this());
async_write(
socket_,
boost::asio::buffer(stdqueAnswers.front()),
[this, self](boost::system::error_code ec, size_t)
{
if (!ec)
{
stdqueAnswers.pop();
do_write();
}
});
}
tcp::socket socket_;
enum { max_length = 12000 };
char data_[max_length];
};
class server
{
public:
server(boost::asio::io_context& io_context, std::uint16_t port)
: acceptor_{ io_context, tcp::endpoint(tcp::v4(), port) }
{
acceptor_.listen();
do_accept();
}
private:
void do_accept()
{
acceptor_.async_accept(
make_strand(acceptor_.get_executor()),
[this](boost::system::error_code ec, tcp::socket socket)
{
if (!ec)
{
std::make_shared<session>(std::move(socket))->start();
do_accept();
}
});
}
tcp::acceptor acceptor_;
};
Like Ruslan explains, you should not use read_some, but a higher level operation that reads a full "message", as defined by your application level wire protocol.
You clearly already have some protocol (the leading bytes), and we cannot guess what the rest could be. For simplicity, let's assume that a full message ends with a \n character. Here's my simplifying take:
async_read_until(
socket_, boost::asio::dynamic_buffer(data_, max_length), "\n",
[this, self](boost::system::error_code ec, size_t length) {
std::cerr << "async_read_until() " << ec.message() << std::endl;
if (!ec) {
std::string msg = data_.substr(0, length /* - 1*/);
data_.erase(0, length);
if (!msg.empty() && msg.front() == '1') {
// we have to send a answer back to the client
wavefrontAccess->ReceiveCommandExternalGet(msg.substr(2));
handleCommand();
} else {
if (msg.find_first_not_of(' ') != std::string::npos) {
// There's a non-space
commandsQueue.push(msg);
}
}
do_read();
}
});
I've simplified by making your buffer std::string directly. This immediately "solves" the complexity that read_until might obviously read more than a single message.
Uncomment the /* -1 */ to exclude the \n character from the message
With some missing bits mocked up:
Live On Coliru
#include <boost/asio.hpp>
#include <deque>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>
#include <queue>
using boost::asio::ip::tcp;
class session : public std::enable_shared_from_this<session> {
public:
session(tcp::socket socket) : socket_(std::move(socket)) {}
void start() {
auto self(shared_from_this());
std::cerr << "start() " << socket_.remote_endpoint() << std::endl;
dispatch(socket_.get_executor(), [this, self] { do_read(); });
}
private:
void handleCommand() { enqueueAnswer(); }
void enqueueAnswer() {
if (stdqueAnswers.size() == 1) {
do_write();
}
}
void do_read() {
auto self(shared_from_this());
async_read_until(
socket_, boost::asio::dynamic_buffer(data_, max_length), "\n",
[this, self](boost::system::error_code ec, size_t length) {
std::cerr << "async_read_until() " << ec.message() << std::endl;
if (!ec) {
std::string msg = data_.substr(0, length /* - 1*/);
data_.erase(0, length);
if (!msg.empty() && msg.front() == '1') {
// we have to send a answer back to the client
wavefrontAccess->ReceiveCommandExternalGet(msg.substr(2));
handleCommand();
} else {
if (msg.find_first_not_of(' ') != std::string::npos) {
// There's a non-space
commandsQueue.push(msg);
}
}
do_read();
}
});
}
void do_write() {
if (stdqueAnswers.empty())
return;
auto self(shared_from_this());
async_write( //
socket_, boost::asio::buffer(stdqueAnswers.front()),
[this, self](boost::system::error_code ec, size_t) {
std::cerr << "async_write() " << ec.message() << std::endl;
if (!ec) {
stdqueAnswers.pop_back();
do_write();
}
});
}
enum { max_length = 12000 };
tcp::socket socket_;
std::string data_;
std::deque<std::string> stdqueAnswers;
std::queue<std::string> commandsQueue;
struct WavefrontAccess {
session* _sess;
void ReceiveCommandExternalGet(std::string cmd) {
_sess->stdqueAnswers.push_back("reply for '" + std::move(cmd) + "'");
}
};
std::unique_ptr<WavefrontAccess> wavefrontAccess =
std::make_unique<WavefrontAccess>(WavefrontAccess{this});
};
class server {
public:
server(boost::asio::io_context& io_context, uint16_t port)
: acceptor_{io_context, {{}, port}} {
acceptor_.listen();
do_accept();
}
private:
void do_accept() {
acceptor_.async_accept(
make_strand(acceptor_.get_executor()),
[this](boost::system::error_code ec, tcp::socket socket) {
std::cerr << "accept: " << ec.message() << " "
<< (ec ? tcp::endpoint{} : socket.remote_endpoint())
<< std::endl;
if (!ec) {
std::make_shared<session>(std::move(socket))->start();
do_accept();
}
});
}
tcp::acceptor acceptor_;
};
int main() {
boost::asio::io_context ioc(1);
server s(ioc, 7878);
ioc.run();
}
Even locally, messages may split into smaller TCP packets, for both client and server.
"Framing" protocol must be able to encode and decode sequence of variable sized messages unambiguously for any sequence of split parts (e.g. abc, ab c, a bc, a b c).
Your protocol can do "framing" by itself, or work on top of other "framing" protocol.
Example: TCP
Fixed length header (20 bytes) contains size(s) of variable size fields, including message content.
TCP reads 20 bytes.
TCP parses message size.
TCP reads that amount of bytes.
(repeat)
Example: HTTP
Header doesn't have fixed length.
However it's structure is unambiguous.
It's a text which ends with \r\n\r\n.
This text mustn't include \r\n\r\n, but it can represent it with escaping.
Somewhere in that text, there is size of message.
HTTP reads text (character by character) until it sees \r\n\r\n.
HTTP parses message size.
HTTP reads that amount of bytes.
(repeat)
Possible solution
If your message text doesn't have limitations (e.g. may include terminator/escape string), then you need to write message size before each message.
Otherwise if your messages have structure, you can write "terminator" (e.g. end-of-line or zero-terminator) after end each message, and read message until "terminator".

Boost TCP server not able to receive string messages with \0 in the end

The server works fine and receives all the messages when there are not prepended with \0.
Suppose if i send a message like
"Message" , and send it 100 times to the server all the messages are received by the server but if i add \0 in the end like "Message\0" , than the server only receives couple messages.
This is the code for the TCP server.
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/write.hpp>
#include <iostream>
#include <global.h>
#include <memory>
#include <fstream>
using boost::asio::ip::tcp;
class session
: public std::enable_shared_from_this<session>
{
public:
session(tcp::socket socket)
: socket_(std::move(socket))
{
}
void start()
{
do_read();
}
private:
void do_read()
{
auto self(shared_from_this());
socket_.async_read_some(boost::asio::buffer(data_, max_length),
[this, self ](boost::system::error_code ec, std::size_t length)
{
if (!ec)
{
if (length > 0) {
std::string strData(data_, length);
std::cout << strData;
if (strData.find_first_not_of(' ') != std::string::npos)
{
// There's a non-space.
commandsQueue.push(strData);
}
}
do_write(length);
}
});
}
void do_write(std::size_t length)
{
if (stdqueAnswers.size() > 0) {
auto self(shared_from_this());
std::string stdstrAnswer = stdqueAnswers.front();
boost::asio::async_write(socket_, boost::asio::buffer(stdstrAnswer.c_str(), stdstrAnswer.length() ),
[this, self](boost::system::error_code ec, std::size_t /*length*/)
{
if (!ec)
{
do_read();
}
});
stdqueAnswers.pop();
}
else {
auto self(shared_from_this());
do_read();
}
}
tcp::socket socket_;
enum { max_length = 8096 };
char data_[max_length];
};
class server {
public:
server(boost::asio::io_context& io_context, short port)
: acceptor_(io_context, tcp::endpoint(tcp::v4(), port))
{
acceptor_.listen();
do_accept();
}
private:
void do_accept()
{
acceptor_.async_accept(
make_strand(acceptor_.get_executor()),
[this](boost::system::error_code ec, tcp::socket socket) {
if (!ec) {
std::make_shared<session>(std::move(socket))->start();
do_accept();
}
});
}
tcp::acceptor acceptor_;
};
At first I thought what's likely happening is that you are breaking the output stream by writing \0 to it.
However, reading on, I spotted Undefined Behavior:
std::string stdstrAnswer = stdqueAnswers.front();
async_write(socket_, boost::asio::buffer(stdstrAnswer),
[this, self](error_code ec, size_t /*length*/) {
if (!ec) {
do_read();
}
});
stdqueAnswers.pop();
You're passing a reference to stdstrAnswer, a local variable, which goes out of scope before async_write completes. That's a problem regardless of whether you included stdqueAnswers.pop(). Instead consider:
auto self(shared_from_this());
async_write(socket_, boost::asio::buffer(stdqueAnswers.front()),
[this, self](error_code ec, size_t /*length*/) {
if (!ec) {
stdqueAnswers.pop();
do_read();
}
});
Of course, there's
the code smell of unused length parameter to do_write (what was it intended for?)
the fact that stdqueAnswers is used, but never pushed to
the fact that stdqueAnswers queues answers, but the queue is never drained: instead, after a single async_write there is always just do_read, so the queue won't function as a queue.
It feels like you're trying to have full-duplex IO. In that case you need to change from the half-duplex model that you currently still use:
To something conceptually like
There's some catches how to start the write loop when the first message is pushed to the out queue. See enqueueAnswer in the code below, which shows what I expect you were after:
Live On Coliru
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/write.hpp>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>
#include <queue>
using boost::asio::ip::tcp;
using boost::system::error_code;
class session : public std::enable_shared_from_this<session>
{
public:
session(tcp::socket socket)
: socket_(std::move(socket))
{
}
void start()
{
auto self(shared_from_this());
// dispatch not strictly necessary for single-threaded contexts
dispatch(
socket_.get_executor(),
[this, self]
{
do_read();
});
}
private:
// all private member functions assumed to be on-strand
std::queue<std::string> stdqueAnswers;
void handleCommand(std::string&& cmd)
{
std::cout << std::quoted(cmd);
if(cmd.find_first_not_of(' ') != std::string::npos)
{
// There's a non-space.
enqueueAnswer("Handled command: '" + cmd + "'\n");
}
}
void enqueueAnswer(std::string&& answer)
{
stdqueAnswers.push(std::move(answer));
if(stdqueAnswers.size() == 1)
{
do_write();
}
}
void do_read()
{
auto self(shared_from_this());
socket_.async_read_some( //
boost::asio::buffer(data_),
[this, self](error_code ec, size_t length)
{
if(! ec)
{
handleCommand({data_, length});
do_read();
}
});
}
void do_write()
{
if(stdqueAnswers.empty())
return;
auto self(shared_from_this());
async_write(
socket_,
boost::asio::buffer(stdqueAnswers.front()),
[this, self](error_code ec, size_t)
{
if(! ec)
{
stdqueAnswers.pop();
do_write();
}
});
}
tcp::socket socket_;
char data_[8096];
};
class server
{
public:
server(boost::asio::io_context& io_context, std::uint16_t port)
: acceptor_{io_context, tcp::endpoint(tcp::v4(), port)}
{
acceptor_.listen();
do_accept();
}
private:
void do_accept()
{
acceptor_.async_accept(
make_strand(acceptor_.get_executor()),
[this](error_code ec, tcp::socket socket)
{
if(! ec)
{
std::make_shared<session>(std::move(socket))->start();
do_accept();
}
});
}
tcp::acceptor acceptor_;
};
int main()
{
boost::asio::io_context ioc;
server s(ioc, 8989);
ioc.run();
}
With a demo client:
sleep 1; printf 'hello\nworld\nnull chars \0 are the best\nbye' | netcat 127.0.0.1 8989 | xxd
Seems to handle \0 characters just fine:

How to write content to file asynchronously with boost::beast or boost::asio?

Based on websocket_client_async_ssl.cpp, I modify the function of on_read so that I can save the content into a local file.
class session : public std::enable_shared_from_this<session>
{
std::ofstream outfile_text; // outfile_text.open("test.txt", std::ofstream::out);
const int MAX_LINE_COUNT; // 10
int current_line_;
...
}
void on_read_version2( beast::error_code ec, std::size_t)
{
if(ec)
return fail(ec, "read");
else
{
++current_line_;
const std::string buf_string = beast::buffers_to_string(buffer_.data());
buffer_.consume(buffer_.size());
outfile_text.write((char*)buf_string.data(), buf_string.size());
outfile_text.write("\n", 1);
if (current_line_ > MAX_LINE_COUNT)
{
outfile_text.close();
return;
}
// Re-read a message into our buffer
ws_.async_read( buffer_, beast::bind_front_handler( &session::on_read, shared_from_this()));
}
}
void on_read_version3( beast::error_code ec, std::size_t)
{
if(ec)
return fail(ec, "read");
else
{
++current_line_;
buffer_.consume(buffer_.size());
queue_.push_back(beast::buffers_to_string(buffer_.data()));
// Are we already writing?
if (queue_.size() > 1)
return;
else
// async_write to file from queue_
if (current_line_ > MAX_LINE_COUNT)
{
outfile_text.close();
return;
}
// Re-read a message into our buffer
ws_.async_read( buffer_, beast::bind_front_handler( &session::on_read, shared_from_this()));
}
}
In version2, I used a blocking method to write the content to file. While in version 3, I list the psedo-code where I like to write this part of logic with async-method.
Question>
Does boost::asio or boost::beast support async_write file?
If not, what is the best way to write content to file within the on_read function?
Thank you
Assuming POSIX, you could use stream_descriptor:
net::posix::stream_descriptor stream_{ex_, ::creat("test.txt", 0755)};
Which models the ASIO AsyncStream concept.
On Windows, you have similar types (including stream_handle).
Demo:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/asio/posix/stream_descriptor.hpp>
#include <boost/beast.hpp>
#include <chrono>
#include <deque>
#include <fstream>
#include <iostream>
namespace net = boost::asio;
namespace beast = boost::beast;
namespace websocket = beast::websocket;
using net::ip::tcp;
using namespace std::chrono_literals;
static inline void fail(beast::error_code ec, std::string_view msg) {
if (ec) {
std::cerr << msg << ": " << ec.message() << std::endl;
}
}
class session : public std::enable_shared_from_this<session> {
public:
session(net::any_io_executor ex) : ex_(ex) {}
void start()
{
// assumed on logical strand
ws_.next_layer().connect({{}, 8989});
ws_.handshake("localhost", "/");
do_read();
}
private:
const int MAX_LINE_COUNT = 10;
int current_line_ = 0;
net::streambuf buffer_;
net::any_io_executor ex_;
net::posix::stream_descriptor stream_{ex_, ::creat("test.txt", 0755)};
websocket::stream<tcp::socket> ws_{ex_};
std::deque<std::string> queue_;
void do_read() {
// assumed on strand
ws_.async_read(
buffer_,
beast::bind_front_handler(&session::on_read, shared_from_this()));
}
void on_read(beast::error_code ec, std::size_t)
{
if (ec)
return fail(ec, "read");
++current_line_; // TODO fixme count `\n` in buffer?
enqueue_output(beast::buffers_to_string(buffer_.data()) + '\n');
do_read();
}
bool file_full() const {
return current_line_ > MAX_LINE_COUNT;
}
void enqueue_output(std::string msg) {
if (file_full())
return;
queue_.push_back(std::move(msg));
buffer_.consume(buffer_.size());
// Are we already writing?
if (queue_.size() == 1)
do_write_loop();
}
void do_write_loop()
{
if (queue_.empty()){
if (file_full())
stream_.close();
return;
}
// async_write to file from queue_
net::async_write(
stream_, net::buffer(queue_.front()),
[this, self = shared_from_this()](beast::error_code ec, size_t) {
if (!ec) {
queue_.pop_front();
do_write_loop();
} // TODO error handling
});
}
};
int main()
{
net::io_context io;
std::make_shared<session>(make_strand(io.get_executor())) //
->start();
io.run_for(5s);
}
And a live demo using websocat: https://imgur.com/0TyHmBj

How to start Boost echo server in a separate thread

I was using boost version boost 1_64_0 to run a Tcp server in a seprate thread
This was the code for the server.
https://www.boost.org/doc/libs/1_52_0/doc/html/boost_asio/example/echo/async_tcp_echo_server.cpp
in the Main loop i could spawn a new thread like this.
int main()
{
// Run the boost echo server as a different thread
boost::asio::io_service io_service;
server server1(io_service, 1980);
boost::thread t(boost::bind(&io_service::run, &io_service));
// when about to close the program
io_service.stop(); // stop the server
t.join();
}
Now i have changed the boost version to boost_1_73_0 and i am using this example to create a server
https://www.boost.org/doc/libs/1_71_0/doc/html/boost_asio/example/cpp11/echo/async_tcp_echo_server.cpp
how do to create a new thread ?
The existing code to create a new thread gives error.
io_service: left of :: must be a class/struct/union
run : undeclared identifier
In &io_service::run, io_service is not a type but the local variable by that name.
Starting from https://www.boost.org/doc/libs/1_74_0/doc/html/boost_asio/example/cpp11/echo/async_tcp_echo_server.cpp
io_context.run();
Needs to be like
std::thread t([&] { io_context.run(); });
// ...
t.join();
Or if you insist:
std::thread t(boost::bind(&boost::asio::io_context::run, &io_context));
I would use a thread_pool to shortcut all the complexities¹:
boost::asio::thread_pool io(1);
server s(io.get_executor(), std::atoi(argv[1]));
// ...
io.join();
A trivial edit to the class interface is required (see e.g. Boost 1.70 io_service deprecation)
Live On Coliru
//
// async_tcp_echo_server.cpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2020 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <utility>
using boost::asio::ip::tcp;
class session : public std::enable_shared_from_this<session> {
public:
session(tcp::socket socket) : socket_(std::move(socket)) {}
void start() { do_read(); }
private:
void do_read() {
auto self(shared_from_this());
socket_.async_read_some(
boost::asio::buffer(data_, max_length),
[this, self](boost::system::error_code ec, std::size_t length) {
if (!ec) {
do_write(length);
}
});
}
void do_write(std::size_t length) {
auto self(shared_from_this());
boost::asio::async_write(
socket_, boost::asio::buffer(data_, length),
[this, self](boost::system::error_code ec, std::size_t /*length*/) {
if (!ec) {
do_read();
}
});
}
tcp::socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
class server {
public:
template <typename Executor>
server(Executor ex, short port)
: acceptor_(ex, tcp::endpoint(tcp::v4(), port)) {
do_accept();
}
private:
void do_accept() {
acceptor_.async_accept(
[this](boost::system::error_code ec, tcp::socket socket) {
if (!ec) {
std::make_shared<session>(std::move(socket))->start();
}
do_accept();
});
}
tcp::acceptor acceptor_;
};
int main(int argc, char* argv[]) {
try {
if (argc != 2) {
std::cerr << "Usage: async_tcp_echo_server <port>\n";
return 1;
}
boost::asio::thread_pool io(1);
server s(io.get_executor(), std::atoi(argv[1]));
// ...
io.join();
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << "\n";
}
}
¹ see e.g. Should the exception thrown by boost::asio::io_service::run() be caught?

boost asio io_context.run() segmentation error

I am trying to make simple server that remembers and operates some variables with receive short instructions.
I didn't complete this server, and I am trying to test connecting to the server.
But when I try to connect the server, it occurs segmentation fault.
It seems that be occured at io_context.run() function.
I don't know exact cause of this error in spite of reading asio's reference page.
Please help me..
I think that you don't have to read code of data(data.hpp).
This is server code.
//server.cpp
#include <iostream>
#include "network/sc_network.hpp"
int main(int argc, char *argv[])
{
try
{
if(argc != 2)
{
std::cerr << "Usage: server <port>\n";
return 1;
}
boost::asio::io_context io_context;
tcp::endpoint endpoint(tcp::v4(), std::atoi(argv[1]));
server server(io_context, endpoint);
io_context.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
This is client code.
//client.cpp
#include <iostream>
#include <thread>
#include <cstdlib>
#include <boost/asio.hpp>
#include "network/data/data.hpp"
using boost::asio::ip::tcp;
class client{
private:
boost::asio::io_context& io_context_;
tcp::socket socket_;
oper_data *data_;
void do_connect(const tcp::resolver::results_type& endpoints)
{
boost::asio::async_connect(socket_, endpoints,
[this](boost::system::error_code ec, tcp::endpoint)
{
if(!ec)
{
boost::asio::async_read(socket_,
boost::asio::buffer(data_, sizeof(oper_data)),
[this](boost::system::error_code ec, std::size_t)
{
if(!ec)
{
boost::asio::async_write(socket_,
boost::asio::buffer(data_,sizeof(oper_data)),
[this](boost::system::error_code ec, std::size_t)
{
});
}
else
{
socket_.close();
}
});
}
else
{
socket_.close();
}
});
}
public:
client(boost::asio::io_context& io_context,
const tcp::resolver::results_type& endpoints)
: io_context_(io_context),
socket_(io_context)
{
do_connect(endpoints);
}
void write(const oper_data& data)
{
boost::asio::post(io_context_,
[this, data]()
{
});
}
};
int main(int argc, char *argv[])
{
try
{
if(argc != 3)
{
std::cerr << "Usage: client <host> <port>\n";
return 1;
}
boost::asio::io_context io_context;
tcp::resolver resolver(io_context);
auto endpoints = resolver.resolve(argv[1], argv[2]);
client c(io_context, endpoints);
std::thread t([&io_context](){ io_context.run(); });
char line[128];
while (std::cin.getline(line, 128))
{
oper_data data;
//processing the line with deviding in 3 words.
}
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
this is sc_network.hpp
//sc_network.hpp
#include <boost/asio.hpp>
#include <memory>
#include <utility>
#include "data/data.hpp"
using boost::asio::ip::tcp;
class session
: public std::enable_shared_from_this<session>
{
private:
tcp::socket socket_;
data_proc data_proc_;
public:
session(tcp::socket socket)
: socket_(std::move(socket)){}
void start()
{
oper_data *input_data;
boost::asio::async_read(socket_,
boost::asio::buffer(input_data, sizeof(oper_data)),
[this, input_data](boost::system::error_code ec, std::size_t)
{
if(!ec)
{
data_proc_.set_data(*input_data);
data_proc_.oper_process();
start();
}
else
{
return;
}
});
}
};
class server
{
private:
tcp::acceptor acceptor_;
void do_accept()
{
acceptor_.async_accept(
[this](boost::system::error_code ec, tcp::socket socket)
{
if(!ec)
{
session ex_session(std::move(socket));
}
do_accept();
});
}
public:
server(boost::asio::io_context& io_context,
const tcp::endpoint& endpoint)
: acceptor_(io_context, endpoint)
{
do_accept();
}
};
this is data.hpp.
//data.hpp
#include <deque>
#include <cstring>
#include "favdew_utility.hpp"
#define max_oper_size 5
#define max_oper_buf max_oper_size + 1
struct oper_data {
char oper_[max_oper_buf] = "\0";
char *operand_;
char *oper_num_;
};
typedef struct oper_data oper_data;
class data_store {
private:
char *var_name_;
char *var_value_;
public:
data_store()
: var_name_(NULL), var_value_(NULL) {}
data_store(const char *var_name, const char *var_value)
{
std::size_t var_name_size = strlen(var_name) + 1;
var_name_ = new char[var_name_size];
strncpy(var_name_, var_name, strlen(var_name));
std::size_t var_value_size = strlen(var_value) + 1;
var_value_ = new char[var_value_size];
strncpy(var_value_, var_value, strlen(var_value));
}
char *var_name() { return var_name_; }
char *var_value() { return var_value_; }
void set_value(const char *var_value) {
var_value_ = new char[strlen(var_value) + 1];
strncpy(var_value_, var_value, strlen(var_value));
}
};
typedef std::deque<data_store> data_queue;
class data_proc {
private:
oper_data data_;
data_queue proc_queue;
void var()
{
if (data_store *var = this->get_var(data_.operand_)) {
var->set_value(data_.oper_num_);
}
else {
data_store input_data(data_.operand_, data_.oper_num_);
this->proc_queue.push_back(input_data);
}
}
bool sum()
{
data_store *var = this->get_var(data_.operand_);
if ( (var) && isNumber(var->var_value()))
{
const int input_data = std::atoi(var->var_value()) +
std::atoi(this->data_.oper_num_);
var->set_value(std::to_string(input_data).c_str());
return true;
}
else
return false;
}
bool dif()
{
data_store *var = this->get_var(data_.operand_);
if ((var) && isNumber(var->var_value()))
{
const int input_data = std::atoi(var->var_value()) -
std::atoi(this->data_.oper_num_);
var->set_value(std::to_string(input_data).c_str());
return true;
}
else
return false;
}
public:
data_proc()
{
oper_data input_data;
//<input_data.oper_> is already initialized with "\0"
std::memset(input_data.operand_, 0, sizeof(char *));
std::memset(input_data.oper_num_, 0, sizeof(char *));
}
data_proc(const char *oper, const char *operand, const char *oper_num)
{
strncpy(data_.oper_, oper, max_oper_size);
std::size_t operand_size = strlen(operand) + 1;
data_.operand_ = new char[operand_size];
strncpy(data_.operand_, operand, strlen(operand));
std::size_t oper_num_size = strlen(oper_num) + 1;
data_.oper_num_ = new char[oper_num_size];
strncpy(data_.oper_num_, oper_num, strlen(oper_num));
}
inline void set_data(oper_data data)
{
this->data_ = data;
}
void set_data(const char *oper, const char *operand, const char *oper_num)
{
strncpy(data_.oper_, oper, max_oper_size);
std::size_t operand_size = strlen(operand) + 1;
data_.operand_ = new char[operand_size];
strncpy(data_.operand_, operand, strlen(operand));
std::size_t oper_num_size = strlen(oper_num) + 1;
data_.oper_num_ = new char[oper_num_size];
strncpy(data_.oper_num_, oper_num, strlen(oper_num));
}
data_store *get_var(const char *var_name)
{
const std::size_t queue_size = this->proc_queue.size();
for (std::size_t i=0; i < queue_size; i++) {
if (!strcmp(this->proc_queue[i].var_name(), var_name)) {
return &proc_queue[i];
}
}
return NULL;
}
bool oper_process()
{
const char *oper = this->data_.oper_;
if (!strcmp(oper, "var")) {
var();
return true;
}
else if (!strcmp(oper, "sum")) {
sum();
return true;
}
else if (!strcmp(oper, "dif")) {
dif();
return true;
}
else {
return false;
}
}
};
this is favdew_utility.hpp
#include <string>
#include <cstdlib>
bool isNumber(const char *str)
{
std::size_t length = strlen(str);
for (std::size_t i = 0; i < length; i++)
{
if (!('0' < str[i] && str[i] < '9'))
return false;
continue;
}
return true;
}
bool isEmpty(void *buffer)
{
if (!buffer || *(char *)buffer == '\0')
return true;
else
return false;
}
There are many issues, just pointing out a few:
The declaration
session ex_session(std::move(socket));
This creates a local (stack) variable that inherits from enable_shared_from_this. Using shared_from_this will be Undefined Behaviour
Session gets immediately destructed and start() appears to be never called
If session::start() were called, it would fail because it starts an async operation without guarding the lifetime of the session instance:
boost::asio::async_read(socket_,
boost::asio::buffer(input_data, sizeof(oper_data)),
[this, input_data](boost::system::error_code ec, std::size_t) { ....
At the very least you need to capture the shared pointer to the session:
auto self = shared_from_this();
boost::asio::async_read(socket_,
boost::asio::buffer(input_data, sizeof(oper_data)),
[this, self, input_data](boost::system::error_code ec, std::size_t)
Even worse, input_data is never initialized. Again: Undefined Behaviour. Even if you did initialize it, you'd have to manage lifetime; why not make it a member of the session, instead of dynamically allocating (or forgetting to, as you have now)?
Caution: No, you cannot stack-allocate inside start() not even if you capture it in the lambda, because the async operations will not complete before start() exits.
Same in client: data_ is never initialized. Boom.
Even if you had it correctly allocated, using it as an asio::buffer() treats it as a POD.
Since, however, data_proc happily aggregates a data_queue which is std::deque<> it obviously IS NOT POD. More Undefined Behaviour.
What you probably need is to serialize your datastructures, instead of hoping that copying some bytes of memory is going to magically "work". It won't!
See e.g. sending/receiving a struct in boost::asio
Note While you're at is, use C++ instead of C? All the raw pointers and char* are complexity that you don't need, and it is handing your dozens of footguns or ends or rope that you're gonna hurt yourself more with.
In client.cpp you have:
std::thread t([&io_context](){ io_context.run(); });
char line[128];
while (std::cin.getline(line, 128))
{
oper_data data;
//processing the line with deviding in 3 words.
}
Soooo many things...
use std::getline, not std::istream::getline
the thread needs to be joined (https://en.cppreference.com/w/cpp/thread/thread/~thread)
if all you do is block for input, why have the thread?
io_context.run(); // replaces all of the above
data_store is also not POD, but it is also a living memory-leak. All the new-ed memory is never freed.
Note that, the way it's written, the struct might APPEAR to be POD, but logically it isn't (Rule Of Three). Basically, you wrote it in C, not C++. This foregoes all abstractions that C++ has, and now the compiler cannot tell that the struct refers to non-owned resources.
Mind you, this gives me the impression that oper_data might have similar issues (though at first I assumed that operand_ and _oper_num are supposed to point inside the fixed-size buffer oper_[])
Summarizing:
You're way ahead of yourself. Start much simpler. Use C++ (std::string, never use new/delete, actually use std::make_shared if you want to enable_shared_from_this).
You'll be much happier. Feel free to come back with simpler questions when you get stuck, ideally the SSCCE would be a (few) dozen or so lines.

Resources