Related
I am attempting to make a fairly simple client-server program with boost asio. The server class is implemented as follows:
template<class RequestHandler, class RequestClass>
class Server {
public:
typedef std::map<std::string, RequestHandler> CommandMap;
Server(short port, CommandMap commands, RequestClass *request_class_inst)
: acceptor_(io_context_, tcp::endpoint(tcp::v4(), port))
, commands_(std::move(commands))
, request_class_inst_(request_class_inst)
{
DoAccept();
}
~Server()
{
}
void Run()
{
io_context_.run();
}
void RunInBackground()
{
std::thread t( [this]{ Run(); });
t.detach();
}
void Kill()
{
acceptor_.close();
}
private:
boost::asio::io_context io_context_;
tcp::acceptor acceptor_;
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<Session<RequestHandler, RequestClass>>
(std::move(socket), commands_, request_class_inst_)->Run();
DoAccept();
});
}
};
In addition to the server class, I implement a basic Client class thusly:
class Client {
public:
/**
* Constructor, initializes JSON parser and serializer.
*/
Client()
: reader_((new Json::CharReaderBuilder)->newCharReader())
{}
Json::Value MakeRequest(const std::string &ip_addr, unsigned short port,
const Json::Value &request)
{
boost::asio::io_context io_context;
std::string serialized_req = Json::writeString(writer_, request);
tcp::socket s(io_context);
tcp::resolver resolver(io_context);
s.connect({ boost::asio::ip::address::from_string(ip_addr), port });
boost::asio::write(s, boost::asio::buffer(serialized_req));
s.shutdown(tcp::socket::shutdown_send);
error_code ec;
char reply[2048];
size_t reply_length = boost::asio::read(s, boost::asio::buffer(reply),
ec);
std::cout << std::string(reply).substr(0, reply_length) << std::endl;
Json::Value json_resp;
JSONCPP_STRING parse_err;
std::string resp_str(reply);
if (reader_->parse(resp_str.c_str(), resp_str.c_str() + resp_str.length(),
&json_resp, &parse_err))
return json_resp;
throw std::runtime_error("Error parsing response.");
}
bool IsAlive(const std::string &ip_addr, unsigned short port)
{
boost::asio::io_context io_context;
tcp::socket s(io_context);
tcp::resolver resolver(io_context);
try {
s.connect({boost::asio::ip::address::from_string(ip_addr), port});
} catch(const boost::wrapexcept<boost::system::system_error> &err) {
s.close();
return false;
}
s.close();
return true;
}
private:
/// Reads JSON.
const std::unique_ptr<Json::CharReader> reader_;
/// Writes JSON.
Json::StreamWriterBuilder writer_;
};
I have implemented a small example to test Client::IsAlive:
int main()
{
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> s1(5000, commands, request_inst);
s1.RunInBackground();
std::vector<Client*> clients(6, new Client());
s1.Kill();
// Should output "0" to console.
std::cout << clients.at(1)->IsAlive("127.0.0.1", 5000);
return 0;
}
However, when I attempt to run this, the output varies. About half the time, I receive the correct value and the program exits with code 0, but, on other occasions, the program will either: (1) exit with code 139 (SEGFAULT) before outputting 0 to the console, (2) output 0 to the console and subsequently exit with code 139, (3) output 0 to the console and subsequently hang, or (4) hang before writing anything to the console.
I am uncertain as to what has caused these errors. I expect that it has to do with the destruction of Server::io_context_ and implementation of Server::Kill. Could this pertain to how I am storing Server::io_context_ as a data member?
A minimum reproducible example is shown below:
#define BOOST_ASIO_HAS_MOVE
#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 Session :
public std::enable_shared_from_this<Session<RequestHandler,
RequestClass>>
{
public:
typedef std::map<std::string, RequestHandler> CommandMap;
Session(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];
std::string resp_;
void DoRead()
{
auto self(this->shared_from_this());
socket_.async_read_some(boost::asio::buffer(data_),
[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;
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);
}
resp_ = Json::writeString(writer_, json_resp);
auto self(this->shared_from_this());
boost::asio::async_write(socket_,
boost::asio::buffer(resp_),
[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(short port, CommandMap commands, RequestClass *request_class_inst)
: acceptor_(io_context_, tcp::endpoint(tcp::v4(), port))
, commands_(std::move(commands))
, request_class_inst_(request_class_inst)
{
DoAccept();
}
~Server()
{
}
void Run()
{
io_context_.run();
}
void RunInBackground()
{
std::thread t( [this]{ Run(); });
t.detach();
}
void Kill()
{
acceptor_.close();
}
private:
boost::asio::io_context io_context_;
tcp::acceptor acceptor_;
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<Session<RequestHandler, RequestClass>>
(std::move(socket), commands_, request_class_inst_)->Run();
DoAccept();
});
}
};
class Client {
public:
/**
* Constructor, initializes JSON parser and serializer.
*/
Client()
: reader_((new Json::CharReaderBuilder)->newCharReader())
{}
Json::Value MakeRequest(const std::string &ip_addr, unsigned short port,
const Json::Value &request)
{
boost::asio::io_context io_context;
std::string serialized_req = Json::writeString(writer_, request);
tcp::socket s(io_context);
tcp::resolver resolver(io_context);
s.connect({ boost::asio::ip::address::from_string(ip_addr), port });
boost::asio::write(s, boost::asio::buffer(serialized_req));
s.shutdown(tcp::socket::shutdown_send);
error_code ec;
char reply[2048];
size_t reply_length = boost::asio::read(s, boost::asio::buffer(reply),
ec);
std::cout << std::string(reply).substr(0, reply_length) << std::endl;
Json::Value json_resp;
JSONCPP_STRING parse_err;
std::string resp_str(reply);
if (reader_->parse(resp_str.c_str(), resp_str.c_str() + resp_str.length(),
&json_resp, &parse_err))
return json_resp;
throw std::runtime_error("Error parsing response.");
}
bool IsAlive(const std::string &ip_addr, unsigned short port)
{
boost::asio::io_context io_context;
tcp::socket s(io_context);
tcp::resolver resolver(io_context);
try {
s.connect({boost::asio::ip::address::from_string(ip_addr), port});
} catch(const boost::wrapexcept<boost::system::system_error> &err) {
s.close();
return false;
}
s.close();
return true;
}
private:
/// Reads JSON.
const std::unique_ptr<Json::CharReader> reader_;
/// Writes JSON.
Json::StreamWriterBuilder writer_;
};
int main()
{
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> s1(5000, commands, request_inst);
s1.RunInBackground();
std::vector<Client*> clients(6, new Client());
Json::Value sub_one_req;
sub_one_req["COMMAND"] = "SUB_1";
sub_one_req["VALUE"] = 1;
s1.Kill();
std::cout << clients.at(1)->IsAlive("127.0.0.1", 5000);
return 0;
}
Using ASAN (-fsanitize=addess) on that shows
false
=================================================================
==31232==ERROR: AddressSanitizer: heap-use-after-free on address 0x6110000002c0 at pc 0x561409ca2ea3 bp 0x7efcf
bbfdc60 sp 0x7efcfbbfdc50
READ of size 8 at 0x6110000002c0 thread T1
=================================================================
#0 0x561409ca2ea2 in boost::asio::detail::epoll_reactor::run(long, boost::asio::detail::op_queue<boost::asi
o::detail::scheduler_operation>&) /home/sehe/custom/boost_1_76_0/boost/asio/detail/impl/epoll_reactor.ipp:504
==31232==ERROR: LeakSanitizer: detected memory leaks
#1 0x561409cb442c in boost::asio::detail::scheduler::do_run_one(boost::asio::detail::conditionally_enabled_
mutex::scoped_lock&, boost::asio::detail::scheduler_thread_info&, boost::system::error_code const&) /home/sehe/
custom/boost_1_76_0/boost/asio/detail/impl/scheduler.ipp:470
Direct leak of 4 byte(s) in 1 object(s) allocated from:
#0 0x7efd08fca717 in operator new(unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.6+0xb4717)
#2 0x561409cf2792 in boost::asio::detail::scheduler::run(boost::system::error_code&) /home/sehe/custom/boos
t_1_76_0/boost/asio/detail/impl/scheduler.ipp:204
#1 0x561409bc62b5 in main /home/sehe/Projects/stackoverflow/test.cpp:229
SUMMARY: AddressSanitizer: 4 byte(s) leaked in 1 allocation(s).
Or on another run:
It already tells you "everything" you need to know. Coincidentally, it was the bug I referred to in my previous answer. To do graceful shutdown you have to synchronize on the thread. Detaching it ruins your chances forever. So, let's not detach it:
void RunInBackground()
{
if (!t_.joinable()) {
t_ = std::thread([this] { Run(); });
}
}
As you can see, this is captured, so you can never allow the thread to run past the destruction of the Server object.
And then in the destructor join it:
~Server()
{
if (t_.joinable()) {
t_.join();
}
}
Now, let's be thorough. We have two threads. They share objects. io_context is thread-safe, so that's fine. But tcp::acceptor is not. Neither might request_class_inst_. You need to synchronize more:
void Kill()
{
post(io_context_, [this] { acceptor_.close(); });
}
Now, note that this is NOT enough! .close() causes .cancel() on the acceptor, but that just makes the completion handler be invoked with error::operation_aborted. So, you need to prevent initiating DoAccept again in that case:
void DoAccept()
{
acceptor_.async_accept(
[this](boost::system::error_code ec, tcp::socket socket) {
if (ec) {
std::cout << "Accept loop: " << ec.message() << std::endl;
} else {
std::make_shared<Session<RequestHandler, RequestClass>>(
std::move(socket), commands_, request_class_inst_)
->Run();
DoAccept();
}
});
}
I took the liberty of aborting on /any/ error. Err on the safe side: you prefer processes to exit instead of being stuck in unresponsive state of high-CPU loops.
Regardless of this, you should be aware of the race condition between server startup/shutdown and your test client:
s1.RunInBackground();
// unspecified, race condition!
std::cout << "IsAlive(" << __LINE__ << "): " << clients.at(0).IsAlive("127.0.0.1", 5000) << std::endl;
sleep_for(10ms); // likely enough for acceptor to start
// true:
std::cout << "IsAlive(" << __LINE__ << "): " << clients.at(1).IsAlive("127.0.0.1", 5000) << std::endl;
std::cout << "MakeRequest: " << clients.at(2).MakeRequest(
"127.0.0.1", 5000, {{"COMMAND", "MUL_2"}, {"VALUE", "21"}})
<< std::endl;
s1.Kill();
// unspecified, race condition!
std::cout << "IsAlive(" << __LINE__ << "): " << clients.at(3).IsAlive("127.0.0.1", 5000) << std::endl;
sleep_for(10ms); // likely enough for acceptor to be closed
// false:
std::cout << "IsAlive(" << __LINE__ << "): " << clients.at(4).IsAlive("127.0.0.1", 5000) << std::endl;
Prints
IsAlive(240): true
IsAlive(245): true
MakeRequest: {"SUCCESS":false,"ERRORS":"not an int64"}
{"SUCCESS":false,"ERRORS":"not an int64"}
IsAlive(252): CLOSING
Accept loop: Operation canceled
THREAD EXIT
false
IsAlive(256): false
Complete Listing
Note that this also fixed the unnecessary leak of the RequestClass instance. You were already assuming copy-ability (because you were passing it by value in various places).
Also note that in MakeRequest we now no longer swallow any errors except EOF.
Like last time, I employ Boost Json for simplicity and to make the sample self-contained for StackOverflow.
Address sanitizer (ASan) and UBSan are silent. Life is good.
Live On Coliru
#include <boost/asio.hpp>
#include <boost/json.hpp>
#include <boost/json/src.hpp>
#include <iostream>
#include <deque>
using boost::asio::ip::tcp;
using boost::system::error_code;
namespace json = boost::json;
using Value = json::object;
using namespace std::chrono_literals;
static auto sleep_for(auto delay) { return std::this_thread::sleep_for(delay); }
/// NOTE: This class exists exclusively for unit testing.
struct RequestClass {
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(RequestClass const&, Value const&)>;
template <class RequestHandler, class RequestClass>
class Session
: public std::enable_shared_from_this<
Session<RequestHandler, RequestClass>> {
public:
using CommandMap = std::map<std::string, RequestHandler>;
Session(tcp::socket socket, CommandMap commands,
RequestClass request_class_inst)
: socket_(std::move(socket))
, commands_(std::move(commands))
, request_class_inst_(std::move(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)
? 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(uint16_t port, CommandMap commands, RequestClass request_class_inst)
: acceptor_(io_context_, tcp::endpoint(tcp::v4(), port))
, commands_(std::move(commands))
, request_class_inst_(std::move(request_class_inst))
{
DoAccept();
}
~Server()
{
if (t_.joinable()) {
t_.join();
}
assert(not t_.joinable());
}
void Run()
{
io_context_.run();
}
void RunInBackground()
{
if (!t_.joinable()) {
t_ = std::thread([this] {
Run();
std::cout << "THREAD EXIT" << std::endl;
});
}
}
void Kill()
{
post(io_context_, [this] {
std::cout << "CLOSING" << std::endl;
acceptor_.close(); // causes .cancel() as well
});
}
private:
boost::asio::io_context io_context_;
tcp::acceptor acceptor_;
CommandMap commands_;
RequestClass request_class_inst_;
std::thread t_;
void DoAccept()
{
acceptor_.async_accept(
[this](boost::system::error_code ec, tcp::socket socket) {
if (ec) {
std::cout << "Accept loop: " << ec.message() << std::endl;
} else {
std::make_shared<Session<RequestHandler, RequestClass>>(
std::move(socket), commands_, request_class_inst_)
->Run();
DoAccept();
}
});
}
};
class Client {
public:
/**
* Constructor, initializes JSON parser and serializer.
*/
Client() {}
Value MakeRequest(std::string const& ip_addr, uint16_t port,
Value const& request)
{
boost::asio::io_context io_context;
std::string serialized_req = serialize(request);
tcp::socket s(io_context);
s.connect({boost::asio::ip::address::from_string(ip_addr), port});
boost::asio::write(s, boost::asio::buffer(serialized_req));
s.shutdown(tcp::socket::shutdown_send);
char reply[2048];
error_code ec;
size_t reply_length = read(s, boost::asio::buffer(reply), ec);
if (ec && ec != boost::asio::error::eof) {
throw boost::system::system_error(ec);
}
// safe method:
std::string_view resp_str(reply, reply_length);
Value res = json::parse({reply, reply_length}).as_object();
std::cout << res << std::endl;
return res;
}
bool IsAlive(std::string const& ip_addr, unsigned short port)
{
boost::asio::io_context io_context;
tcp::socket s(io_context);
error_code ec;
s.connect({boost::asio::ip::address::from_string(ip_addr), port}, ec);
return not ec.failed();
}
};
int main()
{
std::cout << std::boolalpha;
std::deque<Client> clients(6);
Server<RequestClassMethod, RequestClass> s1(
5000,
{
{"ADD_2", std::mem_fn(&RequestClass::add_n)},
{"SUB_2", std::mem_fn(&RequestClass::sub_n)},
{"MUL_2", std::mem_fn(&RequestClass::mul_n)},
{"DIV_2", std::mem_fn(&RequestClass::div_n)},
},
RequestClass{1});
s1.RunInBackground();
// unspecified, race condition!
std::cout << "IsAlive(" << __LINE__ << "): " << clients.at(0).IsAlive("127.0.0.1", 5000) << std::endl;
sleep_for(10ms); // likely enough for acceptor to start
// true:
std::cout << "IsAlive(" << __LINE__ << "): " << clients.at(1).IsAlive("127.0.0.1", 5000) << std::endl;
std::cout << "MakeRequest: " << clients.at(2).MakeRequest(
"127.0.0.1", 5000, {{"COMMAND", "MUL_2"}, {"VALUE", "21"}})
<< std::endl;
s1.Kill();
// unspecified, race condition!
std::cout << "IsAlive(" << __LINE__ << "): " << clients.at(3).IsAlive("127.0.0.1", 5000) << std::endl;
sleep_for(10ms); // likely enough for acceptor to be closed
// false:
std::cout << "IsAlive(" << __LINE__ << "): " << clients.at(4).IsAlive("127.0.0.1", 5000) << std::endl;
}
I am using Asio to handle my network class.
I have been troubling for a while and I can't get the reason why.
First, the code :
// .h
#pragma once
#include <asio.hpp>
class Connection : public std::enable_shared_from_this<Connection>
{
public:
static std::shared_ptr<Connection> create(asio::io_context& IoContext);
asio::ip::tcp::socket& getSocket();
void start();
private:
Connection(asio::io_context& IoContext);
void startReading();
void sendPacket(std::string Packet);
void handle_read(const asio::error_code& error, size_t bytes);
void handle_write(const asio::error_code& error, size_t bytes);
void disconnect();
asio::ip::tcp::socket socket;
char packet[4096];
bool started;
};
class Network
{
public:
Network(asio::io_context& IoContext, unsigned short Port);
void startServer();
private:
void startAccept();
void handle_accept(std::shared_ptr<Connection> connection, const asio::error_code& error);
asio::io_context& ioContext;
asio::ip::tcp::acceptor acceptor;
bool started;
std::vector<std::shared_ptr<Connection>> connections;
};
// .cpp
Connection::Connection(asio::io_context& IoContext)
: socket(IoContext)
, started(false)
, packet("")
{
}
std::shared_ptr<Connection> Connection::create(asio::io_context& IoContext)
{
return std::shared_ptr<Connection>(new Connection(IoContext));
}
asio::ip::tcp::socket& Connection::getSocket()
{
return socket;
}
void Connection::start()
{
std::cout << "Connection::start();" << std::endl;
if (!started)
{
startReading();
started = true;
}
}
void Connection::sendPacket(std::string Packet)
{
socket.async_send(asio::buffer(Packet), [this](const asio::error_code& error, size_t bytes)
{ handle_write(error, bytes); });
}
void Connection::startReading()
{
socket.async_receive(asio::buffer(packet), [this](const asio::error_code& error, size_t bytes)
{ handle_read(error, bytes); });
}
void Connection::handle_write(const asio::error_code& error, size_t bytes)
{
std::cout << "WRITE : " << error.message() << " size : " << bytes << std::endl;
}
void Connection::handle_read(const asio::error_code& error, size_t bytes)
{
if (error)
{
std::cout << "error:" << error.message();
disconnect();
}
else
{
if (bytes > 0)
{
packet[bytes] = 0;
std::string response = ...;
sendPacket(response);
}
}
}
Network::Network(asio::io_context& IoContext, unsigned short Port)
: ioContext(IoContext)
, acceptor(ioContext, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), Port))
, started(false)
{
std::cout << "Server::Server();" << std::endl;
}
void Network::startServer()
{
std::cout << "Server::startServer();" << std::endl;
if (!started)
{
startAccept();
started = true;
}
}
void Network::startAccept()
{
std::cout << "Server::startAccept();" << std::endl;
std::shared_ptr<Connection> connection = Connection::create(ioContext);
connections.push_back(connection);
asio::error_code er;
acceptor.async_accept(connection->getSocket(), std::bind(&Network::handle_accept, this, connection, er));
}
void Network::handle_accept(std::shared_ptr<Connection> connection, const asio::error_code& error)
{
std::cout << "Server::handle_accept();" << std::endl;
if (!error)
{
std::cout << "Ok" << std::endl;
connection->start();
}
startAccept();
}
(I removed unnecessary function from the cpp in order to make it more condensed, don't be surprised if some are missing)
Basically, it crashes when I sendPacket(something) from the handle_read function.
I found two fixes for it :
create and use a member std::string, then the code in handle_read(...) becomes
if (bytes > 0)
{
packet[bytes] = 0;
std::string response = ...;
sendPacket(my_member_string);
}
or I create a std::string* in sendPacket(...) :
void Connection::sendPacket(std::string Packet)
{
std::string *copy;
socket.async_send(asio::buffer(copy), [this](const asio::error_code& error, size_t bytes)
{ handle_write(error, bytes); });
}
without deleting it (if I do, it crashes the same way).
So, my questions : why is response causing a crash ? I read a lot of same issue talking about "lifetime", could you tell me more about it ? What is really happening in this code with response's life ?
Also, what is the clean way to fix this bug, if it is not one of the above ?
By the way, I am not using Boost.
Thank you by advance
You should not capture [this] pointer within your lambdas in ..._async functions. Because lambda will outlive the this pointer and this will point to invalid address within lambda call.
Use [self=shared_from_this()] and work with self within lambda, so the object will live with shared pointer through async calls.
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.
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());
}
}
Ultimately I'm trying to transfer buffers from one machine to another. The code below takes stream of <id><size><data with size bytes> and reads the part in the handleReadHeader function, then reads the <size> number of bytes, then goes back and waits for another <id><size> pair.
I've pasted a lot of code, but really the only functions I'm suspicious of are:
Downlink::addMsgToQueue
Downlink::writeCallback
Downlink::startWrites()
Downlink::handleReadHeader
Downlink::handleReadFrameDataBGR
using namespace std;
using namespace boost;
using namespace boost::asio;
Downlink::Downlink() :
socket(nIO),
headerSize(sizeof(unsigned int)+1),
connected(false),
isWriting(false),
readHeaderBuffer(headerSize)
{}
Downlink::~Downlink() {
disconnect();
}
bool Downlink::connect(const std::string &robotHost, unsigned int port) {
disconnect();
ip::tcp::resolver resolver(nIO);
ip::tcp::resolver::query query(robotHost, lexical_cast<string>(port));
ip::tcp::resolver::iterator iterator = resolver.resolve(query);
ip::tcp::resolver::iterator end;
boost::system::error_code ec;
for(;iterator!=end;++iterator) {
socket.connect(*iterator, ec);
if(!ec)
break;
socket.close();
}
if(!socket.is_open())
return false;
async_read(socket, buffer(readHeaderBuffer),
bind(&Downlink::handleReadHeader, this, _1, _2));
//start network thread.
lock_guard<mutex> l(msgMutex);
outgoingMessages = queue<vector<char> >();
nIO.reset();
t = thread(bind(&boost::asio::io_service::run, &nIO));
connected = true;
return true;
}
bool Downlink::isConnected() const {
return connected;
}
void Downlink::disconnect() {
nIO.stop();
t.join();
socket.close();
connected = false;
isWriting = false;
nIO.reset();
nIO.run();
}
void Downlink::writeToLogs(const std::string &logMsg) {
vector<char> newMsg(logMsg.length()+headerSize);
newMsg[0] = MSG_WRITE_LOG;
const unsigned int msgLen(logMsg.length());
memcpy(&newMsg[1], &msgLen, sizeof(unsigned int));
vector<char>::iterator dataBegin = newMsg.begin();
advance(dataBegin, headerSize);
copy(logMsg.begin(), logMsg.end(), dataBegin);
assert(newMsg.size()==(headerSize+logMsg.length()));
addMsgToQueue(newMsg);
}
void Downlink::addMsgToQueue(const std::vector<char> &newMsg) {
lock_guard<mutex> l(msgMutex);
outgoingMessages.push(newMsg);
lock_guard<mutex> l2(outMutex);
if(!isWriting) {
nIO.post(bind(&Downlink::startWrites, this));
}
}
void Downlink::writeCallback(const boost::system::error_code& error,
std::size_t bytes_transferred) {
if(error) {
disconnect();
lock_guard<mutex> l(msgMutex);
outgoingMessages = queue<vector<char> >();
return;
}
{
lock_guard<mutex> l2(outMutex);
isWriting = false;
}
startWrites();
}
void Downlink::startWrites() {
lock_guard<mutex> l(msgMutex);
lock_guard<mutex> l2(outMutex);
if(outgoingMessages.empty()) {
isWriting = false;
return;
}
if(!isWriting) {
currentOutgoing = outgoingMessages.front();
outgoingMessages.pop();
async_write(socket, buffer(currentOutgoing),
bind(&Downlink::writeCallback, this, _1, _2));
isWriting = true;
}
}
void Downlink::handleReadHeader(const boost::system::error_code& error,
std::size_t bytes_transferred) {
//TODO: how to handle disconnect on errors?
cout<<"handleReadHeader"<<endl;
if(error) {
return;
}
assert(bytes_transferred==headerSize);
if(bytes_transferred!=headerSize) {
cout<<"got "<<bytes_transferred<<" while waiting for a header."<<endl;
}
currentPacketID = readHeaderBuffer[0];
memcpy(¤tPacketLength, &readHeaderBuffer[1], sizeof(unsigned int));
dataStream.resize(currentPacketLength);
switch(currentPacketID) {
case MSG_FRAME_BGR: {
cout<<"- >> gone to read frame. ("<<currentPacketLength<<")"<<endl;
async_read(socket, asio::buffer(dataStream),
boost::asio::transfer_at_least(currentPacketLength),
bind(&Downlink::handleReadFrameDataBGR, this, _1, _2));
} break;
default: {
cout<<"->>> gone to read other. ("<<currentPacketLength<<")"<<endl;
cout<<" "<<(int)currentPacketID<<endl;
async_read(socket, asio::buffer(dataStream),
boost::asio::transfer_at_least(currentPacketLength),
bind(&Downlink::handleReadData, this, _1, _2));
} break;
}
}
void Downlink::handleReadData(const boost::system::error_code& error,
std::size_t bytes_transferred) {
cout<<"handleReadData"<<endl;
if(error) {
return;
}
if(bytes_transferred!=currentPacketLength) {
cout<<"Got "<<bytes_transferred<<" wanted "<<currentPacketLength<<endl;
}
assert(bytes_transferred==currentPacketLength);
switch(currentPacketID) {
case MSG_ASCII: {
string msg(dataStream.begin(), dataStream.end());
textCallback(&msg);
} break;
case MSG_IMU: {
Eigen::Vector3d a,g,m;
unsigned int stamp;
memcpy(a.data(), &dataStream[0], sizeof(double)*3);
memcpy(m.data(), &dataStream[0]+sizeof(double)*3, sizeof(double)*3);
memcpy(g.data(), &dataStream[0]+sizeof(double)*6, sizeof(double)*3);
memcpy(&stamp, &dataStream[0]+sizeof(double)*9, sizeof(unsigned int));
imuCallback(a,m,g,stamp);
} break;
default:
//TODO: handle this better?
cout<<"Unknown packet ID."<<endl;
}
async_read(socket, buffer(readHeaderBuffer),
boost::asio::transfer_at_least(headerSize),
bind(&Downlink::handleReadHeader, this, _1, _2));
}
void Downlink::handleReadFrameDataBGR(const boost::system::error_code& error,
std::size_t bytes_transferred) {
cout<<"Got a frame"<<endl;
if(error) {
return;
}
if(bytes_transferred!=currentPacketLength) {
cout<<"Got "<<bytes_transferred<<" wanted "<<currentPacketLength<<endl;
}
assert(bytes_transferred==currentPacketLength);
unsigned int imageWidth, imageHeight, cameraID;
unsigned char *readOffset = (unsigned char*)&dataStream[0];
memcpy(&imageWidth, readOffset, sizeof(unsigned int));
readOffset += sizeof(unsigned int);
memcpy(&imageHeight, readOffset, sizeof(unsigned int));
readOffset += sizeof(unsigned int);
memcpy(&cameraID, readOffset, sizeof(unsigned int));
readOffset += sizeof(unsigned int);
cout<<"("<<imageWidth<<"x"<<imageHeight<<") ID = "<<cameraID<<endl;
frameCallback(readOffset, imageWidth, imageHeight, cameraID);
async_read(socket, buffer(readHeaderBuffer),
boost::asio::transfer_at_least(headerSize),
bind(&Downlink::handleReadHeader, this, _1, _2));
}
boost::signals2::connection Downlink::connectTextDataCallback(boost::signals2::signal<void (std::string *)>::slot_type s) {
return textCallback.connect(s);
}
boost::signals2::connection Downlink::connectIMUDataCallback(boost::signals2::signal<void (Eigen::Vector3d, Eigen::Vector3d, Eigen::Vector3d, unsigned int)>::slot_type s) {
return imuCallback.connect(s);
}
boost::signals2::connection Downlink::connectVideoFrameCallback(boost::signals2::signal<void (unsigned char *, unsigned int, unsigned int, unsigned int)>::slot_type s) {
return frameCallback.connect(s);
}
Here is the code on the other end. It's almost exactly the same as the other code, but the error could be in either end.
using namespace std;
using namespace boost;
using namespace boost::asio;
Uplink::Uplink(unsigned int port) :
socket(nIO),
acceptor(nIO),
endpoint(ip::tcp::v4(), port),
headerSize(sizeof(unsigned int)+1), //id + data size
headerBuffer(headerSize)
{
//move socket into accept state.
acceptor.open(endpoint.protocol());
acceptor.set_option(ip::tcp::acceptor::reuse_address(true));
acceptor.bind(endpoint);
acceptor.listen(1); //1 means only one client in connect queue.
acceptor.async_accept(socket, bind(&Uplink::accept_handler, this, _1));
//start network thread.
nIO.reset();
t = thread(boost::bind(&boost::asio::io_service::run, &nIO));
}
Uplink::~Uplink() {
nIO.stop(); //tell the network thread to stop.
t.join(); //wait for the network thread to stop.
acceptor.close(); //close listen port.
socket.close(); //close active connections.
nIO.reset();
nIO.run(); //let clients know that we're disconnecting.
}
void Uplink::parse_header(const boost::system::error_code& error,
std::size_t bytes_transferred) {
if(error || bytes_transferred!=headerSize) {
disconnect();
return;
}
currentPacketID = headerBuffer[0];
memcpy(¤tPacketLength, &headerBuffer[1], sizeof(unsigned int));
//move to read data state
//TODO: move to different states to parse various packet types.
async_read(socket, asio::buffer(dataStream), transfer_at_least(currentPacketLength),
bind(&Uplink::parse_data, this, _1, _2));
}
void Uplink::parse_data(const boost::system::error_code& error,
std::size_t bytes_transferred) {
if(error) {
disconnect();
return;
}
if(bytes_transferred != currentPacketLength) {
cout<<"bytes_transferred != currentPacketLength"<<endl;
disconnect();
return;
}
//move back into the header reading state
async_read(socket, buffer(headerBuffer),
bind(&Uplink::parse_header, this, _1, _2));
}
void Uplink::disconnect() {
acceptor.close();
socket.close();
acceptor.open(endpoint.protocol());
acceptor.set_option(ip::tcp::acceptor::reuse_address(true));
acceptor.bind(endpoint);
acceptor.listen(1); //1 means only one client in connect queue.
acceptor.async_accept(socket, bind(&Uplink::accept_handler, this, _1));
}
void Uplink::accept_handler(const boost::system::error_code& error)
{
if (!error) {
//no more clents.
acceptor.close();
//move to read header state.
async_read(socket, buffer(headerBuffer),
bind(&Uplink::parse_header, this, _1, _2));
}
}
void Uplink::sendASCIIMessage(const std::string &m) {
//Format the message
unsigned int msgLength(m.length());
vector<char> outBuffer(msgLength+headerSize);
outBuffer[0] = MSG_ASCII;
memcpy(&outBuffer[1], &msgLength, sizeof(unsigned int));
vector<char>::iterator dataBegin(outBuffer.begin());
advance(dataBegin, headerSize);
copy(m.begin(), m.end(), dataBegin);
//queue the message
addToQueue(outBuffer);
}
void Uplink::sendIMUDataBlock(const nIMUDataBlock *d) {
//Format the message.
//a,g,m, 3 components each plus a stamp
const unsigned int msgLength(3*3*sizeof(double)+sizeof(unsigned int));
vector<char> outBuffer(msgLength+headerSize);
outBuffer[0] = MSG_IMU;
memcpy(&outBuffer[1], &msgLength, sizeof(unsigned int));
const Eigen::Vector3d a(d->getAccel());
const Eigen::Vector3d m(d->getMag());
const Eigen::Vector3d g(d->getGyro());
const unsigned int s(d->getUpdateStamp());
memcpy(&outBuffer[headerSize], a.data(), sizeof(double)*3);
memcpy(&outBuffer[headerSize+3*sizeof(double)], m.data(), sizeof(double)*3);
memcpy(&outBuffer[headerSize+6*sizeof(double)], g.data(), sizeof(double)*3);
memcpy(&outBuffer[headerSize+9*sizeof(double)], &s, sizeof(unsigned int));
/*
cout<<"----------------------------------------"<<endl;
cout<<"Accel = ("<<a[0]<<","<<a[1]<<","<<a[2]<<")"<<endl;
cout<<"Mag = ("<<m[0]<<","<<m[1]<<","<<m[2]<<")"<<endl;
cout<<"Gyro = ("<<g[0]<<","<<g[1]<<","<<g[2]<<")"<<endl;
cout<<"Stamp = "<<s<<endl;
cout<<"----------------------------------------"<<endl;
*/
//queue the message
addToQueue(outBuffer);
}
void Uplink::send_handler(const boost::system::error_code& error,
std::size_t bytes_transferred) {
{
lock_guard<mutex> l(queueLock);
lock_guard<mutex> l2(sendingLock);
if(outQueue.empty()) {
currentlySending = false;
return;
}
}
startSend();
}
void Uplink::addToQueue(const std::vector<char> &out) {
bool needsRestart = false;
{
lock_guard<mutex> l(queueLock);
lock_guard<mutex> l2(sendingLock);
outQueue.push(out);
needsRestart = !currentlySending;
}
if(needsRestart)
nIO.post(bind(&Uplink::startSend, this));
}
void Uplink::startSend() {
lock_guard<mutex> l(queueLock);
lock_guard<mutex> l2(sendingLock);
if(outQueue.empty())
return;
currentlySending = true;
currentWrite = outQueue.front();
outQueue.pop();
async_write(socket, buffer(currentWrite), bind(&Uplink::send_handler,
this, _1, _2));
}
void Uplink::sendVideoFrameBGR(const unsigned int width, const unsigned int height,
const unsigned int cameraID, const unsigned char *frameData) {
// image data image metadata header
const unsigned int packetSize(width*height*3 + sizeof(unsigned int)*3 + headerSize);
const unsigned int dataSize(width*height*3 + sizeof(unsigned int)*3);
vector<char> outgoingBuffer(packetSize);
outgoingBuffer[0] = MSG_FRAME_BGR;
memcpy(&outgoingBuffer[1], &dataSize, sizeof(unsigned int));
char *writePtr = &outgoingBuffer[headerSize];
memcpy(writePtr, &width, sizeof(unsigned int));
writePtr += sizeof(unsigned int);
memcpy(writePtr, &height, sizeof(unsigned int));
writePtr += sizeof(unsigned int);
memcpy(writePtr, &cameraID, sizeof(unsigned int));
writePtr += sizeof(unsigned int);
memcpy(writePtr, frameData, width*height*3*sizeof(char));
//TODO: can we avoid the whole image copy here?
//TODO: should come up with a better packet buffer build system.
//IDEA!: maybe have a "request buffer" funxction so the Uplink
//class can have sole ownership, rather than do the copy in "addtoQueue"
addToQueue(outgoingBuffer);
}
This program works most of the time, but only rarely, when sending a lot of data with no delay between packets it will fail.
For example:
sendVideoFrameBGR(...); //occasional fail
sendASCIIMessage("...");
sendVideoFrameBGR(...); //never fails.
sleep(1);
sendASCIIMessage("...");
after handling a video frame in Downlink it goes back to the hadleHeaderData and waits for a packet that is several megabytes in length and for a packet ID that doesn't exist. Somehow the stream is getting corrupted. I don't know why.
I don't really care much for the code I have written now, so if anybody knows of a good class or library to parse streams over TCP into buffer blocks for me I'd rather use that.
EDIT:
Here is the exact code that runs the sending of data:
if(frontImage) {
uplink.sendVideoFrameBGR(frontImage->width, frontImage->height, 0,
(unsigned char*)frontImage->imageData);
cout<<"Sent"<<endl;
//sleep(1); //works fine if this is uncommented !
}
uplink.sendASCIIMessage("Alive...");
sleep(1);
uplink.sendIMUDataBlock(imuDataBlock.get());
cout<<"Loop"<<endl;
sleep(1);
}
The problem is most likely that your ioservice object has more than one thread handling work.
When you call the second send function immediately after the first, the two function objects posted to the ioservice are probably being delegated to different threads. So basically, two writes are occurring on the same socket in parallel. This is most likely illegal. Using Winsock2 with non-blocking sockets, this would cause the outgoing data to be corrupted.
Even though you use a bool to check whether it's currently sending, the bool isn't checked until one of the ioservice threads is handling the function. If two ioservice threads are active when you post the two pieces of work, it could dispatch both sends at the same time, causing the two send functions to occur asynchronously on separate threads. The 'is currently sending' check may be returning false in both calls, since the two sends are running in parallel.