How to properly shutdown asio tcp server? - c++

What is the proper way to shutdown an asynchronous boost asio tcp server? My currently solution usually deadlocks in the destructor. Why?
class connection;
typedef std::set<shared_ptr<connection>> connection_set;
class connection : public enable_shared_from_this<connection>
{
shared_ptr<tcp::socket> socket_;
std::array<char, 8192> data_;
shared_ptr<connection_set> connection_set_;
public:
static shared_ptr<connection> create(shared_ptr<tcp::socket> socket, shared_ptr<connection_set> connection_set)
{
auto con = shared_ptr<connection>(new connection(std::move(socket), std::move(connection_set)));
con->read_some();
return con;
}
void on_next(const event& e)
{
// async_write_some ...
}
private:
connection(shared_ptr<tcp::socket> socket, shared_ptr<connection_set> connection_set)
: socket_(std::move(socket))
, connection_set_(std::move(connection_set))
{
}
void handle_read(const boost::system::error_code& error, size_t bytes_transferred)
{
if(!error)
{
on_read(std::string(data_.begin(), data_.begin() + bytes_transferred));
read_some();
}
else if (error != boost::asio::error::operation_aborted)
connection_set_->erase(shared_from_this());
else
read_some();
}
void handle_write(const shared_ptr<std::vector<char>>& data, const boost::system::error_code& error, size_t bytes_transferred)
{
if(!error)
{
}
else if (error != boost::asio::error::operation_aborted)
connection_set_->erase(shared_from_this());
}
void read_some()
{
socket_->async_read_some(boost::asio::buffer(data_.data(), data_.size()), std::bind(&connection::handle_read, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
}
void on_read(std::string str)
{
// parse the string...
}
};
class tcp_observer
{
boost::asio::io_service service_;
tcp::acceptor acceptor_;
std::shared_ptr<connection_set> connection_set_;
boost::thread thread_;
public:
tcp_observer(unsigned short port)
: acceptor_(service_, tcp::endpoint(tcp::v4(), port))
, thread_(std::bind(&boost::asio::io_service::run, &service_))
{
start_accept();
}
~tcp_observer()
{
// Deadlocks...
service_.post([=]
{
acceptor_.close();
connection_set_->clear();
});
thread_.join();
}
void on_next(const event& e)
{
service_.post([=]
{
BOOST_FOREACH(auto& connection, *connection_set_)
connection->on_next(e);
});
}
private:
void start_accept()
{
auto socket = std::make_shared<tcp::socket>(service_);
acceptor_.async_accept(*socket, std::bind(&tcp_observer::handle_accept, this, socket, std::placeholders::_1));
}
void handle_accept(const shared_ptr<tcp::socket>& socket, const boost::system::error_code& error)
{
if (!acceptor_.is_open())
return;
if (!error)
connection_set_->insert(connection::create(socket, connection_set_));
start_accept();
}
};

This "deadlocks" because the connections are not going to be destroyed, since there are still pending operations where a shared_from_this() was passed.
Call on each connection's socket shutdown(..) and close(..). Then wait for completion which signals either eof or operation_aborted.

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

Need to lock a boost asio tcp socket handler's internal state for multi-thread access?

Objects of Data_t are sent via a TCP socket to a server. The server creates a ConnectionHandler object to handle each incoming connection.
In ConnectionHandler, Data_t objects are read one by one using async_read from the socket, and their num_ fields are summed up then saved to a field ConnectionHandler::total_sum_.
Do I need to lock ConnectionHandler::total_sum_ since multiple threads will write to it?
See the code below. Please note that
ConnectinHandler::received_data_ is re-used as a buffer to hold Data_t objects read from the socket. Is it safe to do so?
ConnectinHandler::process_data() processes a Data_t object first then call ConnectinHandler::read_pack() to read from the socket again.
struct Data_t
{
int num_;
//... some other data
}
template<typename ConnectionHandler>
class Server {
using shared_handler_t = std::shared_ptr<ConnectionHandler>;
public:
Server(int thread_count = 10) :
thread_count_(thread_count), acceptor_(io_service_)
void
start_server(uint16_t port) {
auto handler = std::make_shared<ConnectionHandler>(io_service_, configer_, thread_names_);
// set up the acceptor to listen on the tcp port
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), port);
acceptor_.open(endpoint.protocol());
boost::system::error_code ec;
acceptor_.bind(endpoint);
acceptor_.listen();
acceptor_.async_accept(handler->socket(),
[=](boost::system::error_code const &ec) {
handle_new_connection(handler, ec);
});
// start pool of threads to process the asio events
for (int i = 0; i < thread_count_; ++i) {
thread_pool_.emplace_back([=] { io_service_.run(); });
}
// Wait for all threads in the pool to exit.
for (std::size_t i = 0; i < thread_pool_.size(); ++i) {
thread_pool_[i].join();
}
}
private:
void
handle_new_connection(shared_handler_t handler,
boost::system::error_code const &error) {
if (error) {
return;
}
handler->start();
auto new_handler = std::make_shared<ConnectionHandler>(io_service_);
acceptor_.async_accept(new_handler->socket(),
[=](boost::system::error_code const &ec) {
handle_new_connection(new_handler, ec);
});
}
int thread_count_;
std::vector<std::thread> thread_pool_;
boost::asio::io_service io_service_;
boost::asio::ip::tcp::acceptor acceptor_;
};
class ConnectionHandler : public std::enable_shared_from_this<ConnectionHandler> {
public:
ConnectionHandler (boost::asio::io_service &service) :
service_ (service), socket_ (service)
{
}
void
start ()
{
read_packet ();
}
private:
void
read_packet ()
{
auto me = shared_from_this ();
boost::asio::async_read (
socket_, boost::asio::buffer (&received_data_, sizeof (Data_t)),
boost::asio::transfer_exactly (sizeof (Data_t)),
[me] (boost::system::error_code const &ec, std::size_t bytes_xfer)
{
me->process_data (ec, bytes_xfer);
});
}
void
process_data (boost::system::error_code const &error,
std::size_t bytes_transferred)
{
if (error)
{
socket_.close ();
return;
}
total_sum_+=received_data_.num_;
read_packet ();
}
boost::asio::io_service &service_;
boost::asio::ip::tcp::socket socket_;
Data_t received_data_;
int total_sum_;
};

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 TCP async server not async?

I am using the code provided in the Boost example.
The server only accepts 1 connection at a time. This means, no new connections until the current one is closed.
How to make the above code accept unlimited connections at the same time?
#include <cstdlib>
#include <iostream>
#include <memory>
#include <utility>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
class session
: public std::enable_shared_from_this<session>
{
public:
session(tcp::socket socket)
: socket_(std::move(socket))
{
}
void start()
{
do_read();
}
private:
void do_read()
{
auto self(shared_from_this());
socket_.async_read_some(boost::asio::buffer(data_, max_length),
[this, self](boost::system::error_code ec, std::size_t length)
{
if (!ec)
{
boost::this_thread::sleep(boost::posix_time::milliseconds(10000));//sleep some time
do_write(length);
}
});
}
void do_write(std::size_t length)
{
auto self(shared_from_this());
boost::asio::async_write(socket_, boost::asio::buffer(data_, length),
[this, self](boost::system::error_code ec, std::size_t /*length*/)
{
if (!ec)
{
do_read();
}
});
}
tcp::socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
class server
{
public:
server(boost::asio::io_service& io_service, short port)
: acceptor_(io_service, tcp::endpoint(tcp::v4(), port)),
socket_(io_service)
{
do_accept();
}
private:
void do_accept()
{
acceptor_.async_accept(socket_,
[this](boost::system::error_code ec)
{
if (!ec)
{
std::make_shared<session>(std::move(socket_))->start();
}
do_accept();
});
}
tcp::acceptor acceptor_;
tcp::socket socket_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: async_tcp_echo_server <port>\n";
return 1;
}
boost::asio::io_service io_service;
server s(io_service, std::atoi(argv[1]));
io_service.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
As you see, the program waits for the sleep and it doesn't grab a second connection in the meantime.
You're doing a synchronous wait inside the handler which runs on the only thread that serves your io_service. This makes Asio wait with invoking the handlers for any new requests.
Use a deadline_time with wait_async, or,
void do_read() {
auto self(shared_from_this());
socket_.async_read_some(boost::asio::buffer(data_, max_length),
[this, self](boost::system::error_code ec, std::size_t length) {
if (!ec) {
timer_.expires_from_now(boost::posix_time::seconds(1));
timer_.async_wait([this, self, length](boost::system::error_code ec) {
if (!ec)
do_write(length);
});
}
});
}
Where the timer_ field is a boost::asio::deadline_timer member of session
as a poor-man's solution add more threads (this simply means that if more requests arrive at the same time than there are threads to handle them, it will still block until the first thread becomes available to pick up the new request)
boost::thread_group tg;
for (int i=0; i < 10; ++i)
tg.create_thread([&]{ io_service.run(); });
tg.join_all();
Both the original code and the modified code are asynchronous and accept multiple connections. As can be seen in the following snippet, the async_accept operation's AcceptHandler initiates another async_accept operation, forming an asynchronous loop:
.-----------------------------------.
V |
void server::do_accept() |
{ |
acceptor_.async_accept(..., |
[this](boost::system::error_code ec) |
{ |
// ... |
do_accept(); ----------------------'
});
}
The sleep() within the session's ReadHandler causes the one thread running the io_service to block until the sleep completes. Hence, the program will be doing nothing. However, this does not cause any outstanding operations to be cancelled. For a better understanding of asynchronous operations and io_service, consider reading this answer.
Here is an example demonstrating the server handling multiple connections. It spawns off a thread that creates 5 client sockets and connects them to the server.
#include <cstdlib>
#include <iostream>
#include <memory>
#include <utility>
#include <vector>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
using boost::asio::ip::tcp;
class session
: public std::enable_shared_from_this<session>
{
public:
session(tcp::socket socket)
: socket_(std::move(socket))
{
}
~session()
{
std::cout << "session ended" << std::endl;
}
void start()
{
std::cout << "session started" << std::endl;
do_read();
}
private:
void do_read()
{
auto self(shared_from_this());
socket_.async_read_some(boost::asio::buffer(data_, max_length),
[this, self](boost::system::error_code ec, std::size_t length)
{
if (!ec)
{
do_write(length);
}
});
}
void do_write(std::size_t length)
{
auto self(shared_from_this());
boost::asio::async_write(socket_, boost::asio::buffer(data_, length),
[this, self](boost::system::error_code ec, std::size_t /*length*/)
{
if (!ec)
{
do_read();
}
});
}
tcp::socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
class server
{
public:
server(boost::asio::io_service& io_service, short port)
: acceptor_(io_service, tcp::endpoint(tcp::v4(), port)),
socket_(io_service)
{
do_accept();
}
private:
void do_accept()
{
acceptor_.async_accept(socket_,
[this](boost::system::error_code ec)
{
if (!ec)
{
std::make_shared<session>(std::move(socket_))->start();
}
do_accept();
});
}
tcp::acceptor acceptor_;
tcp::socket socket_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: async_tcp_echo_server <port>\n";
return 1;
}
boost::asio::io_service io_service;
auto port = std::atoi(argv[1]);
server s(io_service, port);
boost::thread client_main(
[&io_service, port]
{
tcp::endpoint server_endpoint(
boost::asio::ip::address_v4::loopback(), port);
// Create and connect 5 clients to the server.
std::vector<std::shared_ptr<tcp::socket>> clients;
for (auto i = 0; i < 5; ++i)
{
auto client = std::make_shared<tcp::socket>(
std::ref(io_service));
client->connect(server_endpoint);
clients.push_back(client);
}
// Wait 2 seconds before destroying all clients.
boost::this_thread::sleep(boost::posix_time::seconds(2));
});
io_service.run();
client_main.join();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
The output:
session started
session started
session started
session started
session started
session ended
session ended
session ended
session ended
session ended

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