C++ TCP Proxy ASIO missing packets - c++

I am building a small application for my company. I will spare you on the details why, but I was requested to build a TCP Proxy for a specific application and protocol that we have.
For this Proxy, it would make a TCP connection to a server that would send us data periodically, and the Proxy would mirror the data received to two other applications on different ports. For this, I used C++ and ASIO library. I will spare you the full code, but here is (what I think) the important part regarding TCP Connections:
The class belowhandles the incoming data received by our Proxy. Data received here should be sent to our clients.
#include "TCPInConnection.h"
namespace Proxy
{
TCPInConnection::TCPInConnection(asio::io_context& io_context, std::string host, int port) :
host(host),
port(port),
io_context(&io_context),
socket(io_context)
{
}
TCPInConnection::~TCPInConnection()
{
Close();
}
//Starts connection to the server
void TCPInConnection::Start()
{
if(!socket.is_open())
connect();
}
void TCPInConnection::Close()
{
if (socket.is_open())
socket.close();
}
//Receives a callback. The callback passed should be called everytime a new packet was received
void TCPInConnection::OnReceive(std::function<std::vector<uint8_t>(std::vector<uint8_t>&)> callback)
{
this->callback = callback;
}
//Handles the connection. If an error occured while connecting, try connection again. When connecting, call receive handler to read data received
void TCPInConnection::connect()
{
socket.async_connect(asio::ip::tcp::endpoint(asio::ip::address::from_string(host), port), [this](const asio::error_code& error) {
if (error)
{
this->connect();
return;
}
asio::socket_base::receive_buffer_size option(8192 * 40);
socket.set_option(option);
receive();
});
}
//Handles the reception of data. For evey incoming packet, it should call itself to receive the next packet. If socket is closed, try to reconnect.
void TCPInConnection::receive()
{
std::array<char, 4000>* receiveBuffer = new std::array<char, 4000>();
socket.async_receive(asio::buffer(*receiveBuffer), 0, [this, receiveBuffer](const asio::error_code& error, std::size_t len) {
if (error == asio::error::eof || error == asio::error::connection_reset)
{
if (this->socket.is_open())
this->socket.close();
connect();
delete receiveBuffer;
return;
}
receive();
if (len == 0)
{
delete receiveBuffer;
return;
}
std::vector<uint8_t> packet(receiveBuffer->begin(), receiveBuffer->begin() + len);
auto data = callback(packet);
delete receiveBuffer;
if (data.size() == 0)
return;
std::vector<uint8_t>* output = new std::vector<uint8_t>(data.begin(), data.end());
socket.async_write_some(asio::buffer(*output), [output](const asio::error_code& error, std::size_t bytes_transferred) {
delete output;
});
});
}
}
The next class handles the output to our clients:
#include "TCPOutConnection.h"
namespace Proxy
{
TCPOutConnection::TCPOutConnection(asio::io_context& io_context, int port) :
io_context(&io_context),
acceptor(io_context, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)),
clientsMutex(),
clients()
{
}
TCPOutConnection::~TCPOutConnection()
{
Close();
}
//Starts listening for clients
void TCPOutConnection::Start()
{
connect();
}
void TCPOutConnection::Close()
{
std::lock_guard<std::mutex> guard(closingMutex);
if (acceptor.is_open())
acceptor.close();
{
std::lock_guard<std::mutex> guard(clientsMutex);
for (auto c = clients.begin(); c != clients.end(); ++c)
{
asio::ip::tcp::socket* socket = *c;
socket->close();
delete socket;
}
clients.clear();
}
}
//Sends data to each of the clients.
void TCPOutConnection::Send(std::vector<uint8_t> data)
{
if (data.size() == 0)
return;
std::lock_guard<std::mutex> guard(clientsMutex);
for (auto& socket : clients)
{
std::vector<uint8_t>* dataToSend = new std::vector<uint8_t>(data.begin(), data.end());
socket->async_send(asio::buffer(*dataToSend), [socket, this, dataToSend](const asio::error_code& error, std::size_t bytes_transferred) {
delete dataToSend;
if (!error)
return;
std::lock_guard<std::mutex> guard(clientsMutex);
clients.remove(socket);
});
}
}~
//Still not used. Would be implemented later.
void TCPOutConnection::OnDataReceived(std::function<void(std::vector<uint8_t>)> callback)
{
}
//Handles connection of new clients. Everytime a client comes, it should listen for another one
void TCPOutConnection::connect()
{
asio::ip::tcp::socket* socket = new asio::ip::tcp::socket(*io_context);
acceptor.async_accept(*socket, [socket, this](const asio::error_code& error) {
connect();
if (error)
{
delete socket;
return;
}
asio::ip::tcp::no_delay option1;
socket->set_option(option1);
asio::socket_base::receive_buffer_size option2(8192 * 40);
socket->set_option(option2);
std::lock_guard<std::mutex> guard(clientsMutex);
clients.push_back(socket);
});
}
}
Although the above works fine, I am losing some packets when our server is sending too much packets to the proxy (~15 packets per millisecond).
Is there anything I could do to avoid losing packets?

Related

Boost beast service returns "body limit exceeded" when receiving a json payload

I'm working on a project which implement a boost beast service.This part of code was written by a person who left the company and I do not master boot.
Until now it worked well but the size of the payload has increased and it no longer works. The payload is about 2.4MB.
The service is implemented using 3 classes ServerService, Listener and Session.
ServerService:
void ServerService::startServer(const std::string& address, const unsigned short& port,
const std::string& baseRessourceName, const unsigned short& threadNumber)
{
try
{
const auto srvAddress = boost::asio::ip::make_address(address);
// The io_context is required for all I/O
auto const nbThreads = std::max<int>(1, threadNumber);
boost::asio::io_context ioContext(nbThreads);
// Create listener and launch a listening port
std::shared_ptr<Listener> listener = std::make_shared<Listener>(ioContext, tcp::endpoint{ srvAddress, port }, baseRessourceName);
listener->run();
// Run the I/O service on the requested number of threads
std::vector<std::thread> threads;
threads.reserve(nbThreads - 1);
for (auto i = nbThreads - 1; i > 0; --i)
{
threads.emplace_back([&ioContext] { ioContext.run(); });
}
ioContext.run();
}
catch (std::exception const& e)
{
LBC_ERROR("{}", e.what());
}
}
Listener:
// Used namespace
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace Http
{
class Listener : public std::enable_shared_from_this<Listener>
{
private:
tcp::acceptor m_acceptor;
tcp::socket m_socket;
std::string const& m_baseResourceName;
// Report a failure
void logError(boost::system::error_code errorCode, char const* what)
{
LBC_ERROR("{}: {}", what, errorCode.message());
}
public:
Listener(boost::asio::io_context& ioContext, tcp::endpoint endpoint, std::string const& docRoot)
: m_acceptor(ioContext)
, m_socket(ioContext)
, m_baseResourceName(docRoot)
{
boost::system::error_code errorCode;
// Open the acceptor
m_acceptor.open(endpoint.protocol(), errorCode);
if (errorCode)
{
logError(errorCode, "open");
return;
}
// Allow address reuse
m_acceptor.set_option(boost::asio::socket_base::reuse_address(true));
if (errorCode)
{
logError(errorCode, "set_option");
return;
}
// Bind to the server address
m_acceptor.bind(endpoint, errorCode);
if (errorCode)
{
logError(errorCode, "bind");
return;
}
// Start listening for connections
m_acceptor.listen(boost::asio::socket_base::max_listen_connections, errorCode);
if (errorCode)
{
logError(errorCode, "listen");
return;
}
}
// Start accepting incoming connections
void run()
{
if (!m_acceptor.is_open()) {
return;
}
doAccept();
}
void doAccept()
{
m_acceptor.async_accept(m_socket,
std::bind(
&Listener::onAccept,
shared_from_this(),
std::placeholders::_1));
}
void onAccept(boost::system::error_code errorCode)
{
if (errorCode)
{
logError(errorCode, "accept");
}
else
{
// Create the session and run it
std::make_shared<Session>(
std::move(m_socket),
m_baseResourceName)->run();
}
// Accept another connection
doAccept();
}
};
} // namespace Http
Session:
// Used namespaces
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace boostHttp = boost::beast::http; // from <boost/beast/http.hpp>
namespace Http
{
class Session : public std::enable_shared_from_this<Session>
{
private:
// This is the C++11 equivalent of a generic lambda.
// The function object is used to send an HTTP message.
struct send_lambda
{
Session& self_;
explicit send_lambda(Session& self) : self_(self) {}
template<bool isRequest, class Body, class Fields>
void operator()(boostHttp::message<isRequest, Body, Fields>&& msg) const
{
// The lifetime of the message has to extend
// for the duration of the async operation so
// we use a shared_ptr to manage it.
auto sp = std::make_shared<boostHttp::message<isRequest, Body, Fields>>(std::move(msg));
// Store a type-erased version of the shared
// pointer in the class to keep it alive.
self_.res_ = sp;
// Write the response
boostHttp::async_write(self_.socket_, *sp,
boost::asio::bind_executor(
self_.strand_, std::bind(
&Session::onWrite,
self_.shared_from_this(),
std::placeholders::_1,
std::placeholders::_2,
sp->need_eof())));
}
};
// Report a failure
void logError(boost::system::error_code errorCode, char const* what)
{
LBC_ERROR("{}: {}", what, errorCode.message());
}
tcp::socket socket_;
boost::asio::strand<boost::asio::any_io_executor> strand_;
boost::beast::flat_buffer buffer_;
std::string const& baseResourceName_;
boostHttp::request<boostHttp::string_body> req_;
std::shared_ptr<void> res_;
send_lambda lambda_;
public:
// Take ownership of the socket
explicit Session(tcp::socket socket, std::string const& docRoot)
: socket_(std::move(socket))
, strand_(socket_.get_executor())
, baseResourceName_(docRoot)
, lambda_(*this)
{}
// Start the asynchronous operation
void run()
{
doRead();
}
void doRead()
{
// Make the request empty before reading,
// otherwise the operation behavior is undefined.
req_ = {};
// Read a request
boostHttp::async_read(socket_, buffer_, req_,
boost::asio::bind_executor(
strand_, std::bind(
&Session::onRead,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2)));
}
void onRead(boost::system::error_code errorCode, std::size_t transferredBytes)
{
boost::ignore_unused(transferredBytes);
// This means they closed the connection
if (errorCode == boostHttp::error::end_of_stream)
{
return doClose();
}
if (errorCode) {
return logError(errorCode, "*** read"); // Error is here
}
// Some stuff here to manage request
}
void onWrite(boost::system::error_code ec, std::size_t transferredBytes, bool close)
{
boost::ignore_unused(transferredBytes);
if (ec)
{
return logError(ec, "write");
}
if (close)
{
// This means we should close the connection, usually because
// the response indicated the "Connection: close" semantic.
return doClose();
}
// We're done with the response so delete it
res_ = nullptr;
// Read another request
doRead();
}
void doClose()
{
// Send a TCP shutdown
boost::system::error_code ec;
socket_.shutdown(tcp::socket::shutdown_send, ec);
// At this point the connection is closed gracefully
}
};
} // namespace Http
The service is launched as follow:
Service::ServerService serverService;
serverService.startServer("127.0.0.1", 8080, "service_name", 5);
I saw in the boost documentation that the default limit is 1MB. I tried some examples found on the internet to implement a parser and change the body limit but when I send a payload I get the following error "Unknown HTTP request" !
I hope someone can help me solve this problem. Thank you in advance for your answers.
First I made your code self-contained, more modern, simpler and stripped unused code. I chose libfmt to implement the logging requirements, showing how to use source location instead of tediously providing manual context.
Live On Coliru
#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <iostream>
namespace beast = boost::beast;
namespace http = beast::http;
namespace net = boost::asio;
using boost::system::error_code;
using net::ip::tcp;
#include <fmt/ranges.h>
#include <fmt/ostream.h>
template <> struct fmt::formatter<boost::source_location> : fmt::ostream_formatter {};
#define LBC_ERROR(FMTSTR, ...) fmt::print(stderr, FMTSTR "\n", __VA_ARGS__)
// Report a failure
static void inline logError(error_code ec, char const* what) {
LBC_ERROR("{}: {} from {}", what, ec.message(), ec.location());
}
static void inline logError(std::exception const& e) { logError({}, e.what()); }
namespace Http {
using namespace std::placeholders;
using Executor = net::any_io_executor;
class Session : public std::enable_shared_from_this<Session> {
private:
tcp::socket socket_;
std::string baseResourceName_; // TODO FIXME unused
boost::beast::flat_buffer buffer_;
http::request<http::string_body> req_;
public:
// Take ownership of the socket
explicit Session(tcp::socket socket, std::string docRoot)
: socket_(std::move(socket))
, baseResourceName_(std::move(docRoot)) {}
void run() {
std::cerr << "Started session for " << socket_.remote_endpoint() << std::endl;
doRead();
}
~Session() {
error_code ec;
auto ep = socket_.remote_endpoint(ec);
std::cerr << "Close session for " << ep << std::endl;
}
private:
void doRead() {
// Make the request empty before reading, otherwise the operation
// behavior is undefined.
req_.clear();
// Read a request
http::async_read(socket_, buffer_, req_,
std::bind(&Session::onRead, shared_from_this(), _1, _2));
}
void onRead(error_code ec, size_t transferredBytes) {
boost::ignore_unused(transferredBytes);
// This means they closed the connection
if (ec == http::error::end_of_stream) {
return doClose();
}
if (ec) {
return logError(ec, "*** read"); // Error is here
}
// Some stuff here to manage request
}
void onWrite(error_code ec, size_t transferredBytes, bool close) {
boost::ignore_unused(transferredBytes);
if (ec) {
return logError(ec, "write");
}
if (close) {
// This means we should close the connection, usually because
// the response indicated the "Connection: close" semantic.
return doClose();
}
// Read another request
doRead();
}
void doClose() {
// Send a TCP shutdown
error_code ec;
socket_.shutdown(tcp::socket::shutdown_send, ec);
// At this point the connection is closed gracefully
}
};
} // namespace Http
namespace Http {
class Listener : public std::enable_shared_from_this<Listener> {
private:
tcp::acceptor m_acceptor;
std::string m_baseResourceName;
public:
Listener(Executor ex, tcp::endpoint endpoint, std::string docRoot) try
: m_acceptor(ex)
, m_baseResourceName(std::move(docRoot)) //
{
m_acceptor.open(endpoint.protocol());
m_acceptor.set_option(tcp::acceptor::reuse_address(true));
m_acceptor.bind(endpoint);
m_acceptor.listen(tcp::socket::max_listen_connections);
} catch (boost::system::system_error const& se) {
logError(se.code(), "Listener");
throw;
}
// Start accepting incoming connections
void run() {
if (m_acceptor.is_open())
doAccept();
}
void doAccept() {
m_acceptor.async_accept(make_strand(m_acceptor.get_executor()),
std::bind(&Listener::onAccept, shared_from_this(), _1, _2));
}
void onAccept(error_code ec, tcp::socket sock) {
if (ec)
return logError(ec, "accept");
// Accept another connection / Create the session and run it
doAccept();
std::make_shared<Session>(std::move(sock), m_baseResourceName)->run();
}
};
void startServer(std::string address, uint16_t port, std::string docRoot, unsigned threads) {
try {
net::thread_pool ioc(std::max(1u, threads));
// Create listener and launch a listening port
tcp::endpoint ep{net::ip::make_address(address), port};
std::make_shared<Listener>( //
ioc.get_executor(), ep, std::move(docRoot))
->run();
// Run the I/O service on the requested number of threads
ioc.join();
} catch (std::exception const& e) {
logError(e);
}
}
} // namespace Http
int main() {
//Service::ServerService serverService;
/*serverService.*/ Http::startServer("127.0.0.1", 8989, "service_name", 5);
}
Particularly the send_lambda is not outdated (besides being unused), see message_generator instead
Reproducing
I can reproduce the error by replacing the data with something large enough:
Live On Coliru
dd of=test.bin seek=3 bs=1M count=0 status=none
curl -s http://127.0.0.1:8989/blrub -d #test.bin
Prints
Started session for 127.0.0.1:48884
*** read: body limit exceeded from (unknown source location)
Close session for 127.0.0.1:48884
Fixing
Indeed, you can set options on request_parser. Three lines of code changed:
http::request_parser<http::string_body> req_;
And
req_.get().clear();
req_.body_limit(8*1024*1024); // raised to 8Mb
Live On Coliru
With no further changes:
Prints
Started session for 127.0.0.1:48886
Close session for 127.0.0.1:48886

boost::asio - the acceptor doesn't call the callback after the server is stopped and started again

I've created a simple wrapper for boost::asio library. My wrapper consists of 4 main classes: NetServer (server), NetClient (client), NetSession (client/server session) and Network (composition class of these three which also includes all callback methods).
The problem is that the first connection client/server works flawlessly, but when I then stop server, start it again and then try to connect the client, the server just doesn't recognize the client. It seems like the acceptor callback isn't called. And client does connect to server, because first - the connection goes without errors, second - when I close the server's program, the client receives the error message WSAECONNRESET.
I've created test program which emulates the procedure written above. It does following:
Starts the server
Starts the client
Client succesfully connects to server
Stops the server
Client receives the error and disconnects itself
Starts the server again
Client again succesfully connects to server
BUT SERVER DOESN'T CALL THE ACCEPTOR CALLBACK ANYMORE
It means that in point 3 the acceptor succesfully calls the callback function, but in point 7 the acceptor doesn't call the callback.
I think I do something wrong in stop()/start() method of the server, but I can't figure out what's exactly wrong.
The source of the NetServer class:
NetServer::NetServer(Network& netRef) : net{ netRef }
{
acceptor = std::make_unique<boost::asio::ip::tcp::acceptor>(ioc);
}
NetServer::~NetServer(void)
{
ioc.stop();
if (threadStarted)
{
th.join();
threadStarted = false;
}
if (active)
stop();
}
int NetServer::start(void)
{
assert(getAcceptHandler() != nullptr);
assert(getHeaderHandler() != nullptr);
assert(getDataHandler() != nullptr);
assert(getErrorHandler() != nullptr);
closeAll();
try
{
ep = boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), srvPort);
acceptor->open(ep.protocol());
acceptor->bind(ep);
acceptor->listen();
initAccept();
}
catch (system::system_error& e)
{
return e.code().value();
}
if (!threadStarted)
{
th = std::thread([this]()
{
ioc.run();
});
threadStarted = true;
}
active = true;
return Network::NET_OK;
}
int NetServer::stop(void)
{
ioc.post(boost::bind(&NetServer::_stop, this));
return Network::NET_OK;
}
void NetServer::_stop(void)
{
boost::system::error_code ec;
acceptor->close(ec);
for (auto& s : sessions)
closeSession(s.get(), false);
active = false;
}
void NetServer::initAccept(void)
{
sock = std::make_shared<asio::ip::tcp::socket>(ioc);
acceptor->async_accept(*sock.get(), [this](const boost::system::error_code& error)
{
onAccept(error, sock);
});
}
void NetServer::onAccept(const boost::system::error_code& ec, SocketSharedPtr sock)
{
if (ec.value() == 0)
{
if (accHandler())
{
addSession(sock);
initAccept();
}
}
else
getErrorHandler()(nullptr, ec);
}
SessionPtr NetServer::addSession(SocketSharedPtr sock)
{
std::lock_guard<std::mutex> guard(mtxSession);
auto session = std::make_shared<NetSession>(sock, *this, true);
sessions.insert(session);
session->start();
return session;
}
SessionPtr NetServer::findSession(const SessionPtr session)
{
for (auto it = std::begin(sessions); it != std::end(sessions); it++)
if (*it == session)
return *it;
return nullptr;
}
bool NetServer::closeSession(const void *session, bool erase /* = true */)
{
std::lock_guard<std::mutex> guard(mtxSession);
for (auto it = std::begin(sessions); it != std::end(sessions); it++)
if (it->get() == session)
{
try
{
it->get()->getSocket()->cancel();
it->get()->getSocket()->shutdown(asio::socket_base::shutdown_send);
it->get()->getSocket()->close();
it->get()->getSocket().reset();
}
catch (system::system_error& e)
{
UNREFERENCED_PARAMETER(e);
}
if (erase)
sessions.erase(*it);
return true;
}
return false;
}
void NetServer::closeAll(void)
{
using namespace boost::placeholders;
std::lock_guard<std::mutex> guard(mtxSession);
std::for_each(sessions.begin(), sessions.end(), boost::bind(&NetSession::stop, _1));
sessions.clear();
}
bool NetServer::write(const SessionPtr session, std::string msg)
{
if (SessionPtr s = findSession(session); s)
{
s->addMessage(msg);
if (s->canWrite())
s->write();
return true;
}
return false;
}
This is the output from the server:
Enter 0 - server, 1 - client: 0
1. Server started
3. Client connected to server
Stopping server....
4. Server stopped
Net error, server, acceptor: ERROR_OPERATION_ABORTED
Net error, server, ERROR_OPERATION_ABORTED
Client session deleted
6. Server started again
(HERE SHOULD BE "8. Client again connected to server", but the server didn't recognize the reconnected client!)
And from the client:
Enter 0 - server, 1 - client: 1
2. Client started and connected to server
Net error, client: ERROR_FILE_NOT_FOUND
5. Client disconnected from server
Waiting 3 sec before reconnect...
Connecting to server...
7. Client started and connected to server
(WHEN I CLOSE THE SERVER WINDOW, I RECVEIVE HERE THE "Net error, client: WSAECONNRESET" MESSAGE - it means client was connected to server anyhow!)
If the code of NetClient, NetSession and Network is necessary, just let me know.
Thanks in advance
Wow. There's a lot to unpack. There is quite a lot of code smell that reminds me of some books on Asio programming that turned out to be... not excellent in my previous experience.
I couldn't give any real advice without grokking your code, which requires me to review in-depth and add missing bits. So let me just provide you with my reviewed/fixed code first, then we'll talk about some of the details.
A few areas where you seemed to have trouble making up your mind:
whether to use a strand or to use mutex locking
whether to use async or sync (e.g. closeSession is completely synchronous and blokcing)
whether to use shared-pointers for lifetime or not: on the one hand you have NetSesion support shared_from_this, but on the other hand you are keeping them alive in a sessions collection.
whether to use smart pointers or raw pointers (sp.get() is a code smell)
whether to use void* pointers or forward declared structs for opaque implementation
whether to use exceptions or to use error codes. Specifically:
return e.code().value();
is a Very Bad Idea. Just return error_code already. Or just propagate the exception.
judging from the use, my best bet is that sessions is std::set<SessionPtr>. Then it's funny that you're doing linear searches. In fact, findSession could be:
SessionPtr findSession(SessionPtr const& session) {
std::lock_guard guard(mtxSessions);
return sessions.contains(session)? session: nullptr;
}
In fact, given some natural invariants, it could just be
auto findSession(SessionPtr s) { return std::move(s); }
Note as well, you had forgotten to lock the mutex in findSession
closeSession completely violates Law Of Demeter, 6*3 times over if you will. In my example I make it so SessHandle is a weak pointer to NetSession and you can just write:
for (auto& handle : sessions)
if (auto sess = handle.lock())
sess->close();
Of course, sess->close() should not block
Also, it should correctly synchronize on the session e.g. using the sessions strand:
void close() {
return post(sock_.get_executor(), [this, self = shared_from_this()] {
error_code ec;
if (!ec) sock_.cancel(ec);
if (!ec) sock_.shutdown(tcp::socket::shutdown_send, ec);
if (!ec) sock_.close(ec);
});
}
If you insist, you can make it so the caller can still await the result and receive any exceptions:
std::future<void> close() {
return post(
sock_.get_executor(),
std::packaged_task<void()>{[this, self = shared_from_this()] {
sock_.cancel();
sock_.shutdown(tcp::socket::shutdown_send);
sock_.close();
}});
}
Honestly, that seems overkill since you never look at the return value anyways.
In general, I recommend leaving socket::close() to the destructor. It avoids a specific class of race-conditions on socket handles.
Don't use boolean flags (isThreadActive is better replaced with th.joinable())
apparently you had NetSession::stop which I imagine did largely the same as closeSession but in the right place? I replaced it with the new NetSession::close
subtly when accHandler returned false, you would exit the accept loop alltogether. I doubt that was on purpose
try to minimize time under locks:
std::future<void> close() {
return post(
sock_.get_executor(),
std::packaged_task<void()>{[this, self = shared_from_this()] {
sock_.cancel();
sock_.shutdown(tcp::socket::shutdown_send);
sock_.close();
}});
}
I will show you how to do without the lock entirely instead.
Demo Listing
#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <deque>
#include <iostream>
#include <iomanip>
#include <set>
using namespace std::chrono_literals;
using namespace std::placeholders;
namespace asio = boost::asio;
using asio::ip::tcp;
using boost::system::error_code;
static inline std::ostream debug(std::cerr.rdbuf());
struct Network {
static constexpr error_code NET_OK{};
};
struct NetSession; // opaque forward reference
struct NetServer;
using SessHandle = std::weak_ptr<NetSession>; // opaque handle
using Sessions = std::set<SessHandle, std::owner_less<>>;
struct NetSession : std::enable_shared_from_this<NetSession> {
NetSession(tcp::socket&& s, NetServer& srv, bool)
: sock_(std::move(s))
, srv_(srv) {
debug << "New session from " << getPeer() << std::endl;
}
void start() {
post(sock_.get_executor(),
std::bind(&NetSession::do_read, shared_from_this()));
}
tcp::endpoint getPeer() const { return peer_; }
void close() {
return post(sock_.get_executor(), [this, self = shared_from_this()] {
debug << "Closing " << getPeer() << std::endl;
error_code ec;
if (!ec) sock_.cancel(ec);
if (!ec) sock_.shutdown(tcp::socket::shutdown_send, ec);
// if (!ec) sock_.close(ec);
});
}
void addMessage(std::string msg) {
post(sock_.get_executor(),
[this, msg = std::move(msg), self = shared_from_this()] {
outgoing_.push_back(std::move(msg));
if (canWrite())
write_loop();
});
}
private:
// assumed on (logical) strand
bool canWrite() const { // FIXME misnomer: shouldStartWriteLoop()?
return outgoing_.size() == 1;
}
void write_loop() {
if (outgoing_.empty())
return;
async_write(sock_, asio::buffer(outgoing_.front()),
[this, self = shared_from_this()](error_code ec, size_t) {
if (!ec) {
outgoing_.pop_front();
write_loop();
}
});
}
void do_read() {
incoming_.clear();
async_read_until(
sock_, asio::dynamic_buffer(incoming_), "\n",
std::bind(&NetSession::on_read, shared_from_this(), _1, _2));
}
void on_read(error_code ec, size_t);
tcp::socket sock_;
tcp::endpoint peer_ = sock_.remote_endpoint();
NetServer& srv_;
std::string incoming_;
std::deque<std::string> outgoing_;
};
using SessionPtr = std::shared_ptr<NetSession>;
using SocketSharedPtr = std::shared_ptr<tcp::socket>;
struct NetServer {
NetServer(Network& netRef) : net{netRef} {}
~NetServer()
{
if (acceptor.is_open())
acceptor.cancel(); // TODO seems pretty redundant
stop();
if (th.joinable())
th.join();
}
std::function<bool()> accHandler;
std::function<void(SocketSharedPtr, error_code)> errHandler;
// TODO headerHandler
std::function<void(SessionPtr, error_code, std::string)> dataHandler;
error_code start() {
assert(accHandler);
assert(errHandler);
assert(dataHandler);
closeAll(sessions);
error_code ec;
if (!ec) acceptor.open(tcp::v4(), ec);
if (!ec) acceptor.bind({{}, srvPort}, ec);
if (!ec) acceptor.listen(tcp::socket::max_listen_connections, ec);
if (!ec) {
do_accept();
if (!th.joinable()) {
th = std::thread([this] { ioc.run(); }); // TODO exceptions!
}
}
if (ec && acceptor.is_open())
acceptor.close();
return ec;
}
void stop() { //
post(ioc, std::bind(&NetServer::do_stop, this));
}
void closeSession(SessHandle handle, bool erase = true) {
post(acceptor.get_executor(), [=, this] {
if (auto s = handle.lock()) {
s->close();
}
if (erase) {
sessions.erase(handle);
}
});
}
void closeAll() {
post(acceptor.get_executor(), [this] {
closeAll(sessions);
sessions.clear();
});
}
// TODO FIXME is the return value worth it?
bool write(SessionPtr const& session, std::string msg) {
return post(acceptor.get_executor(),
std::packaged_task<bool()>{std::bind(
&NetServer::do_write, this, session, std::move(msg))})
.get();
}
// compare
void writeAll(std::string msg) {
post(acceptor.get_executor(),
std::bind(&NetServer::do_write_all, this, std::move(msg)));
}
private:
Network& net;
asio::io_context ioc;
tcp::acceptor acceptor{ioc}; // active -> acceptor.is_open()
std::thread th; // threadActive -> th.joinable()
Sessions sessions;
std::uint16_t srvPort = 8989;
// std::mutex mtxSessions; // note naming; also replaced by logical strand
// assumed on acceptor logical strand
void do_accept() {
acceptor.async_accept(
make_strand(ioc), [this](error_code ec, tcp::socket sock) {
if (ec.failed()) {
return errHandler(nullptr, ec);
}
if (accHandler()) {
auto s = std::make_shared<NetSession>(std::move(sock),
*this, true);
sessions.insert(s);
s->start();
}
do_accept();
});
}
SessionPtr do_findSession(SessionPtr const& session) {
return sessions.contains(session) ? session : nullptr;
}
bool do_write(SessionPtr session, std::string msg) {
if (auto s = do_findSession(session)) {
s->addMessage(std::move(msg));
return true;
}
return false;
}
void do_write_all(std::string msg) {
for(auto& handle : sessions)
if (auto sess = handle.lock())
do_write(sess, msg);
}
static void closeAll(Sessions const& sessions) {
for (auto& handle : sessions)
if (auto sess = handle.lock())
sess->close();
}
void do_stop()
{
if (acceptor.is_open()) {
error_code ec;
acceptor.close(ec); // TODO error handling?
}
closeAll(sessions); // TODO FIXME why not clear sessions?
}
};
// Implementation must be after NetServer definition:
void NetSession::on_read(error_code ec, size_t) {
if (srv_.dataHandler)
srv_.dataHandler(shared_from_this(), ec, std::move(incoming_));
if (!ec)
do_read();
}
int main() {
Network net;
NetServer srv{net};
srv.accHandler = [] { return true; };
srv.errHandler = [](SocketSharedPtr, error_code ec) {
debug << "errHandler: " << ec.message() << std::endl;
};
srv.dataHandler = [](SessionPtr sess, error_code ec, std::string msg) {
debug << "dataHandler: " << sess->getPeer() << " " << ec.message()
<< " " << std::quoted(msg) << std::endl;
};
srv.start();
std::this_thread::sleep_for(10s);
std::cout << "Shutdown started" << std::endl;
srv.writeAll("We're going to shutdown, take care!\n");
srv.stop();
}
Live Demo:

Boost full duplex - only client to server works, messages from server to client

I have a boost asio project that I am at wits end with. I have defined a TCPConnect class which is inherited by both a TCPSession and a TCPClient class. The reason for this is because I would like both the server and client side of the connection to be directly used for sending and receiving messages over full duplex TCP/IP. Only the TCPSession class listens for new connections, and only the TCPClient class makes an outgoing connection.
When the client connects to the server it sends a handshake message and blocks for the response. The server receives the message and sends a handshake ack back. On sending the ack, the server considers the connection completed. On receiving the ack, the client considers the connection completed.
The problem that I am having is that only the client side (the TCPClient object) can call its inherited TCPConnect::Send() and have it be received by the remote TCPSession object. If the server side (the TCPSession object) calls TCPConnect::Send(), it puts the message on the line without a problem, but the message is never received by the remote TCPClient object.
I must say I am a total beginner at working with boost. I have looked into this issue, trying to word and reword my search query, but have not found a solution. On both sides I have the TCPSession and TCPClient objects sitting on an async_read(), but on the client side the callback for the async_read() does not get called when the server side sends a message. I am assuming this has to do with how the io_service is set up on the TCPSession object's side, and possibly with threading.
So, a TCPServer is started inside a TCPWorker::run() function, this function being run in its own thread. The TCPWorker class owns the io_service object. The threaded run() function instantiates a TCPServer object and then calls io_service::run(). The TCPServicer object is responsible for creating TCPSession objects for new connections. For each new connection, the TCPServer creates a new TCPSession object with the io_service pointer and calls async_accept on its acceptor. Once a new connection is accepted, the TCPSession's socket is set for an async_read(). So it is known that io_service and the multiple sockets that can be created to use it are in one thread.
On the TCPClient side, when Connect() is called, if an io_service doesn't exist and/or a thread does not yet exist for the io_service, they are created using the following lines:
if (!m_pClientServiceThread) {
if (!m_pclient_io_service) // if no io_service has yet been created
m_pclient_io_service = new boost::asio::io_service;
m_pClientServiceThread = new boost::thread(boost::bind(&boost::asio::io_service::run, m_pclient_io_service));
}
So the service is being run in a thread. Then a new resolver and a new boost::asio::ip::tcp::socket are created given the io_service pointer, and boost::asio::connect() is called given the socket and a valid resolved endpoint. So the client seems to have its io_service running in a thread. Having successfully made the connection, I then send a handshake message using boost::asio::read(), and sit with boost::asio::read() waiting on the handshare response. One receiving a valid response, I pass the socket to an async_read() to wait for incoming messages.
I have looked at this for so long now without figuring out why the client side async_read() does no receive a message sent from the server side, even though the server side receives the message that is sent from the client side.
Please help me to figure this out. I am quite sure there is something simple I am not seeing, but as I have said, I am not a boost expert, so I am not sure what it is.
Added code:
TcpConnect class:
class TcpConnect : public boost::enable_shared_from_this<TcpConnect>
{
TcpConnect(boost::asio::io_service* pio_service, ConnType connType, std::string sHostIp, int iHostPort)
: m_pio_service(pio_service)
, eConnType(connType)
, m_strHostIp(sHostIp)
, m_iHostPort(iHostPort)
{
}
virtual ~TcpConnect() { /* does what is needed - this works */ }
bool SendBlocked(CmdPacket& msg, CmdPacket& rsp)
{
// Function used for handling connection handshake response
std::size_t write_length = boost::asio::write( *m_pSocket, boost::asio::buffer(msg.Serialize(), (std::size_t)msg.SerializedLength()));
if (msg.SerializedLength() != write_length)
{
return false;
}
boost::asio::streambuf sbuff;
boost::system::error_code error;
size_t reply_length(0);
// read header for message body length
byte* buffer = rsp.CreateBuffer(MSG_HEADER_LENGTH);
reply_length = boost::asio::read(*m_pSocket, boost::asio::buffer(buffer, MSG_HEADER_LENGTH), boost::asio::transfer_exactly(MSG_HEADER_LENGTH), error);
if (error || !rsp.ReadMessageLength())
{
/* error handled here */
return false;
}
// read message body
int expectedlen = rsp.BodyLength();
buffer = rsp.CreateBuffer(expectedlen);
reply_length = boost::asio::read(*m_pSocket, boost::asio::buffer(buffer, expectedlen), boost::asio::transfer_exactly(expectedlen), error);
if (error)
{
/* error handled here */
return false;
}
if (!rsp.Deserialize() || reply_length != rsp.BodyLength())
{
/* error handled here */
return false;
}
return true;
}
bool Send(CmdPacket& msg)
{
bool bStatus = true;
size_t write_length = 0;
try
{
write_length = boost::asio::write( *m_pSocket, boost::asio::buffer(msg.Serialize(), (std::size_t)msg.SerializedLength()) );
}
catch (...)
{
/* error handled here */
return false;
}
if (write_length != msg.SerializedLength())
{
/* error handled here */
return false;
}
return true;
}
void StartAsyncRead()
{
m_pInMsg = new CmdPacket();
boost::asio::async_read(*m_pSocket, boost::asio::buffer(m_pInMsg->CreateBuffer(MSG_HEADER_LENGTH), MSG_HEADER_LENGTH),
boost::bind(&TcpConnect::handle_read_header, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_read_header(const boost::system::error_code& error, size_t bytes_transferred)
{
if (!error && bytes_transferred == MSG_HEADER_LENGTH && m_pInMsg->ReadMessageLength())
{
boost::asio::async_read(*m_pSocket,
boost::asio::buffer(m_pInMsg->CreateBuffer(m_pInMsg->SerializedLength()), m_pInMsg->SerializedLength()),
boost::bind(&TcpConnect::handle_read_body, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else if (error)
{
/* errors handled */
}
}
void handle_read_body(const boost::system::error_code& error, size_t bytes_transferred)
{
bool deserialized = false;
if (!error && (deserialized = m_pInMsg->Deserialize()))
{
// if not yet connected, expecting handshake message, in which case acknowledge, otherwise error
if (m_pInMsg->IsHandshake())
{
m_pInMsg->SetAcknowledge(true);
std::size_t write_length = boost::asio::write(
*m_pSocket, boost::asio::buffer(m_pInMsg->Serialize(), (std::size_t)m_pInMsg->SerializedLength()));
if (write_length == m_pInMsg->SerializedLength())
{
/* we sent the acknowledgement, so we consider we're connected */
}
else
{
/* handling error here */
return;
}
delete m_pInMsg;
m_pInMsg = NULL;
}
// if graceful disconnect, notify the connection manager of new status, which will remove the connection from the map
else if (m_pInMsg->IsDisconnect())
{
/* disconnect request handled here */
return;
}
else
{
/* message received, passing it to the local process here */
}
// set up to handle the next read
m_pInMsg = new CmdPacket;
boost::asio::async_read(*m_pSocket, boost::asio::buffer(m_pInMsg->CreateBuffer(MSG_HEADER_LENGTH), MSG_HEADER_LENGTH),
boost::bind(&TcpConnect::handle_read_header, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else if (error)
{
/* handle case where boost error */
}
else if (!deserialized)
{
/* handle case where message not correctly deserialized */
}
}
protected:
ConnType eConnType;
boost::asio::ip::tcp::socket* m_pSocket{ 0 };
boost::asio::io_service* m_pio_service;
boost::asio::ip::tcp::resolver::iterator m_itEndpoint;
CmdPacket* m_pInMsg{ 0 };
std::string m_strHostIp;
int m_iHostPort;
};
typedef boost::shared_ptr<TcpConnect> ConnectionPtr;
TcpClient class:
class TcpClient : public TcpConnect
{
public:
TcpClient(boost::asio::io_service& io_service, ConnType connType, const std::string& sIP, int iPort)
: TcpConnect(&io_service, connType, sIP, iPort)
{
}
~TcpClient() { /* does what is needed - this works */ }
//virtual ObjType Type() { return OT_CLIENT; }
//virtual int sessionId() { return -1; } // client end does not have a session id
//Use the following to initialize and to to reestablish the connection.
bool Connect()
{
bool bStatus = true;
//Convert the port to a string
std::stringstream ss;
ss << m_iHostPort;
std::string strPort = ss.str();
//Establish the connection
try
{
boost::system::error_code ec;
// create TCP resolver and query and resolve the endpoint
boost::asio::ip::tcp::resolver resolver(*m_pio_service);
boost::asio::ip::tcp::resolver::query query(m_strHostIp.c_str(), strPort.c_str());
boost::asio::ip::tcp::resolver::iterator m_iterEndpoint = resolver.resolve(query, ec);
if (ec)
{
/* error handled here */
bStatus = false;
}
else
{
// close an old socket (shouldn't ever be the case)
if (m_pSocket != NULL) CloseSocket(); /* NOTE: this is defined in TcpConnect, but not shown here */
// create the socket on the io_service object and connect to the endpoint
m_pSocket = new boost::asio::ip::tcp::socket(*m_pio_service);
boost::asio::connect(*m_pSocket, m_iterEndpoint, ec);
if (ec)
{
/* error handled here */
bStatus = false;
}
}
} //end try
catch(...)
{
/* error handled here */
bStatus = false;
}
return bStatus;
}
};
typedef boost::shared_ptr<TcpClient> TcpClientPtr;
TcpServer class (run by TcpWorker and creates TcpSession objects):
class TcpServer;
class TcpSession : public TcpConnect
{
public:
TcpSession(boost::asio::io_service& io_service)
: TcpConnect(&io_service)
, m_session_id(next_session_id())
, m_pSocket(new tcp::socket(io_service))
{
}
virtual ~TcpSession() { /* NOTE: m_pSocket is owned and deleted by TcpConnect */ }
private:
int next_session_id()
{
static int id = 0;
return (++id > 0) ? id : 1;
}
private:
int m_session_id;
};
typedef boost::shared_ptr<TcpSession> TcpSessionPtr;
class TcpServer
{
public:
TcpServer(boost::asio::io_service& io_service, short port)
: m_pio_service(&io_service)
, m_acceptor(io_service, tcp::endpoint(tcp::v4(), port))
{
m_acceptor.listen();
}
~TcpServer()
{
boost::system::error_code errorcode;
m_acceptor.close(errorcode);
}
void start_accept()
{
TcpSessionPtr new_session(new TcpSession(*m_pio_service));
// start listening for this session
m_acceptor.async_accept(new_session->socket(),
boost::bind(&TcpServer::handle_accept, this, new_session,
boost::asio::placeholders::error));
new_session.reset();
}
private:
void handle_accept(TcpSessionPtr new_session, const boost::system::error_code& error)
{
if (!error)
{
new_session->StartAsyncRead(); /* NOTE: there is code for aggregating session objects */
/* NOTE: The result of an async_read() will be handled in TcpConnect::handle_read_header() */
}
else
{
/* error handled here */
}
// listen for the next connection
start_accept();
}
private:
boost::asio::io_service* m_pio_service;
tcp::acceptor m_acceptor;
};
class TcpWorker
{
public:
TcpWorker(unsigned int port)
: m_port(port)
{}
~TcpWorker() {}
void StopWorker()
{
if (!m_io_service.stopped())
{
m_io_service.stop();
while (!m_io_service.stopped()) { boost::this_thread::sleep(boost::posix_time::milliseconds(1)); }
}
}
void operator()() // threaded run function started from Communicator::Listen()
{
TcpServer server(m_io_service, (short)m_port);
server.start_accept(); // set up async_accept() for listening
std::size_t inumhandlers = m_io_service.run(); // blocks here until StopWorker() is called
}
private:
unsigned int m_port;
boost::asio::io_service m_io_service;
bool m_running;
};
Communicator class:
class Communicator {
public:
Communicator() = default;
~Communicator() { /* does what is needed - this works */ }
bool Listen()
{
if (!m_pServerThread || !m_pServerWorker)
{
m_pServerWorker = new TcpWorker(m_myPort);
m_pServerThread = new boost::thread(&TcpWorker::operator(), m_pServerWorker);
return true;
}
return false;
}
bool Connect(int srcId, int destId, std::string ipaddr, int port)
{
bool ret = false;
if (connected(destId))
{
ret = true;
}
else
{
// if io_service is not running, start it (happens if never started, or if no remaining client sockets running)
if (!ClientThreadRunning())
{
if (m_pClientThread) // since going to create a new thread, make sure this one is deleted if exists
delete m_pClientThread;
if (!m_pclient_io_service) // if no io_service has yet been created
m_pclient_io_service = new boost::asio::io_service;
m_pClientServiceThread = new boost::thread(boost::bind(&boost::asio::io_service::run, m_pclient_io_service));
}
// create the connection. Wait for Ack before returning.
TcpClientPtr client(new TcpClient(*m_pclient_io_service, destId, ip, port));
// connect to the client and do a handshake
if (client->Connect())
{
// if an initial handshake works, we're connected
CmdPacket msg(CMD_NONE, srcId, destId, port, ipaddr), rsp;
msg.SetHandshake(); // this starts the handshake protocol, which is completed on receiving the necessary response.
if (!client->SendBlocked(msg, rsp) || rsp != msg)
{
client.reset();
ret = false;
}
else
{
// Connected, now set up for asynchronous reading
client->StartAsyncRead(m_pclient_io_service);
// save it in the class
connection = client;
ret = true;
}
}
// decrement reference count, if not added to shared pointer map, this will set client object for deletion
client.reset();
}
return ret;
}
bool sendMessage(CmdPacket& msg)
{
bool bret = false;
if (connection != nullptr)
{
iter->second->Send(msg);
}
return bret;
}
private:
TcpConnect *connection{ 0 };
TcpWorker* m_pServerWorker{ 0 };
boost::thread *m_pServerThread{ 0 };
boost::thread* m_pClientThread{ 0 };
boost::asio::io_service* m_pclient_io_service{ 0 };
ConnectionPtr connection{ 0 };
};
Your code is not self-contained. It's also out of synch (TcpConnect constructors do not match the initializer lists, AsyncStartRead is called with an argument). It looks like you might have duplicated m_pSocket members in the TcpSession and TcpConnect classes, namely:
is very contradictory.
There's a unhealthy reliance on pointers obscuring ownership and lifetime.
If you actually enable compiler warnings, the compiler will let you know that several member variables are is not initialized in the order you seem to expect (based on the ordering in the constructor's initializer lists).
There is the code smell of race conditions (sleep in a loop with a boolean check?). Unused variabled (m_running?).
Undeclared variables with very confused names (what is m_pClientServiceThread? What makes it different from m_pServerThread and m_pClientThread?
Why is connection declared twice, once as a raw pointer, once as a shared_ptr?
Many functions are left out (connected? ClientThreadRunning?), variables (mentioned above, ip, iter comes out of nowhere), etc.
All in all, I stopped trying to make the sample compile after an hour or two:
Broken on Coliru
#undef NDEBUG // make sure we assert
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <boost/make_shared.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/endian/arithmetic.hpp>
#include <boost/json.hpp>
#include <boost/json/src.hpp>
#include <boost/thread.hpp>
namespace json = boost::json;
using byte = char;
using boost::asio::ip::tcp;
enum class ConnType {
Session
};
using Big32 = boost::endian::big_uint32_t;
static constexpr size_t MSG_HEADER_LENGTH = 2 * sizeof(Big32);
enum CmdType {
CMD_NONE,
CMD_HANDSHAKE,
CMD_ACK,
CMD_DISCONNECT,
};
struct CmdPacket {
std::string _buf;
json::value _data; // { "cmd_type": MsgType, "value": json::value }
bool IsHandshake() const { return _data.at("cmd_type") == CMD_HANDSHAKE; }
bool IsDisconnect() const{ return _data.at("cmd_type") == CMD_DISCONNECT; }
void SetAcknowledge(json::value v) {
auto& o = _data.as_object();
o["cmd_type"] = CMD_ACK;
o["value"] = v;
}
void SetHandshake() { _data.as_object()["cmd_type"] = CMD_HANDSHAKE; }
byte* CreateBuffer(uint32_t n) { _buf.assign(n, '\0'); return _buf.data(); }
uint32_t ReadMessageLength() const {
assert(_buf.size() >= sizeof(Big32));
boost::endian::big_uint32_t decode;
memcpy(&decode, _buf.data(), sizeof(decode));
return decode;
}
uint32_t BodyLength() const {
return ReadMessageLength();
}
std::string Serialize() const { return json::serialize(_data); }
bool Deserialize() {
json::error_code ec;
_data = json::parse(_buf, ec);
return !ec;
}
size_t SerializedLength() const { return Serialize().length(); }
};
class TcpConnect : public boost::enable_shared_from_this<TcpConnect> {
public:
virtual ~TcpConnect()
{ /* does what is needed - this works */
}
auto& socket() { assert(m_pSocket); return *m_pSocket; }
auto& socket() const { assert(m_pSocket); return *m_pSocket; }
protected:
TcpConnect(boost::asio::io_service* pio_service, ConnType connType,
std::string sHostIp, uint16_t iHostPort)
: m_pio_service(pio_service)
, eConnType(connType)
, m_pSocket(new tcp::socket(*pio_service))
, m_strHostIp(sHostIp)
, m_iHostPort(iHostPort)
{
}
void CloseSocket() {
if (m_pSocket)
m_pSocket->close();
}
bool SendBlocked(CmdPacket& msg, CmdPacket& rsp)
{
// Function used for handling connection handshake response
std::size_t write_length = boost::asio::write(
*m_pSocket,
boost::asio::buffer(msg.Serialize(),
(std::size_t)msg.SerializedLength()));
if (msg.SerializedLength() != write_length) {
return false;
}
boost::asio::streambuf sbuff;
boost::system::error_code error;
size_t reply_length(0);
// read header for message body length
byte* buffer = rsp.CreateBuffer(MSG_HEADER_LENGTH);
reply_length = boost::asio::read(
*m_pSocket, boost::asio::buffer(buffer, MSG_HEADER_LENGTH),
boost::asio::transfer_exactly(MSG_HEADER_LENGTH), error);
if (error || !rsp.ReadMessageLength()) {
/* error handled here */
return false;
}
// read message body
int expectedlen = rsp.BodyLength();
buffer = rsp.CreateBuffer(expectedlen);
reply_length = boost::asio::read(
*m_pSocket, boost::asio::buffer(buffer, expectedlen),
boost::asio::transfer_exactly(expectedlen), error);
if (error) {
/* error handled here */
return false;
}
if (reply_length != rsp.BodyLength() || !rsp.Deserialize()) {
/* error handled here */
return false;
}
return true;
}
bool Send(CmdPacket& msg)
{
//bool bStatus = true;
size_t write_length = 0;
try {
write_length = boost::asio::write(
*m_pSocket,
boost::asio::buffer(msg.Serialize(),
(std::size_t)msg.SerializedLength()));
} catch (...) {
/* error handled here */
return false;
}
if (write_length != msg.SerializedLength()) {
/* error handled here */
return false;
}
return true;
}
public:
void StartAsyncRead()
{
m_pInMsg = new CmdPacket();
boost::asio::async_read(
*m_pSocket,
boost::asio::buffer(m_pInMsg->CreateBuffer(MSG_HEADER_LENGTH),
MSG_HEADER_LENGTH),
boost::bind(&TcpConnect::handle_read_header, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
private:
void handle_read_header(const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error && bytes_transferred == MSG_HEADER_LENGTH &&
m_pInMsg->ReadMessageLength()) {
boost::asio::async_read(
*m_pSocket,
boost::asio::buffer(
m_pInMsg->CreateBuffer(m_pInMsg->SerializedLength()),
m_pInMsg->SerializedLength()),
boost::bind(&TcpConnect::handle_read_body, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
} else if (error) {
/* errors handled */
}
}
void handle_read_body(const boost::system::error_code& error,
size_t bytes_transferred)
{
bool deserialized = false;
if (!error && (deserialized = m_pInMsg->Deserialize())) {
// if not yet connected, expecting handshake message, in which case
// acknowledge, otherwise error
if (m_pInMsg->IsHandshake()) {
m_pInMsg->SetAcknowledge(true);
std::size_t write_length = boost::asio::write(
*m_pSocket,
boost::asio::buffer(
m_pInMsg->Serialize(),
(std::size_t)m_pInMsg->SerializedLength()));
if (write_length == m_pInMsg->SerializedLength()) {
/* we sent the acknowledgement, so we consider we're
* connected */
} else {
/* handling error here */
return;
}
delete m_pInMsg;
m_pInMsg = NULL;
}
// if graceful disconnect, notify the connection manager of new
// status, which will remove the connection from the map
else if (m_pInMsg->IsDisconnect()) {
/* disconnect request handled here */
return;
} else {
/* message received, passing it to the local process here */
}
// set up to handle the next read
m_pInMsg = new CmdPacket;
boost::asio::async_read(
*m_pSocket,
boost::asio::buffer(m_pInMsg->CreateBuffer(MSG_HEADER_LENGTH),
MSG_HEADER_LENGTH),
boost::bind(&TcpConnect::handle_read_header, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
} else if (error) {
/* handle case where boost error */
} else if (!deserialized) {
/* handle case where message not correctly deserialized */
}
}
protected:
boost::asio::io_service* m_pio_service;
ConnType eConnType;
boost::asio::ip::tcp::socket* m_pSocket{0};
boost::asio::ip::tcp::resolver::iterator m_itEndpoint;
CmdPacket* m_pInMsg{0};
std::string m_strHostIp;
uint16_t m_iHostPort;
};
typedef boost::shared_ptr<TcpConnect> ConnectionPtr;
// TcpClient class:
class TcpClient : public TcpConnect {
public:
TcpClient(boost::asio::io_service& io_service, ConnType connType,
const std::string& sIP, uint16_t iPort)
: TcpConnect(&io_service, connType, sIP, iPort)
{
}
~TcpClient()
{ /* does what is needed - this works */
}
// virtual ObjType Type() { return OT_CLIENT; }
// virtual int sessionId() { return -1; } // client end does not have a
// session id
// Use the following to initialize and to to reestablish the connection.
bool Connect()
{
bool bStatus = true;
// Convert the port to a string
std::stringstream ss;
ss << m_iHostPort;
std::string strPort = ss.str();
// Establish the connection
try {
boost::system::error_code ec;
// create TCP resolver and query and resolve the endpoint
boost::asio::ip::tcp::resolver resolver(*m_pio_service);
boost::asio::ip::tcp::resolver::query query(m_strHostIp.c_str(),
strPort.c_str());
boost::asio::ip::tcp::resolver::iterator m_iterEndpoint =
resolver.resolve(query, ec);
if (ec) {
/* error handled here */
bStatus = false;
} else {
// close an old socket (shouldn't ever be the case)
if (m_pSocket != NULL)
CloseSocket(); /* NOTE: this is defined in TcpConnect, but
not shown here */
// create the socket on the io_service object and connect to the
// endpoint
m_pSocket = new boost::asio::ip::tcp::socket(*m_pio_service);
boost::asio::connect(*m_pSocket, m_iterEndpoint, ec);
if (ec) {
/* error handled here */
bStatus = false;
}
}
} // end try
catch (...) {
/* error handled here */
bStatus = false;
}
return bStatus;
}
};
typedef boost::shared_ptr<TcpClient> TcpClientPtr;
// TcpServer class (run by TcpWorker and creates TcpSession objects):
class TcpServer;
class TcpSession : public TcpConnect {
public:
TcpSession(boost::asio::io_service& io_service)
: TcpConnect(&io_service, ConnType::Session, "127.0.0.1", 8787)
, m_session_id(next_session_id())
{
}
virtual ~TcpSession()
{ /* NOTE: m_pSocket is owned and deleted by TcpConnect */
}
private:
int next_session_id()
{
static int id = 0;
return (++id > 0) ? id : 1;
}
private:
int m_session_id;
};
typedef boost::shared_ptr<TcpSession> TcpSessionPtr;
class TcpServer {
public:
TcpServer(boost::asio::io_service& io_service, uint16_t port)
: m_pio_service(&io_service)
, m_acceptor(io_service, tcp::endpoint(tcp::v4(), port))
{
m_acceptor.listen();
}
~TcpServer()
{
boost::system::error_code errorcode;
m_acceptor.close(errorcode);
}
void start_accept()
{
TcpSessionPtr new_session(new TcpSession(*m_pio_service));
// start listening for this session
m_acceptor.async_accept(new_session->socket(),
boost::bind(&TcpServer::handle_accept, this,
new_session,
boost::asio::placeholders::error));
new_session.reset();
}
private:
void handle_accept(TcpSessionPtr new_session,
const boost::system::error_code& error)
{
if (!error) {
new_session->StartAsyncRead(); /* NOTE: there is code for
aggregating session objects */
/* NOTE: The result of an async_read() will be handled in
* TcpConnect::handle_read_header() */
} else {
/* error handled here */
}
// listen for the next connection
start_accept();
}
private:
boost::asio::io_service* m_pio_service;
tcp::acceptor m_acceptor;
};
class TcpWorker {
public:
TcpWorker(uint16_t port) : m_port(port) {}
~TcpWorker() {}
void StopWorker()
{
if (!m_io_service.stopped()) {
m_io_service.stop();
while (!m_io_service.stopped()) {
boost::this_thread::sleep(boost::posix_time::milliseconds(1));
}
}
}
void
operator()() // threaded run function started from Communicator::Listen()
{
TcpServer server(m_io_service, m_port);
server.start_accept(); // set up async_accept() for listening
std::size_t inumhandlers =
m_io_service.run(); // blocks here until StopWorker() is called
}
private:
uint16_t m_port;
boost::asio::io_service m_io_service;
bool m_running;
};
// Communicator class:
class Communicator {
public:
uint16_t m_myPort = 8787;
Communicator() = default;
~Communicator()
{ /* does what is needed - this works */
}
bool Listen()
{
if (!m_pServerThread || !m_pServerWorker) {
m_pServerWorker = new TcpWorker(m_myPort);
m_pServerThread =
new boost::thread(&TcpWorker::operator(), m_pServerWorker);
return true;
}
return false;
}
bool Connect(int srcId, int destId, std::string ipaddr, uint16_t port)
{
bool ret = false;
if (connected(destId)) {
ret = true;
} else {
// if io_service is not running, start it (happens if never started,
// or if no remaining client sockets running)
if (!ClientThreadRunning()) {
if (m_pClientThread) // since going to create a new thread, make
// sure this one is deleted if exists
delete m_pClientThread;
if (!m_pclient_io_service) // if no io_service has yet been
// created
m_pclient_io_service = new boost::asio::io_service;
m_pClientServiceThread = new boost::thread(boost::bind(
&boost::asio::io_service::run, m_pclient_io_service));
}
// create the connection. Wait for Ack before returning.
TcpClientPtr client(
new TcpClient(*m_pclient_io_service, destId, ip, port));
// connect to the client and do a handshake
if (client->Connect()) {
// if an initial handshake works, we're connected
CmdPacket msg(CMD_NONE, srcId, destId, port, ipaddr), rsp;
msg.SetHandshake(); // this starts the handshake protocol, which
// is completed on receiving the necessary
// response.
if (!client->SendBlocked(msg, rsp) || rsp != msg) {
client.reset();
ret = false;
} else {
// Connected, now set up for asynchronous reading
client->StartAsyncRead(m_pclient_io_service);
// save it in the class
connection = client;
ret = true;
}
}
// decrement reference count, if not added to shared pointer map,
// this will delete client object
client.reset();
}
return ret;
}
bool sendMessage(CmdPacket& msg)
{
bool bret = false;
if (connection != nullptr) {
iter->second->Send(msg);
}
return bret;
}
private:
ConnectionPtr connection;
TcpWorker* m_pServerWorker{0};
boost::thread* m_pServerThread{0};
boost::thread* m_pClientThread{0};
boost::asio::io_service* m_pclient_io_service{0};
};
int main() {}
Out Of The Box
All of this reminds me very much of this answer: How do I make this HTTPS connection persistent in Beast? in that answer I show how to to keep a pool of connections for different destinations. It uses a different style, so hopefully you can use that as inspiration.

How can i make Boost Beast Reply From A String Instead Of A File

I am trying to work with this example code
boost beast advanced server example
It compiles and works nice. Now i want to make it read from a given string to reply a Get or Post request instead of reading from a file.
For example: Client sends a Get request for "www.xxxxxxxxxx.com/index.html"
Program will reply the request from a string which is taken from a database, not file.
How can i do it? Thanks.
The sample already shows it. Look, e.g. at how error responses are generated:
// Returns a not found response
auto const not_found = [&req](boost::beast::string_view target) {
http::response<http::string_body> res{ http::status::not_found, req.version() };
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "text/html");
res.keep_alive(req.keep_alive());
res.body() = "The resource '" + target.to_string() + "' was not found.";
res.prepare_payload();
return res;
};
Just set body() to something different.
DEMO
A full demo, basically just by stripping the sample from the unneeded code, and using prepare_payload to get content length/encoding automatically.
#include <algorithm>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/strand.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/config.hpp>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <vector>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
namespace websocket = boost::beast::websocket; // from <boost/beast/websocket.hpp>
// This function produces an HTTP response for the given request.
template <class Body, class Allocator, class Send>
void handle_request(http::request<Body, http::basic_fields<Allocator> > &&req, Send &&send) {
// Returns a bad request response
auto const bad_request = [&req](boost::beast::string_view why) {
http::response<http::string_body> res{ http::status::bad_request, req.version() };
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "text/html");
res.keep_alive(req.keep_alive());
res.body() = why.to_string();
res.prepare_payload();
return res;
};
// Make sure we can handle the method
if (req.method() != http::verb::get)
return send(bad_request("Unsupported HTTP-method"));
// Request path must be absolute and not contain "..".
auto target = req.target();
if (target.empty() || target[0] != '/' || target.find("..") != boost::beast::string_view::npos)
return send(bad_request("Illegal request-target"));
http::response<http::string_body> res{ http::status::ok, req.version() };
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "text/html");
res.body() = "You're looking at " + target.to_string();
res.prepare_payload();
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
// Report a failure
void fail(boost::system::error_code ec, char const *what) { std::cerr << what << ": " << ec.message() << "\n"; }
// Echoes back all received WebSocket messages
class websocket_session : public std::enable_shared_from_this<websocket_session> {
websocket::stream<tcp::socket> ws_;
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
boost::asio::steady_timer timer_;
boost::beast::multi_buffer buffer_;
public:
// Take ownership of the socket
explicit websocket_session(tcp::socket socket)
: ws_(std::move(socket)), strand_(ws_.get_executor()),
timer_(ws_.get_executor().context(), (std::chrono::steady_clock::time_point::max)()) {}
// Start the asynchronous operation
template <class Body, class Allocator> void run(http::request<Body, http::basic_fields<Allocator> > req) {
// Run the timer. The timer is operated
// continuously, this simplifies the code.
on_timer({});
// Set the timer
timer_.expires_after(std::chrono::seconds(15));
// Accept the websocket handshake
ws_.async_accept(req,
boost::asio::bind_executor(strand_, std::bind(&websocket_session::on_accept,
shared_from_this(), std::placeholders::_1)));
}
// Called when the timer expires.
void on_timer(boost::system::error_code ec) {
if (ec && ec != boost::asio::error::operation_aborted)
return fail(ec, "timer");
// Verify that the timer really expired since the deadline may have moved.
if (timer_.expiry() <= std::chrono::steady_clock::now()) {
// Closing the socket cancels all outstanding operations. They
// will complete with boost::asio::error::operation_aborted
ws_.next_layer().shutdown(tcp::socket::shutdown_both, ec);
ws_.next_layer().close(ec);
return;
}
// Wait on the timer
timer_.async_wait(boost::asio::bind_executor(
strand_, std::bind(&websocket_session::on_timer, shared_from_this(), std::placeholders::_1)));
}
void on_accept(boost::system::error_code ec) {
// Happens when the timer closes the socket
if (ec == boost::asio::error::operation_aborted)
return;
if (ec)
return fail(ec, "accept");
// Read a message
do_read();
}
void do_read() {
// Set the timer
timer_.expires_after(std::chrono::seconds(15));
// Read a message into our buffer
ws_.async_read(buffer_,
boost::asio::bind_executor(strand_, std::bind(&websocket_session::on_read, shared_from_this(),
std::placeholders::_1, std::placeholders::_2)));
}
void on_read(boost::system::error_code ec, std::size_t bytes_transferred) {
boost::ignore_unused(bytes_transferred);
// Happens when the timer closes the socket
if (ec == boost::asio::error::operation_aborted)
return;
// This indicates that the websocket_session was closed
if (ec == websocket::error::closed)
return;
if (ec)
fail(ec, "read");
// Echo the message
ws_.text(ws_.got_text());
ws_.async_write(buffer_.data(),
boost::asio::bind_executor(strand_, std::bind(&websocket_session::on_write, shared_from_this(),
std::placeholders::_1, std::placeholders::_2)));
}
void on_write(boost::system::error_code ec, std::size_t bytes_transferred) {
boost::ignore_unused(bytes_transferred);
// Happens when the timer closes the socket
if (ec == boost::asio::error::operation_aborted)
return;
if (ec)
return fail(ec, "write");
// Clear the buffer
buffer_.consume(buffer_.size());
// Do another read
do_read();
}
};
// Handles an HTTP server connection
class http_session : public std::enable_shared_from_this<http_session> {
// This queue is used for HTTP pipelining.
class queue {
enum {
// Maximum number of responses we will queue
limit = 8
};
// The type-erased, saved work item
struct work {
virtual ~work() = default;
virtual void operator()() = 0;
};
http_session &self_;
std::vector<std::unique_ptr<work> > items_;
public:
explicit queue(http_session &self) : self_(self) {
static_assert(limit > 0, "queue limit must be positive");
items_.reserve(limit);
}
// Returns `true` if we have reached the queue limit
bool is_full() const { return items_.size() >= limit; }
// Called when a message finishes sending
// Returns `true` if the caller should initiate a read
bool on_write() {
BOOST_ASSERT(!items_.empty());
auto const was_full = is_full();
items_.erase(items_.begin());
if (!items_.empty())
(*items_.front())();
return was_full;
}
// Called by the HTTP handler to send a response.
template <bool isRequest, class Body, class Fields>
void operator()(http::message<isRequest, Body, Fields> &&msg) {
// This holds a work item
struct work_impl : work {
http_session &self_;
http::message<isRequest, Body, Fields> msg_;
work_impl(http_session &self, http::message<isRequest, Body, Fields> &&msg)
: self_(self), msg_(std::move(msg)) {}
void operator()() {
http::async_write(self_.socket_, msg_,
boost::asio::bind_executor(
self_.strand_, std::bind(&http_session::on_write, self_.shared_from_this(),
std::placeholders::_1, msg_.need_eof())));
}
};
// Allocate and store the work
items_.emplace_back(new work_impl(self_, std::move(msg)));
// If there was no previous work, start this one
if (items_.size() == 1)
(*items_.front())();
}
};
tcp::socket socket_;
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
boost::asio::steady_timer timer_;
boost::beast::flat_buffer buffer_;
http::request<http::string_body> req_;
queue queue_;
public:
// Take ownership of the socket
explicit http_session(tcp::socket socket)
: socket_(std::move(socket)), strand_(socket_.get_executor()),
timer_(socket_.get_executor().context(), (std::chrono::steady_clock::time_point::max)()), queue_(*this) {}
// Start the asynchronous operation
void run() {
// Run the timer. The timer is operated
// continuously, this simplifies the code.
on_timer({});
do_read();
}
void do_read() {
// Set the timer
timer_.expires_after(std::chrono::seconds(15));
// Read a request
http::async_read(socket_, buffer_, req_,
boost::asio::bind_executor(
strand_, std::bind(&http_session::on_read, shared_from_this(), std::placeholders::_1)));
}
// Called when the timer expires.
void on_timer(boost::system::error_code ec) {
if (ec && ec != boost::asio::error::operation_aborted)
return fail(ec, "timer");
// Verify that the timer really expired since the deadline may have moved.
if (timer_.expiry() <= std::chrono::steady_clock::now()) {
// Closing the socket cancels all outstanding operations. They
// will complete with boost::asio::error::operation_aborted
socket_.shutdown(tcp::socket::shutdown_both, ec);
socket_.close(ec);
return;
}
// Wait on the timer
timer_.async_wait(boost::asio::bind_executor(
strand_, std::bind(&http_session::on_timer, shared_from_this(), std::placeholders::_1)));
}
void on_read(boost::system::error_code ec) {
// Happens when the timer closes the socket
if (ec == boost::asio::error::operation_aborted)
return;
// This means they closed the connection
if (ec == http::error::end_of_stream)
return do_close();
if (ec)
return fail(ec, "read");
// See if it is a WebSocket Upgrade
if (websocket::is_upgrade(req_)) {
// Create a WebSocket websocket_session by transferring the socket
std::make_shared<websocket_session>(std::move(socket_))->run(std::move(req_));
return;
}
// Send the response
handle_request(std::move(req_), queue_);
// If we aren't at the queue limit, try to pipeline another request
if (!queue_.is_full())
do_read();
}
void on_write(boost::system::error_code ec, bool close) {
// Happens when the timer closes the socket
if (ec == boost::asio::error::operation_aborted)
return;
if (ec)
return fail(ec, "write");
if (close) {
// This means we should close the connection, usually because
// the response indicated the "Connection: close" semantic.
return do_close();
}
// Inform the queue that a write completed
if (queue_.on_write()) {
// Read another request
do_read();
}
}
void do_close() {
// Send a TCP shutdown
boost::system::error_code ec;
socket_.shutdown(tcp::socket::shutdown_send, ec);
// At this point the connection is closed gracefully
}
};
//------------------------------------------------------------------------------
// Accepts incoming connections and launches the sessions
class listener : public std::enable_shared_from_this<listener> {
tcp::acceptor acceptor_;
tcp::socket socket_;
public:
listener(boost::asio::io_context &ioc, tcp::endpoint endpoint) : acceptor_(ioc), socket_(ioc) {
boost::system::error_code ec;
// Open the acceptor
acceptor_.open(endpoint.protocol(), ec);
if (ec) {
fail(ec, "open");
return;
}
// Bind to the server address
acceptor_.bind(endpoint, ec);
if (ec) {
fail(ec, "bind");
return;
}
// Start listening for connections
acceptor_.listen(boost::asio::socket_base::max_listen_connections, ec);
if (ec) {
fail(ec, "listen");
return;
}
}
// Start accepting incoming connections
void run() {
if (!acceptor_.is_open())
return;
do_accept();
}
void do_accept() {
acceptor_.async_accept(socket_, std::bind(&listener::on_accept, shared_from_this(), std::placeholders::_1));
}
void on_accept(boost::system::error_code ec) {
if (ec) {
fail(ec, "accept");
} else {
// Create the http_session and run it
std::make_shared<http_session>(std::move(socket_))->run();
}
// Accept another connection
do_accept();
}
};
//------------------------------------------------------------------------------
int main(int argc, char *argv[]) {
// Check command line arguments.
if (argc != 4) {
std::cerr << "Usage: advanced-server <address> <port> <threads>\n"
<< "Example:\n"
<< " advanced-server 0.0.0.0 8080 1\n";
return EXIT_FAILURE;
}
auto const address = boost::asio::ip::make_address(argv[1]);
auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
auto const threads = std::max<int>(1, std::atoi(argv[3]));
// The io_context is required for all I/O
boost::asio::io_context ioc{ threads };
// Create and launch a listening port
std::make_shared<listener>(ioc, tcp::endpoint{ address, port })->run();
// Run the I/O service on the requested number of threads
std::vector<std::thread> v;
v.reserve(threads - 1);
for (auto i = threads - 1; i > 0; --i)
v.emplace_back([&ioc] { ioc.run(); });
ioc.run();
return EXIT_SUCCESS;
}

Boost::Asio::Ip::Tcp::Iostream questions

Hey all, I'm new to asio and boost, I've been trying to implement a TCP Server & Client so that I could transmit an std::vector - but I've failed so far. I'm finding the boost documentation of Asio lacking (to say the least) and hard to understand (english is not my primary language).
In any case, I've been looking at the iostreams examples and I've been trying to implement an object oriented solution - but I've failed.
The server that I'm trying to implement should be able to accept connections from multiple clients (How do I do that ?)
The server should receive the std::vector, /* Do something */ and then return it to the client so that the client can tell that the server received the data intact.
*.h file
class TCP_Server : private boost::noncopyable
{
typedef boost::shared_ptr<TCP_Connection> tcp_conn_pointer;
public :
TCP_Server(ba::io_service &io_service, int port);
virtual ~TCP_Server() {}
virtual void Start_Accept();
private:
virtual void Handle_Accept(const boost::system::error_code& e);
private :
int m_port;
ba::io_service& m_io_service; // IO Service
bi::tcp::acceptor m_acceptor; // TCP Connections acceptor
tcp_conn_pointer m_new_tcp_connection; // New connection pointer
};
*.cpp file
TCP_Server::TCP_Server(boost::asio::io_service &io_service, int port) :
m_io_service(io_service),
m_acceptor(io_service, bi::tcp::endpoint(bi::tcp::v4(), port)),
m_new_tcp_connection(TCP_Connection::Create(io_service))
{
m_port = port;
Start_Accept();
}
void TCP_Server::Start_Accept()
{
std::cout << "[TCP_Server][Start_Accept] => Listening on port : " << m_port << std::endl;
//m_acceptor.async_accept(m_new_tcp_connection->Socket(),
// boost::bind(&TCP_Server::Handle_Accept, this,
// ba::placeholders::error));
m_acceptor.async_accept(*m_stream.rdbuf(),
boost::bind(&TCP_Server::Handle_Accept,
this,
ba::placeholders::error));
}
void TCP_Server::Handle_Accept(const boost::system::error_code &e)
{
if(!e)
{
/*boost::thread T(boost::bind(&TCP_Connection::Run, m_new_tcp_connection));
std::cout << "[TCP_Server][Handle_Accept] => Accepting incoming connection. Launching Thread " << std::endl;
m_new_tcp_connection = TCP_Connection::Create(m_io_service);
m_acceptor.async_accept(m_new_tcp_connection->Socket(),
boost::bind(&TCP_Server::Handle_Accept,
this,
ba::placeholders::error));*/
m_stream << "Server Response..." << std::endl;
}
}
How should the client look ?
How do I keep the connection alive while both apps "talk" ?
AFAIK ASIO iostreams are only for synchronous I/O. But your example gives me a hint that you want to use asynchronous I/O.
Here is a small example of a server which uses async I/O to read a request comprising of an array of integers preceded by 4 byte count of the integers in the request.
So in effect I am serializing a vector of integerss as
count(4 bytes)
int
int
...
etc
if reading the vector of ints is successful, the server will write a 4 byte response code(=1) and then issue a read for a new request from the client. Enough said, Code follows.
#include <iostream>
#include <vector>
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <boost/asio.hpp>
using namespace boost::asio;
using boost::asio::ip::tcp;
class Connection
{
public:
Connection(tcp::acceptor& acceptor)
: acceptor_(acceptor), socket_(acceptor.get_io_service(), tcp::v4())
{
}
void start()
{
acceptor_.get_io_service().post(boost::bind(&Connection::start_accept, this));
}
private:
void start_accept()
{
acceptor_.async_accept(socket_,boost::bind(&Connection::handle_accept, this,
placeholders::error));
}
void handle_accept(const boost::system::error_code& err)
{
if (err)
{
//Failed to accept the incoming connection.
disconnect();
}
else
{
count_ = 0;
async_read(socket_, buffer(&count_, sizeof(count_)),
boost::bind(&Connection::handle_read_count,
this, placeholders::error, placeholders::bytes_transferred));
}
}
void handle_read_count(const boost::system::error_code& err, std::size_t bytes_transferred)
{
if (err || (bytes_transferred != sizeof(count_))
{
//Failed to read the element count.
disconnect();
}
else
{
elements_.assign(count_, 0);
async_read(socket_, buffer(elements_),
boost::bind(&Connection::handle_read_elements, this,
placeholders::error, placeholders::bytes_transferred));
}
}
void handle_read_elements(const boost::system::error_code& err, std::size_t bytes_transferred)
{
if (err || (bytes_transferred != count_ * sizeof(int)))
{
//Failed to read the request elements.
disconnect();
}
else
{
response_ = 1;
async_write(socket_, buffer(&response_, sizeof(response_)),
boost::bind(&Connection::handle_write_response, this,
placeholders::error, placeholders::bytes_transferred));
}
}
void handle_write_response(const boost::system::error_code& err, std::size_t bytes_transferred)
{
if (err)
disconnect();
else
{
//Start a fresh read
count_ = 0;
async_read(socket_, buffer(&count_, sizeof(count_)),
boost::bind(&Connection::handle_read_count,
this, placeholders::error, placeholders::bytes_transferred));
}
}
void disconnect()
{
socket_.shutdown(tcp::socket::shutdown_both);
socket_.close();
socket_.open(tcp::v4());
start_accept();
}
tcp::acceptor& acceptor_;
tcp::socket socket_;
std::vector<int> elements_;
long count_;
long response_;
};
class Server : private boost::noncopyable
{
public:
Server(unsigned short port, unsigned short thread_pool_size, unsigned short conn_pool_size)
: acceptor_(io_service_, tcp::endpoint(tcp::v4(), port), true)
{
unsigned short i = 0;
for (i = 0; i < conn_pool_size; ++i)
{
ConnectionPtr conn(new Connection(acceptor_));
conn->start();
conn_pool_.push_back(conn);
}
// Start the pool of threads to run all of the io_services.
for (i = 0; i < thread_pool_size; ++i)
{
thread_pool_.create_thread(boost::bind(&io_service::run, &io_service_));
}
}
~Server()
{
io_service_.stop();
thread_pool_.join_all();
}
private:
io_service io_service_;
tcp::acceptor acceptor_;
typedef boost::shared_ptr<Connection> ConnectionPtr;
std::vector<ConnectionPtr> conn_pool_;
boost::thread_group thread_pool_;
};
boost::function0<void> console_ctrl_function;
BOOL WINAPI console_ctrl_handler(DWORD ctrl_type)
{
switch (ctrl_type)
{
case CTRL_C_EVENT:
case CTRL_BREAK_EVENT:
case CTRL_CLOSE_EVENT:
case CTRL_SHUTDOWN_EVENT:
console_ctrl_function();
return TRUE;
default:
return FALSE;
}
}
void stop_server(Server* pServer)
{
delete pServer;
pServer = NULL;
}
int main()
{
Server *pServer = new Server(10255, 4, 20);
console_ctrl_function = boost::bind(stop_server, pServer);
SetConsoleCtrlHandler(console_ctrl_handler, TRUE);
while(true)
{
Sleep(10000);
}
}
I believe the code you have posted is a little incomplete/incorrect. Nonetheless, here is some guidance..
1)
Your async_accept() call seems wrong. It should be something like,
m_acceptor.async_accept(m_new_tcp_connection->socket(),...)
2)
Take note that the Handle_Accept() function will be called after the socket is accepted. In other words, when control reaches Handle_Accept(), you simply have to write to the socket. Something like
void TCP_Server::Handle_Accept(const system::error_code& error)
{
if(!error)
{
//send data to the client
string message = "hello there!\n";
//Write data to the socket and then call the handler AFTER that
//Note, you will need to define a Handle_Write() function in your TCP_Connection class.
async_write(m_new_tcp_connection->socket(),buffer(message),bind(&TCP_Connection::Handle_Write, this,placeholders::error,placeholders::bytes_transferred));
//accept the next connection
Start_Accept();
}
}
3)
As for the client, you should take a look here:
http://www.boost.org/doc/libs/1_39_0/doc/html/boost_asio/tutorial/tutdaytime1.html
If your communication on both ends is realized in C++ you can use Boost Serialization library to sezilize the vector into bytes and transfer these to the other machine. On the opposite end you will use boost serialization lib to desirialize the object. I saw at least two approaches doing so.
Advantage of Boost Serialization: this approach works when transferring objects between 32bit and 64bit systems as well.
Below are the links:
code project article
boost mailing list ideas
Regards,
Ovanes