i have a question about maximum size of buffer in handler function which is called in boost::bind method.
I have a tcp socket client:
chat_client(boost::asio::io_service& io_service,
tcp::resolver::iterator endpoint_iterator)
: io_service_(io_service),
socket_(io_service),
t(io_service)
{
boost::asio::async_connect(socket_, endpoint_iterator,
boost::bind(&chat_client::handle_connect, this,
boost::asio::placeholders::error));
}
in chat_client class, i create a method to write request to socket buffer
void set_timer(boost::posix_time::ptime time, boost::function<void(const
boost::system::error_code&)> handler)
{
t.expires_at(time);
t.async_wait(handler);
}
void write(const chat_message& msg)
{
set_timer(boost::posix_time::microsec_clock::universal_time() +
boost::posix_time::seconds(1),
boost::bind(&chat_client::do_write, this, msg,
boost::asio::placeholders::error));
io_service_.run();
}
void do_write(chat_message msg, const boost::system::error_code& error)
{
std::cout << "dcm" << std::endl;
bool write_in_progress = !write_msgs_.empty();
write_msgs_.push_back(msg);
if (!write_in_progress)
{
boost::asio::async_write(socket_,
boost::asio::buffer(write_msgs_.front().data(),
write_msgs_.front().length()),
boost::bind(&chat_client::handle_write, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
}
void handle_write(const boost::system::error_code& error, size_t
bytes_transferred)
{
if (!error)
{
write_msgs_.pop_front();
if (!write_msgs_.empty())
{
boost::asio::async_write(socket_,
boost::asio::buffer(write_msgs_.front().data(),
write_msgs_.front().length()),
boost::bind(&chat_client::handle_write, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
}
else
{
do_close();
}
}
My request content is packaged in an instance of chat_message class. And here is chat_message class:
class chat_message
{
public:
enum { header_length = 7 };
enum { max_body_length = 0x1FFFFF };
chat_message()
: body_length_(0)
{
}
const char* data() const
{
return data_;
}
char* data()
{
return data_;
}
size_t length() const
{
return header_length + body_length_;
}
const char* body() const
{
return data_ + header_length;
}
char* body()
{
return data_ + header_length;
}
size_t body_length() const
{
return body_length_;
}
void body_length(size_t new_length)
{
body_length_ = new_length;
if (body_length_ > max_body_length)
body_length_ = max_body_length;
}
bool decode_header()
{
using namespace std; // For strncat and atoi.
char header[header_length + 1] = "";
strncat(header, data_, header_length);
body_length_ = atoi(header);
if (body_length_ > max_body_length)
{
body_length_ = 0;
return false;
}
return true;
}
void encode_header()
{
using namespace std; // For sprintf and memcpy.
char header[header_length + 1] = "";
sprintf(header, "%4d", static_cast<int>(body_length_));
memcpy(data_, header, header_length);
}
private:
char data_[header_length + max_body_length];
size_t body_length_;
};
My issue is when i set max_body_length > 0xFFFFF, boost::bind() method in write() function lead to segmentation fault. So i doubt that, boost::bind() method has a maximum of buffersize. Can someone explain it for me? Thank you
The problem is not in boost::bind() but in the following:
char data_[header_length + max_body_length];
This line will compile alright but can crash when elements get accessed. I would strongly recommend not to use such a huge array of chars here. As one of the ways to keep it you might think about dynamic allocation. Or move from array at all. STL and BOOST libraries provide a lot of tools to safely handle strings and collections.
Related
I'm working on a RS485 communication class and I'm trying to make a function that reads until a certain char is on the line, but with a time out. The problem is that my system timer immediately returns, doesn't matter which time out I enter. I tried changing the timer to be a member variable of the class, so it doesn't go out of scope, but that wasn't the problem. I tried different implementations of timers (deadline_timer mostly) but that didn't help. If I remove the timer from the code, then the read succeeds, but when I add it, even if I give it a timeout of 10 seconds (which should be waay more than enough), it will respond with an immediate timeout.
I tried making a simple version of the class here, but I guess that the options mostly depend on the type of machine you're talking to:
class RS485CommunicationLayer final {
public:
RS485CommunicationLayer(
const std::string& path,
/* options */
): io(), port(io), timer(port.get_io_service()) {
open(/* options */);
};
std::size_t write(const char* const buffer, const size_t size) {
/*impl*/
}
// THIS FUNCTION --v
void readUntil(std::vector<char>& buffer, char delim,std::chrono::microseconds timeout) {
boost::optional<boost::system::error_code> timer_result;
boost::optional<boost::system::error_code> read_result;
port.get_io_service().reset();
timer.expires_from_now(timeout);
boost::asio::async_read_until(port, asio::dynamic_buffer(buffer), delim, [&read_result] (const boost::system::error_code& error, size_t) { read_result.reset(error); });
timer.async_wait([&timer_result] (const boost::system::error_code& error) { timer_result.reset(error); });
while (port.get_io_service().run_one())
{
if (read_result)
timer.cancel();
else if (timer_result) {
port.cancel();
}
}
if (read_result)
throw boost::system::system_error(*read_result);
};
private:
asio::io_context io;
asio::serial_port port;
boost::asio::system_timer timer;
void open(/*args*/) {
port.open(path);
/*set options*/
}
};
Edit:
I also tried the following implementation after finding out that run_for() exists. But then the buffer stays empty weirdly enough.
void RS485CommunicationLayer::readUntil(std::vector<char>& buffer, char delim, std::chrono::microseconds timeout) {
boost::optional<boost::system::error_code> read_result;
boost::asio::async_read_until(port, asio::dynamic_buffer(buffer), delim, [&read_result] (const boost::system::error_code& error, size_t) { read_result.reset(error); });
port.get_io_service().run_for(timeout);
if (read_result)
throw boost::system::system_error(*read_result);
}
First off, get_io_service() indicates a Very Old(TM) boost version. Also, it just returns io.
Secondly, why so complicated? I don't even really have the energy to see whether there is a subtle problem with the run_one() loop (it looks fine at a glance).
I'd simplify:
size_t readUntil(std::vector<char>& buffer, char delim,
std::chrono::microseconds timeout) {
error_code read_result;
size_t msglen = 0;
io.reset();
asio::system_timer timer(io, timeout);
asio::async_read_until(port, asio::dynamic_buffer(buffer), delim,
[&](error_code ec, size_t n) {
timer.cancel();
read_result = ec;
msglen = n;
});
timer.async_wait([&](error_code ec) { if (!ec) port.cancel(); });
io.run();
if (read_result)
boost::throw_with_location(boost::system::system_error(read_result),
read_result.location());
return msglen;
}
You can just cancel the complementary IO object from the respective completion handlers.
The timer is per-op and local to the readUntil, so it doesn't have to be a member.
Let's also throw in the write side, which is all of:
size_t write(char const* const data, const size_t size) {
return asio::write(port, asio::buffer(data, size));
}
And I can demo it working:
Live On Coliru
#include <boost/asio.hpp>
#include <iomanip>
#include <iostream>
namespace asio = boost::asio;
using boost::system::error_code;
using namespace std::chrono_literals;
class RS485CommunicationLayer final {
public:
RS485CommunicationLayer(std::string const& path) : io(), port(io) { open(path); };
size_t write(char const* const data, const size_t size) {
return asio::write(port, asio::buffer(data, size));
}
size_t readUntil(std::vector<char>& buffer, char delim,
std::chrono::microseconds timeout) {
error_code read_result;
size_t msglen = 0;
io.reset();
asio::system_timer timer(io, timeout);
asio::async_read_until(port, asio::dynamic_buffer(buffer), delim,
[&](error_code ec, size_t n) {
timer.cancel();
read_result = ec;
msglen = n;
});
timer.async_wait([&](error_code ec) { if (!ec) port.cancel(); });
io.run();
if (read_result)
boost::throw_with_location(boost::system::system_error(read_result),
read_result.location());
return msglen;
}
private:
asio::io_context io;
asio::serial_port port;
void open(std::string path) {
port.open(path);
/*set options*/
}
void close();
};
int main(int argc, char** argv) {
RS485CommunicationLayer comm(argc > 1 ? argv[1] : "");
comm.write("Hello world\n", 12);
for (std::vector<char> response_buffer;
auto len = comm.readUntil(response_buffer, '\n', 100ms);) //
{
std::cout << "Received " << response_buffer.size() << " bytes, next "
<< quoted(std::string_view(response_buffer.data(), len - 1))
<< std::endl;
// consume
response_buffer.erase(begin(response_buffer), begin(response_buffer) + len);
}
}
Demo locally with a socat PTS tunnel:
socat -d -d pty,raw,echo=0 pty,raw,echo=0
And throwing dictionaries at the other end:
while true; do cat /etc/dictionaries-common/words ; done | pv > /dev/pts/10
I am unable to receive data over serial port in boost::asio while using asynchronous. When I use synchronous routines I am able to receive data.
Code :
SerialPort.cpp
bool SerialPort::read_async(std::uint32_t read_timeout)
{
try
{
if (read_timeout not_eq SerialPort::ignore_timeout)
this->read_timeout = read_timeout;//If read_timeout is not set to ignore_timeout, update the read_timeout else use old read_timeout
this->port.async_read_some(boost::asio::buffer(this->read_buffer.data(), this->read_buffer.size()),
boost::bind(&SerialPort::read_handler, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
return true;
}
catch (const std::exception& ex)
{
PLOG_ERROR << ex.what();
return false;
}
}
void SerialPort::read_handler(const boost::system::error_code& error, std::size_t bytes_transferred)
{
std::string received_data_buffer;
std::transform(this->read_buffer.begin(), this->read_buffer.begin() + bytes_transferred,
std::back_inserter(received_data_buffer), [](std::byte character) {
return static_cast<char>(character);
});
PLOG_INFO << "In Read Buffer : " << received_data_buffer;
}
bool SerialPort::open_port(void)
{
try
{
this->port.open(this->port_name);
return true;
}
catch (const std::exception& ex)
{
PLOG_FATAL << ex.what();
}
return false;
}
SerialPort.hpp
class SerialPort
{
private:
boost::asio::io_context io;
boost::asio::serial_port port;
boost::asio::serial_port::native_handle_type native_port;
std::string port_name;
const static std::uint32_t READ_BUFFER_MAX_LENGTH{ 8096 };
std::array<std::byte, SerialPort::READ_BUFFER_MAX_LENGTH> read_buffer;//Used in synchronous read
void read_handler(
const boost::system::error_code& error, // Result of operation.
std::size_t bytes_transferred // Number of bytes read.
);
//boost::asio::deadline_timer timer;
public:
SerialPort() : io(), port(io), thread_sync_read()
{
}
~SerialPort();
bool open_port(void);
bool read_async(std::uint32_t read_timeout = SerialPort::ignore_timeout);
};
main.cpp
SerialPort sp;
int main()
{
sp.open_port("COM11");
sp.write_sync("Testing123");
sp.read_async();
while (true)
{
}
return 0;
}
You're supposedly trying to do some operations asynchronously.
Firstly, mixing synchronous and asynchronous operations is not always advisable. Some services/IO objects might hold inner state that assumes one or the other.
Secondly, the asynchronous operation requires the io_service to be run. That doesn't happen. You could make it explicit instead of the current while() loop.
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <iostream>
namespace asio = boost::asio;
using boost::system::error_code;
std::ostream PLOG_INFO(std::clog.rdbuf());
std::ostream PLOG_ERROR(std::clog.rdbuf());
std::ostream PLOG_FATAL(std::clog.rdbuf());
class SerialPort
{
public:
SerialPort()
: io_()
, port_(io_) /*, thread_sync_read()*/
{}
~SerialPort() = default;
bool open_port(std::string name);
static constexpr uint32_t ignore_timeout = -1;
bool read_async(std::uint32_t read_timeout = SerialPort::ignore_timeout);
void run() { io_.run(); }
private:
static constexpr uint32_t READ_BUFFER_MAX_LENGTH{8096};
asio::io_context io_;
asio::serial_port port_;
std::string port_name_;
uint32_t read_timeout_ = ignore_timeout;
// asio::deadline_timer timer;
std::array<std::byte, READ_BUFFER_MAX_LENGTH> read_buffer_;
void read_handler(error_code error, size_t bytes_transferred);
};
bool SerialPort::read_async(std::uint32_t read_timeout) {
try {
if (read_timeout != SerialPort::ignore_timeout)
read_timeout_ =
read_timeout; // If read_timeout is not set to ignore_timeout,
// update the read_timeout else use old
// read_timeout
port_.async_read_some(
asio::buffer(read_buffer_.data(), read_buffer_.size()),
boost::bind(&SerialPort::read_handler, this,
asio::placeholders::error,
asio::placeholders::bytes_transferred));
return true;
} catch (const std::exception& ex) {
PLOG_ERROR << ex.what() << std::endl;
return false;
}
}
void SerialPort::read_handler(error_code error, size_t bytes_transferred) {
std::string s;
std::transform(
read_buffer_.begin(), read_buffer_.begin() + bytes_transferred,
std::back_inserter(s),
[](std::byte character) { return static_cast<char>(character); });
PLOG_INFO << "In Read Buffer : " << s << " (" << error.message() << ")" << std::endl;
}
bool SerialPort::open_port(std::string name) {
try {
port_name_ = std::move(name);
port_.open(port_name_);
return true;
} catch (std::exception const& ex) {
PLOG_FATAL << ex.what() << std::endl;
return false;
}
}
SerialPort sp;
int main(int argc, char** argv) {
sp.open_port(argc > 1 ? argv[1] : "COM11");
// sp.write_sync("Testing123");
sp.read_async();
sp.run();
}
I'm currently attempting to design a fairly simple boost::asio server. My first unit test is fairly simple: send a JSON request {"COMMAND": "ADD_1", "VALUE" : 1} and receive the following response:
{
"SUCCESS" : true,
"VALUE" : 2
}
However, instead, the reply is truncated by one character after being read from the socket:
Reply is: {
"SUCCESS" : true,
"VALUE" : 2
Process finished with exit code 0
The code to write to the socket is fairly simple, a member function of a class RequestContext:
void RequestContext::DoWrite(std::size_t length)
{
JSONCPP_STRING parse_err;
Json::Value json_req, json_resp;
auto self(this->shared_from_this());
std::string client_req_str(data_);
if (reader_->parse(client_req_str.c_str(),
client_req_str.c_str() +
client_req_str.length(),
&json_req, &parse_err))
{
try {
// Get JSON response.
json_resp = ProcessRequest(json_req);
json_resp["SUCCESS"] = true;
} catch (const std::exception &ex) {
// If json parsing failed.
json_resp["SUCCESS"] = false;
json_resp["ERRORS"] = std::string(ex.what());
}
} else {
// If json parsing failed.
json_resp["SUCCESS"] = false;
json_resp["ERRORS"] = std::string(parse_err);
}
std::string resp = Json::writeString(writer_, json_resp);
boost::asio::async_write(socket_,
boost::asio::buffer(&resp[0], resp.size()),
[this, self]
(boost::system::error_code ec,
std::size_t bytes_xfered) {
if (!ec) DoRead();
});
}
I have verified that ProcessRequest returns the correct value, so the issue is evidently with async_write. I have tried increasing the value of the second argument to async_write, but it seems to have no effect. What am I doing wrong?
A minimum reproducible example can be found below:
#include <cstdlib>
#include <iostream>
#include <memory>
#include <utility>
#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <json/json.h>
using boost::asio::ip::tcp;
using boost::system::error_code;
/// NOTE: This class exists exclusively for unit testing.
class RequestClass {
public:
/**
* Initialize class with value n to add sub from input values.
*
* #param n Value to add/sub from input values.
*/
explicit RequestClass(int n) : n_(n) {}
/// Value to add/sub from
int n_;
/**
* Add n to value in JSON request.
*
* #param request JSON request with field "value".
* #return JSON response containing modified field "value" = [original_value] + n.
*/
[[nodiscard]] Json::Value add_n(const Json::Value &request) const
{
Json::Value resp;
resp["SUCCESS"] = true;
// If value is present in request, return value + 1, else return error.
if (request.get("VALUE", NULL) != NULL) {
resp["VALUE"] = request["VALUE"].asInt() + this->n_;
} else {
resp["SUCCESS"] = false;
resp["ERRORS"] = "Invalid value.";
}
return resp;
}
/**
* Sun n from value in JSON request.
*
* #param request JSON request with field "value".
* #return JSON response containing modified field "value" = [original_value] - n.
*/
[[nodiscard]] Json::Value sub_n(const Json::Value &request) const
{
Json::Value resp, value;
resp["SUCCESS"] = true;
// If value is present in request, return value + 1, else return error.
if (request.get("VALUE", NULL) != NULL) {
resp["VALUE"] = request["VALUE"].asInt() - this->n_;
} else {
resp["SUCCESS"] = false;
resp["ERRORS"] = "Invalid value.";
}
return resp;
}
};
typedef std::function<Json::Value(RequestClass, const Json::Value &)> RequestClassMethod;
template<class RequestHandler, class RequestClass>
class RequestContext :
public std::enable_shared_from_this<RequestContext<RequestHandler,
RequestClass>>
{
public:
typedef std::map<std::string, RequestHandler> CommandMap;
RequestContext(tcp::socket socket, CommandMap commands,
RequestClass *request_class_inst)
: socket_(std::move(socket))
, commands_(std::move(commands))
, request_class_inst_(request_class_inst)
, reader_((new Json::CharReaderBuilder)->newCharReader())
{}
void Run()
{
DoRead();
}
void Kill()
{
continue_ = false;
}
private:
tcp::socket socket_;
RequestClass *request_class_inst_;
CommandMap commands_;
/// Reads JSON.
const std::unique_ptr<Json::CharReader> reader_;
/// Writes JSON.
Json::StreamWriterBuilder writer_;
bool continue_ = true;
char data_[2048];
void DoRead()
{
auto self(this->shared_from_this());
socket_.async_read_some(boost::asio::buffer(data_, 2048),
[this, self](error_code ec, std::size_t length)
{
if (!ec)
{
DoWrite(length);
}
});
}
void DoWrite(std::size_t length)
{
JSONCPP_STRING parse_err;
Json::Value json_req, json_resp;
auto self(this->shared_from_this());
std::string client_req_str(data_);
if (reader_->parse(client_req_str.c_str(),
client_req_str.c_str() +
client_req_str.length(),
&json_req, &parse_err))
{
try {
// Get JSON response.
json_resp = ProcessRequest(json_req);
json_resp["SUCCESS"] = true;
} catch (const std::exception &ex) {
// If json parsing failed.
json_resp["SUCCESS"] = false;
json_resp["ERRORS"] = std::string(ex.what());
}
} else {
// If json parsing failed.
json_resp["SUCCESS"] = false;
json_resp["ERRORS"] = std::string(parse_err);
}
std::string resp = Json::writeString(writer_, json_resp);
boost::asio::async_write(socket_,
boost::asio::buffer(&resp[0], resp.size()),
[this, self]
(boost::system::error_code ec,
std::size_t bytes_xfered) {
if (!ec) DoRead();
});
}
Json::Value ProcessRequest(Json::Value request)
{
Json::Value response;
std::string command = request["COMMAND"].asString();
// If command is not valid, give a response with an error.
if(commands_.find(command) == commands_.end()) {
response["SUCCESS"] = false;
response["ERRORS"] = "Invalid command.";
}
// Otherwise, run the relevant handler.
else {
RequestHandler handler = commands_.at(command);
response = handler(*request_class_inst_, request);
}
return response;
}
};
template<class RequestHandler, class RequestClass>
class Server {
public:
typedef std::map<std::string, RequestHandler> CommandMap;
Server(boost::asio::io_context &io_context, short port,
const CommandMap &commands,
RequestClass *request_class_inst)
: acceptor_(io_context, tcp::endpoint(tcp::v4(), port))
, commands_(commands)
, request_class_inst_(request_class_inst)
{
DoAccept();
}
~Server()
{
Kill();
}
void Kill()
{
continue_ = false;
}
private:
tcp::acceptor acceptor_;
bool continue_ = true;
CommandMap commands_;
RequestClass *request_class_inst_;
void DoAccept()
{
acceptor_.async_accept(
[this](boost::system::error_code ec, tcp::socket socket) {
if (!ec)
std::make_shared<RequestContext<RequestHandler, RequestClass>>
(std::move(socket), commands_, request_class_inst_)->Run();
DoAccept();
});
}
};
void RunServer(short port)
{
boost::asio::io_context io_context;
auto *request_inst = new RequestClass(1);
std::map<std::string, RequestClassMethod> commands {
{"ADD_1", std::mem_fn(&RequestClass::add_n)},
{"SUB_1", std::mem_fn(&RequestClass::sub_n)}
};
Server<RequestClassMethod, RequestClass> s(io_context, port, commands,
request_inst);
io_context.run();
}
void RunServerInBackground(short port)
{
std::thread t([port] { RunServer(port); });
t.detach();
}
int main()
{
try
{
RunServerInBackground(5000);
boost::asio::io_context io_context;
tcp::socket s(io_context);
tcp::resolver resolver(io_context);
boost::asio::connect(s, resolver.resolve("127.0.0.1", "5000"));
char request[2048] = R"({"COMMAND": "ADD_1", "VALUE" : 1})";
size_t request_length = std::strlen(request);
boost::asio::write(s, boost::asio::buffer(request, request_length));
char reply[2048];
size_t reply_length = boost::asio::read(s, boost::asio::buffer(reply, request_length));
std::cout << "Reply is: ";
std::cout << reply << std::endl;
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
The outgoing buffer needs to be a class member, just like data_, so that the lifetime is guaranteed until async_write is completed.
You can also spot issues like this with linter/runtime checks like ASAN/UBSAN or Valgrind.
UPDATE
Also
size_t reply_length =
boost::asio::read(s, boost::asio::buffer(reply, request_length));
wrongly uses request_length. As a rule, avoid manually specifying buffer sizes, at any time.
Besides, your protocol doesn't provide framing, so you cannot practically keep the same connection open for newer requests (you don't know how many bytes to expect for a response to be complete). I'll "fix" it here by closing the connection after the first request, so we have a working demo.
There's also a race condition with the continue_ flags, but I'll leave that as an exorcism for the reader.
Of course, consider not leaking the request class instance.
Oh, I also switched to Boost JSON as it seemed an easier fit:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/json.hpp>
#include <boost/json/src.hpp>
#include <iostream>
using boost::asio::ip::tcp;
using boost::system::error_code;
namespace json = boost::json;
using Value = json::object;
/// NOTE: This class exists exclusively for unit testing.
struct Sample {
int n_;
Value add_n(Value const& request) const { return impl(std::plus<>{}, request); }
Value sub_n(Value const& request) const { return impl(std::minus<>{}, request); }
Value mul_n(Value const& request) const { return impl(std::multiplies<>{}, request); }
Value div_n(Value const& request) const { return impl(std::divides<>{}, request); }
private:
template <typename Op> Value impl(Op op, Value const& req) const {
return (req.contains("VALUE"))
? Value{{"VALUE", op(req.at("VALUE").as_int64(), n_)},
{"SUCCESS", true}}
: Value{{"ERRORS", "Invalid value."}, {"SUCCESS", false}};
}
};
using RequestClassMethod =
std::function<Value(Sample, Value const&)>;
template <class RequestHandler, class RequestClass>
class RequestContext
: public std::enable_shared_from_this<
RequestContext<RequestHandler, RequestClass>> {
public:
using CommandMap = std::map<std::string, RequestHandler>;
RequestContext(tcp::socket socket, CommandMap commands,
RequestClass* request_class_inst)
: socket_(std::move(socket))
, commands_(std::move(commands))
, request_class_inst_(request_class_inst)
{}
void Run() { DoRead(); }
void Kill() { continue_ = false; }
private:
tcp::socket socket_;
CommandMap commands_;
RequestClass* request_class_inst_;
bool continue_ = true;
char data_[2048];
std::string resp_;
void DoRead()
{
socket_.async_read_some(
boost::asio::buffer(data_),
[this, self = this->shared_from_this()](error_code ec, std::size_t length) {
if (!ec) {
DoWrite(length);
}
});
}
void DoWrite(std::size_t length)
{
Value json_resp;
try {
auto json_req = json::parse({data_, length}).as_object();
json_resp = ProcessRequest(json_req);
json_resp["SUCCESS"] = true;
} catch (std::exception const& ex) {
json_resp = {{"SUCCESS", false}, {"ERRORS", ex.what()}};
}
resp_ = json::serialize(json_resp);
boost::asio::async_write(socket_, boost::asio::buffer(resp_),
[this, self = this->shared_from_this()](
error_code ec, size_t bytes_xfered) {
if (!ec)
DoRead();
});
}
Value ProcessRequest(Value request)
{
auto command = request.contains("COMMAND")
? request["COMMAND"].as_string() //
: "";
std::string cmdstr(command.data(), command.size());
// If command is not valid, give a response with an error.
return commands_.contains(cmdstr) && request_class_inst_
? commands_.at(cmdstr)(*request_class_inst_, request)
: Value{{"SUCCESS", false}, {"ERRORS","Invalid command."}};
}
};
template<class RequestHandler, class RequestClass>
class Server {
public:
using CommandMap = std::map<std::string, RequestHandler>;
Server(boost::asio::io_context& io_context, uint16_t port,
const CommandMap& commands, RequestClass* request_class_inst)
: acceptor_(io_context, {{}, port})
, commands_(commands)
, request_class_inst_(request_class_inst)
{
DoAccept();
}
~Server() { Kill(); }
void Kill() { continue_ = false; }
private:
tcp::acceptor acceptor_;
bool continue_ = true;
CommandMap commands_;
RequestClass *request_class_inst_;
void DoAccept()
{
acceptor_.async_accept(
[this](error_code ec, tcp::socket socket) {
if (!ec)
std::make_shared<
RequestContext<RequestHandler, RequestClass>>(
std::move(socket), commands_, request_class_inst_)
->Run();
DoAccept();
});
}
};
void RunServer(uint16_t port)
{
boost::asio::io_context io_context;
Server<RequestClassMethod, Sample> s(
io_context, port,
{{"ADD_2", std::mem_fn(&Sample::add_n)},
{"SUB_2", std::mem_fn(&Sample::sub_n)},
{"MUL_2", std::mem_fn(&Sample::mul_n)},
{"DIV_2", std::mem_fn(&Sample::div_n)}},
new Sample{2});
io_context.run();
}
void RunServerInBackground(uint16_t port)
{
std::thread t([port] { RunServer(port); });
t.detach();
}
int main() try {
RunServerInBackground(5000);
::sleep(1); // avoid startup race
boost::asio::io_context io;
tcp::socket s(io);
s.connect({{}, 5000});
std::string const request = R"({"COMMAND": "MUL_2", "VALUE" : 21})";
std::cout << "Request: " << std::quoted(request, '\'') << std::endl;
boost::asio::write(s, boost::asio::buffer(request));
s.shutdown(tcp::socket::shutdown_send); // avoid framing problems
error_code ec;
char reply[2048];
size_t reply_length = boost::asio::read(s, boost::asio::buffer(reply), ec);
std::cout << "Reply is: "
<< std::quoted(std::string_view(reply, reply_length), '\'')
<< " (" << ec.message() << ")" << std::endl;
} catch (std::exception const& e) {
std::cerr << "Exception: " << e.what() << "\n";
}
Prints
Request: '{"COMMAND": "MUL_2", "VALUE" : 21}'
Reply is: '{"VALUE":42,"SUCCESS":true}' (End of file)
I have a segmentation fault in the following code ( on _answers.push_back(tmp); ).
Gdb said
(gdb) p tmp
$7 = "HTTP/1.0 200 OK\r\nContent-Type: text/plain; charset=UTF-8\r\nSet-Cookie: color=black;path=/\r\nSet-Cookie: code=f69a2d941420d23be97bbb1ae963295647a91c4f3faf9c5fa80727399927d9d5;path=/\r\nSet-Cookie: game=c1e"...
(gdb) call _answers.size()
$8 = 271275648142580811
So I guess the array has been corrupted. But I don't know where it happened.
// Network.hpp
#pragma once
#include <utility>
#include <string>
#include <vector>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
class Network
{
public:
Network(std::string const &, std::string const &);
~Network() {};
void init();
void connect();
void update();
void sendQuery(const std::string);
bool isConnected();
void reset();
std::string getAnswer();
void handleRead(const boost::system::error_code &, size_t);
void handleWrite(const boost::system::error_code &);
boost::asio::io_service _io_service;
private:
boost::asio::ip::tcp::resolver _resolver;
boost::asio::ip::tcp::socket _sock;
boost::asio::ip::tcp::resolver::iterator _it;
char _buff[2048];
std::vector<std::string> _answers;
std::string const &_host;
std::string const &_port;
bool _answered;
};
// Network.cpp
Network::Network(std::string const &host, std::string const &port) : _resolver(_io_service), _sock(_io_service), _host(host), _port(port), _answered(true) {}
void Network::connect() {
_answers.reserve(2048);
boost::asio::ip::tcp::resolver::query query(_host, _port);
boost::asio::ip::tcp::resolver::iterator iterator = _resolver.resolve(query);
boost::asio::connect(_sock.lowest_layer(), iterator);
}
void Network::handleRead(const boost::system::error_code &err, size_t bread) {
_answered = true;
if (err && err.value() != 2)
throw Gomoku::NetworkException(err.message());
if (bread > 0) {
std::string tmp(_buff);
_answers.push_back(tmp);
}
memset(_buff, 0, 2048);
}
void Network::handleWrite(const boost::system::error_code &err) {
if (err)
throw Gomoku::NetworkException(err.message());
}
void Network::reset() {
_io_service.poll();
_io_service.reset();
_answers.clear();
_answered = true;
}
void Network::sendQuery(const std::string req) {
_io_service.poll();
_io_service.reset();
if (_answered == 0)
return;
_answered = false;
connect();
const char *str = new char[req.length()];
str = req.c_str();
boost::asio::async_write(_sock, boost::asio::buffer(str, req.length()), boost::bind(&Network::handleWrite, this, boost::asio::placeholders::error));
boost::asio::async_read(_sock, boost::asio::buffer(_buff, 2048), boost::bind(&Network::handleRead, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
std::string Network::getAnswer() {
if (_answers.empty())
return "";
std::string tmp = _answers.back();
_answers.pop_back();
return tmp;
}
// Player.cpp
Player::Player(std::string const &host, std::string const &port) : _network(host, port) {
_myTurn = false;
_whiteScore = _blackScore = 0;
_host = host + ":" + port;
initMap();
}
void Player::connect() {
std::string str = "GET /players/connect/ HTTP/1.0\r\nHost: " + _host + "\r\nAccept: */*\r\n\r\n";
_network.sendQuery(str);
}
void Player::sendClick(std::pair<int, int> click, std::string const &header) {
std::stringstream ss;
ss << "POST /game/play/" << click.first << "/" << click.second << header << _cookie << "\r\n\r\n";
std::string req = ss.str();
_network.sendQuery(req);
_network._io_service.run();
_network._io_service.reset();
std::string ans = _network.getAnswer();
parseAnswer(ans);
req = "GET /game/map.txt" + header + _cookie + "\r\n\r\n";
_network.sendQuery(req);
}
I also saw the following code by following the segv trace (basic_string.h:400):
: _M_dataplus(_M_local_data(), __str._M_get_allocator()) // TODO A traits
At first glance: std::string tmp(_buff); is wrong, because _buff may not be null terminated, thus reading 271275648142580811 bytes into memory.
Additionally, sendQuery's parameter is a local string, so as soon as the function exits, the string is free'd, and async_write continues to read from invalid memory, which is undefined behavior. Anything can happen.
Also, all requests use the same incoming buffer, so if multiple occur at the same time, you get undefined or useless behavior.
I apologise for the length of code posted here. I am trying to create a class that uses boost::process to spawn a process, feed it some data on its stdin and to capture all its stdout & stderr.
The subprocess' stdin may be lengthy as might the stdout; The target machine does not have vast amounts of memory so each of these needs to be handled in chunks.
I have read endless examples of usage of boost::process, but have found nothing that answers all these questions together. I have tried combining these examples without success. I'm obviously missing something. I would be grateful for any help.
What happens is that the child process is successfully spawned but nothing happens. The parent process halt on the line marked THUS *.
The class defined:
class CommandProcessor {
public:
explicit CommandProcessor(const std::string &executable_path, bool slow) :
executable_path_(executable_path), slow_(slow), in_(io_service_, ::dup(STDIN_FILENO)), out_(io_service_, ::dup(STDOUT_FILENO)), err_(io_service_, ::dup(STDERR_FILENO)) {
}
private:
void begin_write_stdin();
void end_write_stdin(const boost::system::error_code &ec, std::size_t bytes_transferred);
void begin_read_stdout();
void end_read_stdout(const boost::system::error_code &ec, std::size_t bytes_transferred);
void begin_read_stderr();
void end_read_stderr(const boost::system::error_code &ec, std::size_t bytes_transferred);
public:
void execute_command(const Command& command);
private:
boost::filesystem::path executable_path_;bool slow_;
boost::asio::io_service io_service_;
boost::asio::posix::stream_descriptor in_;
boost::asio::posix::stream_descriptor out_;
boost::asio::posix::stream_descriptor err_;
std::string stdout_;
std::string stderr_;
std::string stdin_buffer_;
std::array<char, 4096> stdout_buffer_;
std::array<char, 4096> stderr_buffer_;
std::vector<std::string>::const_iterator stdin_it_;
std::vector<std::string>::const_iterator stdin_end_;
};
The code (for brevity I include only the bits giving me trouble):
void CommandProcessor::begin_write_stdin() {
if (stdin_buffer_.size() == 0) {
for (; stdin_it_ != stdin_end_; stdin_it_++) {
if (stdin_buffer_.size() + stdin_it_->size() > 4096) {
break;
}
stdin_buffer_ += *stdin_it_;
}
}
if (stdin_buffer_.size() == 0) {
return;
}
in_.async_write_some(boost::asio::buffer(stdin_buffer_),
boost::bind(&CommandProcessor::end_write_stdin, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void CommandProcessor::end_write_stdin(const boost::system::error_code &ec, std::size_t bytes_transferred __attribute__((unused))) {
if (!ec) {
stdin_it_++;
if (stdin_it_ != stdin_end_) {
begin_write_stdin();
}
}
in_.close();
}
void CommandProcessor::begin_read_stdout() {
out_.async_read_some(boost::asio::buffer(stdout_buffer_),
boost::bind(&CommandProcessor::end_read_stdout, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void CommandProcessor::end_read_stdout(const boost::system::error_code &ec, std::size_t bytes_transferred __attribute__((unused))) {
if (!ec) {
stdout_ += stdout_buffer_.data();
begin_read_stdout();
}
out_.close();
}
void CommandProcessor::begin_read_stderr() {
err_.async_read_some(boost::asio::buffer(stderr_buffer_), boost::bind(&CommandProcessor::end_read_stderr, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void CommandProcessor::end_read_stderr(const boost::system::error_code &ec, std::size_t bytes_transferred __attribute__((unused))) {
if (!ec) {
stderr_ += stderr_buffer_.data();
begin_read_stderr();
}
err_.close();
}
void CommandProcessor::execute_command(const Command& command) {
boost::process::context ctx;
ctx.stdin_behavior = boost::process::capture_stream();
ctx.stdout_behavior = boost::process::capture_stream();
ctx.stderr_behavior = boost::process::capture_stream();
stdin_it_ = command.for_stdin_.begin();
stdin_end_ = command.for_stdin_.end();
boost::process::child child(boost::process::launch((executable_path_ / command.executable_name_).string(), command.executable_name_ + command.arguments_, ctx));
boost::process::pistream &child_stdout(child.get_stdout());
**** Halts in next statement
in_.assign(child_stdout.handle().release());
boost::process::pistream &child_stderr(child.get_stderr());
err_.assign(child_stderr.handle().release());
boost::process::postream &child_stdin = child.get_stdin();
out_.assign(child_stdin.handle().release());
begin_read_stdout();
begin_read_stderr();
begin_write_stdin();
boost::process::status child_status(child.wait());
if (child_status.exited()) {
if (child_status.exit_status() == 0) {
throw ProcessorException((boost::format("Exec status %d on %s") % child_status.exit_status() % (executable_path_ / command.executable_name_).string()).str());
}
} else {
throw ProcessorException((boost::format("Exec failure on %s") % (executable_path_ / command.executable_name_).string()).str());
}
}