Asio async_read_until EOF Error in Asynchronous TCP Server - c++

when I build it, and running server and then run client, that appear a error
error code = 2, error message = End of file
when I code synchronous tcp server it's work ok;
thanks
full client code
#include <boost/predef.h> // Tools to identify the os
#ifdef BOOST_OS_WINDOWS
#define _WIN32_WINNT 0x0501
#if _WIN32_WINNT <= 0x0502
#define BOOST_ASIO_DISABLE_TOCP
#define BOOST_ASIO_ENABLE_CANCELIO
#endif
#endif
#include <boost/asio.hpp>
#include <mutex>
#include <thread>
#include <memory>
#include <iostream>
#include <map>
using namespace boost;
typedef void(*Callback) (unsigned int request_id, const std::string& response, const system::error_code& ec);
struct Session{
Session(asio::io_service& ios, const std::string& raw_ip_address, unsigned short port_num, const std::string& request, unsigned int id, Callback callback) : m_sock(ios), m_ep(asio::ip::address::from_string(raw_ip_address),port_num), m_request(request), m_id(id), m_callback(callback), m_was_cancelled(false) {}
asio::ip::tcp::socket m_sock;
asio::ip::tcp::endpoint m_ep; // Remote endpoint
std::string m_request;
// streambuf where the response will be stored.
asio::streambuf m_response_buf;
std::string m_response; // Response represented as a string
system::error_code m_ec;
unsigned int m_id;
Callback m_callback;
bool m_was_cancelled;
std::mutex m_cancel_guard;
};
class AsyncTCPClient : public boost::asio::noncopyable {
public:
AsyncTCPClient(){
m_work.reset(new boost::asio::io_service::work(m_ios));
m_thread.reset(new std::thread([this](){
m_ios.run();
}));
}
void emulateLongComputationOp( unsigned int duration_sec, const std::string& raw_ip_address, unsigned short port_num, Callback callback, unsigned int request_id){
std::string request = "EMULATE_LONG_CALC_OP " + std::to_string(duration_sec) + "\n";
std::cout << "Request: " << request << std::endl;
std::shared_ptr<Session> session = std::shared_ptr<Session> (new Session(m_ios, raw_ip_address, port_num, request, request_id, callback));
session->m_sock.open(session->m_ep.protocol());
// active sessions list can be accessed from multiple thread, we guard it with a mutex to avoid data coruption
std::unique_lock<std::mutex> lock(m_active_sessions_guard);
m_active_sessions[request_id] = session;
lock.unlock();
session->m_sock.async_connect(session->m_ep, [this, session](const system::error_code& ec) {
if (ec.value() != 0) {
session->m_ec = ec;
onRequestComplete(session);
return;
}
std::unique_lock<std::mutex> cancel_lock(session->m_cancel_guard);
if (session->m_was_cancelled) {
onRequestComplete(session);
return;
}
asio::async_write(session->m_sock, asio::buffer(session->m_request), [this, session](const boost::system::error_code &ec, std::size_t bytes_transferred) {
if (ec.value() != 0) {
session->m_ec = ec;
onRequestComplete(session);
return;
}
std::unique_lock<std::mutex> cancel_lock(session->m_cancel_guard);
if (session->m_was_cancelled) {
onRequestComplete(session);
return;
}
asio::async_read_until(session->m_sock, session->m_response_buf, '\n',
[this, session](const boost::system::error_code &ec,
std::size_t bytes_transferred) {
if (ec.value() != 0) {
session->m_ec = ec;
} else {
std::istream strm(&session->m_response_buf);
std::getline(strm, session->m_response);
}
onRequestComplete(session);
});
});
});
};
// Cancels the request
void cancelRequest(unsigned int request_id){
std::unique_lock<std::mutex> lock(m_active_sessions_guard);
auto it = m_active_sessions.find(request_id);
if(it != m_active_sessions.end()){
std::unique_lock<std::mutex> cancel_lock(it->second->m_cancel_guard);
it->second->m_was_cancelled = true;
it->second->m_sock.cancel();
}
}
void close(){
// Destroy work object
m_work.reset(NULL);
// wait for the I/O thread tot exit
m_thread->join();
}
private:
void onRequestComplete(std::shared_ptr<Session> session){
// shutting down the connection, we don't care about the error code if function failed
boost::system::error_code ignored_ec;
session->m_sock.shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec);
// remove session from the map of active sessions
std::unique_lock<std::mutex> lock(m_active_sessions_guard);
auto it = m_active_sessions.find(session->m_id);
if(it != m_active_sessions.end()){
m_active_sessions.erase(it);
}
lock.unlock();
boost::system::error_code ec;
if(session->m_ec.value() == 0 && session->m_was_cancelled){
ec = asio::error::operation_aborted;
}else{
ec = session->m_ec;
}
session->m_callback(session->m_id, session->m_response, ec);
};
private:
asio::io_service m_ios;
std::map<int, std::shared_ptr<Session>> m_active_sessions;
std::mutex m_active_sessions_guard;
std::unique_ptr<boost::asio::io_service::work> m_work;
std::unique_ptr<std::thread> m_thread;
};
void handler(unsigned int request_id, const std::string& response, const system::error_code& ec){
if(ec.value() == 0){
std::cout << "Request #" << request_id << " has completed. Reponse: "<< response << std::endl;
}else if(ec == asio::error::operation_aborted){
std::cout << "Request #" << request_id << " has been cancelled by the user. " << std::endl;
}else{
std::cout << "Request #" << request_id << " failed! Error code = " << ec.value() << ". Error Message = " << ec.message() << std::endl;
}
return;
}
int main(){
try{
AsyncTCPClient client;
// emulate the user's behavior
client.emulateLongComputationOp(10, "127.0.0.1", 3333, handler, 1);
std::this_thread::sleep_for(std::chrono::seconds(60));
// another request with id 2
client.emulateLongComputationOp(11, "127.0.0.1", 3334, handler, 2);
// cancel request 1
client.cancelRequest(1);
std::this_thread::sleep_for(std::chrono::seconds(6));
// another request with id 3
client.emulateLongComputationOp(12, "127.0.0.1", 3335, handler, 3);
std::this_thread::sleep_for(std::chrono::seconds(15));
// exit the application
client.close();
}
catch(system::system_error &e){
std::cout << "Error occured! Error code = " << e.code() << ". Message: " << e.what();
return e.code().value();
}
return 0;
}
full server code
#include <boost/asio.hpp>
#include <thread>
#include <atomic>
#include <memory>
#include <iostream>
using namespace boost;
class Service {
public:
Service(std::shared_ptr<asio::ip::tcp::socket> sock) : m_sock(sock) {}
void StartHandling() {
asio::async_read_until(*m_sock.get(), m_request, '\n', [this](const boost::system::error_code& ec, std::size_t bytes_transferred){
onRequestReceived(ec, bytes_transferred);
});
std::istream is(&m_request);
std::string line;
std::getline(is, line);
std::cout << "m_request: " << line << std::endl;
}
private:
void onRequestReceived(const boost::system::error_code& ec, std::size_t bytes_transfered){
std::cout << "ec.value : " << ec.value() << std::endl;
if (ec.value() != 0){
std::cout << "Error occurred! Error code = " << ec.value() << ".Message: " << ec.message();
onFinish();
return;
}
// Process the request
asio::async_write(*m_sock.get(), asio::buffer(m_response), [this](const boost::system::error_code& ec, std::size_t bytes_transferred){
onResponseSent(ec, bytes_transferred);
});
}
void onResponseSent(const boost::system::error_code& ec, std::size_t bytes_transferred){
if(ec.value() != 0){
std::cout << "Error occurred! Error code = " << ec.value() << ". Message: " << ec.message();
}
onFinish();
}
// cleanup
void onFinish(){
delete this;
}
std::string ProcessingRequest(asio::streambuf& request){
// parse the request, process it and prepare the request
// Emulating CPU-consuming operations
int i = 0;
while (i != 1000){
i++;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::string response = "Response\n";
return response;
}
std::shared_ptr<asio::ip::tcp::socket> m_sock;
std::string m_response;
asio::streambuf m_request;
};
class Acceptor {
public:
Acceptor(asio::io_service& ios, unsigned short port_num) : m_ios(ios), m_acceptor(m_ios, asio::ip::tcp::endpoint(asio::ip::address_v4::any(), port_num)), m_isStopped(
false) {}
// Start accepting incoming connection request.
void Start(){
m_acceptor.listen();
InitAccept();
}
void Stop() {
m_isStopped.store(true);
}
private:
void InitAccept() {
std::shared_ptr<asio::ip::tcp::socket> sock(new asio::ip::tcp::socket(m_ios));
m_acceptor.async_accept(*sock.get(), [this, sock](const boost::system::error_code& error){
onAccept(error, sock);
});
}
void onAccept(const boost::system::error_code& ec, std::shared_ptr<asio::ip::tcp::socket> sock){
if(ec.value() == 0){
(new Service(sock))->StartHandling();
}else{
std::cout << "Error occurred! Error code = " << ec.value() << ". Message: " << ec.message();
}
// Init next accept operation if acceptor has not been stopped yet
if(!m_isStopped.load()){
InitAccept();
}else{
// free resources
m_acceptor.close();
}
}
private:
asio::io_service& m_ios;
asio::ip::tcp::acceptor m_acceptor;
std::atomic<bool> m_isStopped;
};
class Server{
public:
Server() {
m_work.reset(new asio::io_service::work(m_ios));
}
// Start the server
void Start(unsigned short port_num, unsigned int thread_pool_size){
assert(thread_pool_size > 0);
// Create and start Acceptor
acc.reset(new Acceptor(m_ios, port_num));
acc->Start();
// Create specified number of thread and add them to the pool
for(unsigned int i = 0; i < thread_pool_size; i++){
std::cout << "Thread " << i << " Running !";
std::unique_ptr<std::thread> th(new std::thread([this](){
m_ios.run();
}));
m_thread_pool.push_back(std::move(th));
}
}
// Stop the Server
void Stop(){
acc->Stop();
m_ios.stop();
for(auto& th : m_thread_pool){
th->join();
}
}
private:
asio::io_service m_ios;
std::unique_ptr<asio::io_service::work> m_work;
std::unique_ptr<Acceptor> acc;
std::vector<std::unique_ptr<std::thread>> m_thread_pool;
};
const unsigned int DEFAULT_THREAD_POOL_SIZE = 2;
int main(){
unsigned short port_num = 3333;
try{
Server srv;
unsigned int thread_pool_size = std::thread::hardware_concurrency() * 2;
if (thread_pool_size == 0){
thread_pool_size = DEFAULT_THREAD_POOL_SIZE;
}
srv.Start(port_num, thread_pool_size);
std::this_thread::sleep_for(std::chrono::seconds(60));
srv.Stop();
}
catch(system::system_error &e){
std::cout << "Error occurred! Error code = " << e.code() << ". Message: " << e.what();
}
return 0;
}

The server closes the connection after sending the (empty) response. That leads to EOF on the client, naturally. Just handle it.
There's loads of code smells
delete this; is an abomination, just make Service shared_from_this.
No need to use shared_ptrs other than that
When you use smart pointers, use them. Don't "convert to raw pointer" just to dereference (so *m_socket instead of *m_socket.get()).
In fact, there should be no need to use new, delete or get() in your code
You are accessing the m_request immediately after async_read_until which is too early,
it is a data race (so Undefined Behaviour)
it doesn't get the request, because async_read_until didn't complete yet.
So move that code into onRequestReceived at a minimum
It's pretty unnecessary to use an istream to read the line from the request when you already have bytes_transferred. I'd suggest
if (bytes_transferred) {
std::string line(m_request.data().data(), bytes_transferred - 1);
m_request.consume(bytes_transferred);
std::cout << "request: " << line << std::endl;
}
Or even:
std::cout << "request: ";
std::cout.write(asio::buffer_cast<char const*>(m_request.data()),
bytes_transferred - 1);
m_request.consume(bytes_transferred);
Or, if you indeed wanted to show the entire m_request, simply
std::cout << "m_request: " << &m_request << std::endl;
Note that read_until may read more than just including the delimiter; for your safety you might want to validate that no other data is trailing, or process it as well
Never switch on error_code::value(), that loses the error category, which is essential to interpret error codes.
Why unique_ptr for each thread? Just a deque<thread>:
while (thread_pool_size--)
m_thread_pool.emplace_back([this] { m_ios.run(); });
But see Should the exception thrown by boost::asio::io_service::run() be caught?
Why unique_ptr for acceptor?
Why a separate class for acceptor? It's not like the server allows more than 1
why a vector of threads anyways? Prefer boost::thread_group
why a manual thread pool? Prefer asio::thread_pool - which already uses the hardware_concurrency if available
In terms of review, the TCPAsyncClient looks like an attempt to implement async_result protocol. It misses the mark on many points. So I'll just point to something like how do i return the response back to caller asynchronously using a final callback dispatched from on_read handler? or How do I make this HTTPS connection persistent in Beast?. They have pretty similar interfaces (perhaps except for the cancellation, if I remember correctly).
Fixed/Return Demo
Here's the completed sample. It includes request parsing, so the server waits the actual amount of time requested.
I scaled all the times down 10x so it can complete online.
Client and server are in single source. Starting with:
./sotest&
./sotest client
wait
Completes both in 6 seconds (see screengrab below)
Live On Coliru
#include <boost/asio.hpp>
#include <boost/spirit/home/x3.hpp> // for request parsing
#include <iomanip>
#include <iostream>
#include <map>
#include <mutex>
#include <thread>
namespace asio = boost::asio;
using asio::ip::tcp;
using boost::system::error_code;
using namespace std::chrono_literals;
using std::this_thread::sleep_for;
/////// server //////////////////////////////////////////////////////////
struct Service : std::enable_shared_from_this<Service> {
Service(tcp::socket sock) : m_sock(std::move(sock)) {}
void StartHandling() {
async_read_until(
m_sock, asio::dynamic_buffer(m_request), '\n',
[this, self = shared_from_this()](error_code ec, size_t bytes) {
onRequestReceived(ec, bytes);
});
}
private:
void onRequestReceived(error_code ec, size_t /*bytes*/) {
std::cout << "onRequestReceived: " << ec.message() << std::endl;
if (ec)
return;
// Process the request
m_response = ProcessingRequest(m_request);
async_write(
m_sock, asio::buffer(m_response),
[this, self = shared_from_this()](error_code ec, size_t bytes) {
onResponseSent(ec, bytes);
});
}
void onResponseSent(error_code ec, size_t /*bytes*/) {
std::cout << "onResponseSent: " << ec.message() << std::endl;
}
std::string static ProcessingRequest(std::string request) {
std::cout << "request: " << request << std::endl;
// parse the request, process it and prepare the response
namespace x3 = boost::spirit::x3;
double value;
if (parse(request.begin(), request.end(),
"EMULATE_LONG_CALC_OP " >> x3::double_ >> "s" >> x3::eol >> x3::eoi,
value)) //
{
// Emulating time-consuming operation
sleep_for(1.0s * value);
return "Waited " + std::to_string(value) + "s\n";
}
return "Unknown request\n";
}
tcp::socket m_sock;
std::string m_request, m_response;
};
struct Server {
Server(asio::any_io_executor ex, uint16_t port_num)
: m_acceptor{ex, {{}, port_num}} {
m_acceptor.listen();
accept_loop();
}
void Stop() { m_acceptor.cancel(); }
private:
void accept_loop() {
m_acceptor.async_accept([this](error_code ec, tcp::socket sock) {
std::cout << "OnAccept: " << ec.message() << std::endl;
if (!ec) {
std::make_shared<Service>(std::move(sock))->StartHandling();
accept_loop();
}
//m_acceptor.close();
});
}
tcp::acceptor m_acceptor;
};
void server(uint16_t port) try {
asio::thread_pool io;
Server srv{io.get_executor(), port};
sleep_for(6s);
srv.Stop();
io.join();
} catch (std::exception const& e) {
std::cout << "Exception: " << e.what() << std::endl;
}
/////// client //////////////////////////////////////////////////////////
struct RequestOp : public std::enable_shared_from_this<RequestOp> {
using Callback = std::function<void( //
unsigned /*request_id*/, std::string_view /*response*/, error_code)>;
RequestOp(asio::any_io_executor ex, const std::string& raw_ip_address,
uint16_t port_num, std::string request, unsigned id,
Callback callback)
: m_ep(asio::ip::address::from_string(raw_ip_address), port_num)
, m_sock(ex, m_ep.protocol())
, m_request(std::move(request))
, m_id(id)
, m_callback(callback) {}
void Run() {
// assumed on logical strand
m_sock.async_connect(
m_ep, [this, self = shared_from_this()](error_code ec) {
if ((m_ec = ec) || m_was_cancelled)
return onComplete();
asio::async_write(m_sock, asio::buffer(m_request),
[this, self = shared_from_this()](
error_code ec, size_t /*bytes*/) {
onRequestWritten(ec);
});
});
}
void Cancel() {
m_was_cancelled = true;
dispatch(m_sock.get_executor(), [self=shared_from_this()]{ self->doCancel(); });
}
private:
void doCancel() {
m_sock.cancel();
}
void onRequestWritten(error_code ec) {
if ((m_ec = ec) || m_was_cancelled)
return onComplete();
asio::async_read_until(
m_sock, asio::dynamic_buffer(m_response), '\n',
[this, self = shared_from_this()](error_code ec, size_t bytes) {
onResponseReceived(ec, bytes);
});
}
void onResponseReceived(error_code ec, size_t /*bytes*/) {
if ((m_ec = ec) || m_was_cancelled)
return onComplete();
if (!m_response.empty())
m_response.resize(m_response.size() - 1); // drop '\n'
onComplete();
}
void onComplete() {
// shutting down the connection, we don't care about the error code
// if function failed
error_code ignored_ec;
m_sock.shutdown(tcp::socket::shutdown_both, ignored_ec);
if(!m_ec && m_was_cancelled){
m_ec = asio::error::operation_aborted;
}
m_callback(m_id, m_response, m_ec);
}
tcp::endpoint m_ep; // Remote endpoint
tcp::socket m_sock;
std::string m_request;
std::string m_response; // Response represented as a string
error_code m_ec;
unsigned m_id;
Callback m_callback;
std::atomic_bool m_was_cancelled{false};
};
class AsyncTCPClient {
public:
AsyncTCPClient(asio::any_io_executor ex) : m_executor(ex) {}
using Duration = std::chrono::steady_clock::duration;
size_t emulateLongCalcOp(Duration delay, std::string const& raw_ip_address,
uint16_t port_num, RequestOp::Callback callback) {
auto request =
"EMULATE_LONG_CALC_OP " + std::to_string(delay / 1.0s) + "s\n";
std::cout << "Request: " << request << std::flush;
auto const request_id = m_nextId++;
auto session = std::make_shared<RequestOp>(
make_strand(m_executor), //
raw_ip_address, port_num, request, request_id, callback);
{
// active sessions list can be accessed from multiple thread, we
// guard it with a mutex to avoid data coruption
std::unique_lock lock(m_active_sessions_guard);
auto [_,ok] = m_pending_ops.emplace(request_id, session);
assert(ok); // duplicate request_id?
// optionally: garbage collect completed sessions
std::erase_if(m_pending_ops,
[](auto& kv) { return kv.second.expired(); });
};
session->Run();
return request_id;
}
// Cancels the request
void cancelRequest(unsigned request_id) {
std::unique_lock lock(m_active_sessions_guard);
if (auto session = m_pending_ops[request_id].lock())
session->Cancel();
}
private:
using PendingOp = std::weak_ptr<RequestOp>;
asio::any_io_executor m_executor;
std::mutex m_active_sessions_guard;
size_t m_nextId = 1;
std::map<int, PendingOp> m_pending_ops;
};
void handler(unsigned request_id, std::string_view response, error_code ec) {
std::cout << "Request #" << request_id << " ";
if (!ec.failed())
std::cout << "Response: " << std::quoted(response) << std::endl;
else if (ec == asio::error::operation_aborted)
std::cout << "Cancelled" << std::endl;
else
std::cout << ec.message() << std::endl;
}
void client(uint16_t port) try {
asio::thread_pool io;
{
AsyncTCPClient client(io.get_executor());
auto id1 = client.emulateLongCalcOp(4s, "127.0.0.1", port, handler);
auto id2 = client.emulateLongCalcOp(1100ms, "127.0.0.1", port, handler);
auto id3 = client.emulateLongCalcOp(3500ms, "127.0.0.1", port, handler);
// cancel request 1
sleep_for(3s);
client.cancelRequest(id1);
sleep_for(1200ms);
client.cancelRequest(id2); // no effect, already completed
client.cancelRequest(id3); // no effect, already completed
// exit the application
}
io.join();
} catch (std::exception const& e) {
std::cout << "Exception: " << e.what() << std::endl;
}
/////// main //////////////////////////////////////////////////////////
int main(int argc, char**) {
if (argc > 1)
client(3333);
else
server(3333);
}
Prints client:
Request: EMULATE_LONG_CALC_OP 4.000000s
Request: EMULATE_LONG_CALC_OP 1.100000s
Request: EMULATE_LONG_CALC_OP 3.500000s
Request #2 Response: "Waited 1.100000s"
Request #1 Cancelled
Request #3 Response: "Waited 3.500000s"
Prints server:
OnAccept: Success
OnAccept: Success
onRequestReceived: Success
request: EMULATE_LONG_CALC_OP 1.100000s
onRequestReceived: Success
request: EMULATE_LONG_CALC_OP 4.000000s
OnAccept: Success
onRequestReceived: Success
request: EMULATE_LONG_CALC_OP 3.500000s
onResponseSent: Success
onResponseSent: Success
onResponseSent: Success
OnAccept: Operation canceled

Related

boost::asio::async_write write ssl::stream succuess but server not get

I code a ssl server and client run a pingpang process, after a little time, client say send data success but server not get it.
client run in multi thread, when single thread, seem normal.
and i try add timer to add shake hand, then server can get all data, but i want it can run rightly with out shake hand
can anyone help figure out whats wrong.
here is my server
#include <cstdlib>
#include <functional>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind/bind.hpp>
using boost::asio::ip::tcp;
class session : public std::enable_shared_from_this<session> {
public:
session(tcp::socket socket, boost::asio::ssl::context &context)
: socket_(std::move(socket), context), m_strand(socket.get_executor()) {
}
void start() {
do_handshake();
}
private:
void do_handshake() {
auto self(shared_from_this());
socket_.async_handshake(boost::asio::ssl::stream_base::server,
[this, self](const boost::system::error_code &error) {
if (!error) {
do_read();
}
});
}
void do_read() {
auto self(shared_from_this());
socket_.async_read_some(boost::asio::buffer(data_),
[this, self](const boost::system::error_code &ec, std::size_t length) {
if (!ec) {
std::cout << "get <";
std::cout.write(data_, length);
std::cout << std::endl;
do_write(length);
}
});
}
void do_write(std::size_t length) {
auto self(shared_from_this());
std::cout << "send <";
std::cout.write(data_, length);
std::cout << std::endl;
boost::asio::async_write(socket_, boost::asio::buffer(data_, length),
[this, self](const boost::system::error_code &ec,
std::size_t /*length*/) {
if (!ec) {
do_read();
}
});
}
boost::asio::ssl::stream<tcp::socket> socket_;
boost::asio::strand<boost::asio::ip::tcp::socket::executor_type> m_strand;
char data_[1024];
};
class server {
public:
server(boost::asio::io_context &io_context, unsigned short port)
: acceptor_(io_context, tcp::endpoint(tcp::v4(), port)),
context_(boost::asio::ssl::context::sslv23) {
context_.set_options(
boost::asio::ssl::context::default_workarounds
| boost::asio::ssl::context::no_sslv2
| boost::asio::ssl::context::single_dh_use);
context_.set_password_callback(std::bind(&server::get_password, this));
context_.use_certificate_chain_file("server.pem");
context_.use_private_key_file("server.pem", boost::asio::ssl::context::pem);
context_.use_tmp_dh_file("dh2048.pem");
do_accept();
}
private:
std::string get_password() const {
return "test";
}
void do_accept() {
acceptor_.async_accept(
[this](const boost::system::error_code &error, tcp::socket socket) {
if (!error) {
std::make_shared<session>(std::move(socket), context_)->start();
}
do_accept();
});
}
tcp::acceptor acceptor_;
boost::asio::ssl::context context_;
};
int main(int argc, char *argv[]) {
try {
if (argc != 2) {
std::cerr << "Usage: server <port>\n";
return 1;
}
boost::asio::io_context io_context;
using namespace std; // For atoi.
server s(io_context, atoi(argv[1]));
io_context.run();
}
catch (std::exception &e) {
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
and next client
#include <cstdlib>
#include <cstring>
#include <functional>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/thread.hpp>
using boost::asio::ip::tcp;
using std::placeholders::_1;
using std::placeholders::_2;
enum {
max_length = 1024
};
class client {
public:
client(boost::asio::io_context &io_context,
boost::asio::ssl::context &context,
const tcp::resolver::results_type &endpoints)
: socket_(io_context, context), strand_(io_context.get_executor()) {
socket_.set_verify_mode(boost::asio::ssl::verify_peer);
socket_.set_verify_callback(
std::bind(&client::verify_certificate, this, _1, _2));
connect(endpoints);
}
private:
bool verify_certificate(bool preverified,
boost::asio::ssl::verify_context &ctx) {
char subject_name[256];
X509 *cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
std::cout << "Verifying " << subject_name << "\n";
return true;
}
void connect(const tcp::resolver::results_type &endpoints) {
boost::asio::async_connect(socket_.lowest_layer(), endpoints,
[this](const boost::system::error_code &error,
const tcp::endpoint & /*endpoint*/) {
if (!error) {
handshake();
} else {
std::cout << "Connect failed: " << error.message() << "\n";
}
});
}
void handshake() {
socket_.async_handshake(boost::asio::ssl::stream_base::client,
[this](const boost::system::error_code &error) {
if (!error) {
send_request("hello ssl");
boost::asio::post(strand_, std::bind(&client::recv, this));
} else {
std::cout << "Handshake failed: " << error.message() << "\n";
}
});
}
void send_request(const std::string &msg) {
boost::asio::async_write(
socket_, boost::asio::buffer(msg),
[this](const boost::system::error_code &error, std::size_t length) {
if (!error) {
std::cout << "send data success, size : " << length << std::endl;
} else {
std::cout << "Write failed: " << error.message() << std::endl;
}
});
}
void recv() {
boost::asio::async_read(
socket_, buffer_, boost::asio::transfer_exactly(9),
boost::asio::bind_executor(
strand_, [this](const boost::system::error_code &error, std::size_t length) {
if (!error) {
std::istream buffer(&buffer_);
std::vector<char> msg(length, 0);
buffer.readsome(msg.data(), length);
std::string recvMsg(msg.begin(), msg.end());
std::cout << recvMsg << std::endl;
send_request(recvMsg);
boost::asio::post(strand_, std::bind(&client::recv, this));
} else {
std::cout << "Read failed: " << error.message() << std::endl;
}
}));
}
boost::asio::ssl::stream<tcp::socket> socket_;
boost::asio::streambuf buffer_;
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
};
int main(int argc, char *argv[]) {
try {
if (argc != 3) {
std::cerr << "Usage: client <host> <port>\n";
return 1;
}
boost::asio::io_context io_context;
tcp::resolver resolver(io_context);
auto endpoints = resolver.resolve(argv[1], argv[2]);
boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
ctx.load_verify_file("ca.pem");
client c(io_context, ctx, endpoints);
boost::thread_group threadPool;
for (size_t i = 0; i < boost::thread::hardware_concurrency(); ++i) {
threadPool.create_thread(boost::bind(&boost::asio::io_context::run, &io_context));
}
io_context.run();
}
catch (std::exception &e) {
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
when data not send to server,client will print like this
hello ssl
hello ssl
send data success, size : 9
send data success, size : 9
Check this out. If you remove the thread_group (as far as I can tell, it adds no value), everything works. This is a good sign that you have a threading bug.
I'm not in the mood to read the code until I see the problem, so let's circle a bit.
Adding ASAN/UBSAN shows nothing bad immediately, so that's good.
Let me look at the code a little then.
session creates a m_strand - which is never used...
you forgot to join the extra threads
Now that I noticed some potential confusion around the strand, I looked at the client strand use. And see that it is inconsistent:
the socket itself is NOT on the strand
send_request doesn't run on nor bind the completion handler to the strand's executor
the communication is full-duplex (meaning async_write and async_read happen concurrently).
this means that where client::recv is posted to the strand, it doesn't actually synchronize threaded access to socket_ (because the send_request is not tied to the strand in the first place)
If the above is surprising, you're not the first to fall into that:
Why is `net::dispatch` needed when the I/O object already has an executor?. In your example connect() and handshake() can be considered safe because they form a logical strand (sequential flow of execution). The problem arises with the concurrent paths.
By far the simplest way to fix the situation seems to construct socket_ from the strand_. This implies reordering the members so strand_ is initialized first:
client(boost::asio::io_context& io_context, ssl::context& context,
const tcp::resolver::results_type& endpoints)
: strand_(io_context.get_executor())
, socket_(strand_, context)
Next up, all posts to the strand can be dropped because they always happen from a completion handler on that strand.
send_request("hello ssl");
recv(); // already on the strand in this completion handler
The mild irony is that send_request was executed under the implied assumption that it was on the strand.
The cleaned up programs until this point are
File client.cpp
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/thread.hpp>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <iostream>
using boost::asio::ip::tcp;
using boost::system::error_code;
using std::placeholders::_1;
using std::placeholders::_2;
namespace ssl = boost::asio::ssl;
class client {
public:
client(boost::asio::io_context& io_context, ssl::context& context,
const tcp::resolver::results_type& endpoints)
: strand_(io_context.get_executor())
, socket_(strand_, context)
{
socket_.set_verify_mode(ssl::verify_peer);
socket_.set_verify_callback(
std::bind(&client::verify_certificate, this, _1, _2));
connect(endpoints);
}
private:
bool verify_certificate(bool preverified, ssl::verify_context& ctx)
{
char subject_name[256];
X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
std::cout << "Verifying " << subject_name << "\n";
return true;
}
void connect(const tcp::resolver::results_type& endpoints)
{
async_connect( //
socket_.lowest_layer(), endpoints,
bind_executor(
strand_, [this](error_code error, const tcp::endpoint&) {
if (!error) {
handshake();
} else {
std::cout << "Connect failed: " << error.message() << "\n";
}
}));
}
void handshake()
{
socket_.async_handshake(
ssl::stream_base::client,
bind_executor(strand_, [this](error_code error) {
if (!error) {
send_request("hello ssl");
recv(); // already on the strand in this completion handler
} else {
std::cout << "Handshake failed: " << error.message()
<< "\n";
}
}));
}
void send_request(std::string const& msg)
{
msg_ = msg;
async_write(
socket_, boost::asio::buffer(msg_),
bind_executor(
strand_, [/*this*/](error_code error, std::size_t length) {
if (!error) {
std::cout << "send data success, size : " << length << std::endl;
} else {
std::cout << "Write failed: " << error.message() << std::endl;
}
}));
}
void recv()
{
async_read(
socket_, buffer_, boost::asio::transfer_exactly(9),
boost::asio::bind_executor(
strand_, [this](error_code error, std::size_t length) {
if (!error) {
std::istream buffer(&buffer_);
std::vector<char> msg(length, 0);
buffer.readsome(msg.data(), length);
msg_.assign(msg.begin(), msg.end());
std::cout << msg_ << std::endl;
send_request(msg_);
recv(); // already on the strand in this completion handler
} else {
std::cout << "Read failed: " << error.message() << std::endl;
}
}));
}
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
ssl::stream<tcp::socket> socket_;
boost::asio::streambuf buffer_;
std::string msg_;
};
int main(int argc, char* argv[])
{
try {
if (argc != 3) {
std::cerr << "Usage: client <host> <port>\n";
return 1;
}
boost::asio::io_context io_context;
tcp::resolver resolver(io_context);
auto endpoints = resolver.resolve(argv[1], argv[2]);
ssl::context ctx(ssl::context::sslv23);
ctx.load_verify_file("ca.pem");
client c(io_context, ctx, endpoints);
boost::thread_group threadPool;
for (size_t i = 0; i < boost::thread::hardware_concurrency(); ++i) {
threadPool.create_thread(
boost::bind(&boost::asio::io_context::run, &io_context));
}
threadPool.join_all();
//io_context.run();
return 0;
} catch (std::exception const& e) {
std::cerr << "Exception: " << e.what() << "\n";
return 1;
}
}
File server.cpp
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind/bind.hpp>
#include <cstdlib>
#include <functional>
#include <iostream>
namespace ssl = boost::asio::ssl;
using boost::asio::ip::tcp;
using boost::system::error_code;
class session : public std::enable_shared_from_this<session> {
public:
session(tcp::socket socket, ssl::context& context)
: socket_(std::move(socket), context)
, m_strand(socket.get_executor())
{
}
void start()
{
do_handshake();
}
private:
void do_handshake()
{
auto self(shared_from_this());
socket_.async_handshake(ssl::stream_base::server,
[this, self](error_code error) {
if (!error) {
do_read();
}
});
}
void do_read()
{
auto self(shared_from_this());
socket_.async_read_some(
boost::asio::buffer(data_),
[this, self](error_code ec, std::size_t length) {
if (!ec) {
std::cout << "get <";
std::cout.write(data_, length);
std::cout << std::endl;
do_write(length);
}
});
}
void do_write(std::size_t length)
{
auto self(shared_from_this());
std::cout << "send <";
std::cout.write(data_, length);
std::cout << std::endl;
boost::asio::async_write(
socket_, boost::asio::buffer(data_, length),
[this, self](error_code ec, std::size_t /*length*/) {
if (!ec) {
do_read();
}
});
}
ssl::stream<tcp::socket> socket_;
boost::asio::strand<tcp::socket::executor_type> m_strand;
char data_[1024];
};
class server {
public:
server(boost::asio::io_context& io_context, unsigned short port)
: acceptor_(io_context, tcp::endpoint(tcp::v4(), port))
, context_(ssl::context::sslv23)
{
context_.set_options(ssl::context::default_workarounds |
ssl::context::no_sslv2 |
ssl::context::single_dh_use);
context_.set_password_callback(std::bind(&server::get_password, this));
context_.use_certificate_chain_file("server.pem");
context_.use_private_key_file("server.pem", ssl::context::pem);
context_.use_tmp_dh_file("dh2048.pem");
do_accept();
}
private:
std::string get_password() const
{
return "test";
}
void do_accept()
{
acceptor_.async_accept([this](error_code error, tcp::socket socket) {
if (!error) {
std::make_shared<session>(std::move(socket), context_)->start();
}
do_accept();
});
}
tcp::acceptor acceptor_;
ssl::context context_;
};
int main(int argc, char* argv[])
{
try {
if (argc != 2) {
std::cerr << "Usage: server <port>\n";
return 1;
}
boost::asio::io_context io_context;
server s(io_context, std::atoi(argv[1]));
io_context.run();
return 0;
} catch (std::exception const& e) {
std::cerr << "Exception: " << e.what() << "\n";
return 1;
}
}
Other Problems
Lifetime Error
UBSAN/ASAN didn't catch it,but this is wrong:
void send_request(const std::string& msg)
{
async_write(
socket_, boost::asio::buffer(msg),
...
The problem is the lifetime of msg, which disappears before the async operation got a chance to run, let alone complete. So, move the buffer so the lifetime is sufficient (e.g. member msg_).
Concurrent Writes
When the client locks up, it shows
send data success, size : 9
hello ssl
hello ssl
send data success, size : 9
send data success, size : 9
This indicates that a second hello ssl is received before send is initiated. This means that a second send is initiated. Under the hood this cancels a duplex synchronization object inside the ssl stream context. You can see this with -DBOOST_ASIO_ENABLE_HANDLER_TRACKING:
#asio|1630155694.209267|51139|deadline_timer#0x7ffc6fa61e48.cancel
Visualizing with the handlerviz.pl script:
The problem is violating the requirements here:
The program must ensure that the stream performs no other write operations (such as async_write, the stream's async_write_some function, or any other composed operations that perform writes) until this operation completes.
Two easy ways to fix it:
change the IO from full duplex to sequential read/write/read/write just like the server
make an output queue that contains messages still to be written in sequence
Fixed Solution
This uses an outbox as in the second solution for overlapping writes above. I've also taken the liberty to
remove the unnecessary intermediate buffer streambuf buffer_ instead reading directly into a string.
replace io_context + thread_group with the more elegant thread_pool
many minor improvements (some mentioned above)
File client.cpp
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/thread.hpp>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <iostream>
using boost::asio::ip::tcp;
using boost::system::error_code;
using std::placeholders::_1;
using std::placeholders::_2;
namespace ssl = boost::asio::ssl;
using Executor = boost::asio::thread_pool::executor_type;
class client {
public:
client(Executor ex, ssl::context& context,
const tcp::resolver::results_type& endpoints)
: strand_(ex)
, socket_(strand_, context)
{
socket_.set_verify_mode(ssl::verify_peer);
socket_.set_verify_callback(
std::bind(&client::verify_certificate, this, _1, _2));
connect(endpoints);
}
private:
bool verify_certificate(bool preverified, ssl::verify_context& ctx)
{
char subject_name[256];
X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
std::cout << "Verifying " << subject_name << "\n";
return true;
}
void connect(const tcp::resolver::results_type& endpoints)
{
async_connect( //
socket_.lowest_layer(), endpoints,
bind_executor(
strand_, [this](error_code error, const tcp::endpoint&) {
if (!error) {
handshake();
} else {
std::cout << "Connect failed: " << error.message() << "\n";
}
}));
}
void handshake()
{
socket_.async_handshake(
ssl::stream_base::client,
bind_executor(strand_, [this](error_code error) {
if (!error) {
send_request("hello ssl");
recv(); // already on the strand in this completion handler
} else {
std::cout << "Handshake failed: " << error.message()
<< "\n";
}
}));
}
void send_request(std::string msg)
{
outbox_.push_back(std::move(msg));
if (outbox_.size() == 1)
{
send_loop();
}
}
void send_loop()
{
async_write( //
socket_, boost::asio::buffer(outbox_.back()),
bind_executor(
strand_, [this](error_code error, std::size_t length) {
if (!error) {
std::cout << "send data success, size : " << length << std::endl;
outbox_.pop_back();
} else {
std::cout << "Write failed: " << error.message() << std::endl;
}
if (!outbox_.empty())
send_loop();
}));
}
void recv()
{
async_read(
socket_, boost::asio::dynamic_buffer(buffer_), boost::asio::transfer_exactly(9),
boost::asio::bind_executor(
strand_, [this](error_code error, std::size_t length) {
if (!error) {
std::cout << buffer_ << std::endl;
send_request(std::move(buffer_));
recv(); // already on the strand in this completion handler
} else {
std::cout << "Read failed: " << error.message() << std::endl;
}
}));
}
boost::asio::strand<Executor> strand_;
ssl::stream<tcp::socket> socket_;
std::string buffer_;
std::deque<std::string> outbox_;
};
int main(int argc, char* argv[])
{
try {
if (argc != 3) {
std::cerr << "Usage: client <host> <port>\n";
return 1;
}
ssl::context ctx(ssl::context::sslv23);
ctx.load_verify_file("ca.pem");
boost::asio::thread_pool io;
tcp::resolver resolver(io);
client c(io.get_executor(), ctx, resolver.resolve(argv[1], argv[2]));
io.join();
return 0;
} catch (std::exception const& e) {
std::cerr << "Exception: " << e.what() << "\n";
return 1;
}
}
File server.cpp
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind/bind.hpp>
#include <cstdlib>
#include <functional>
#include <iostream>
namespace ssl = boost::asio::ssl;
using boost::asio::ip::tcp;
using boost::system::error_code;
class session : public std::enable_shared_from_this<session> {
public:
session(tcp::socket socket, ssl::context& context)
: socket_(std::move(socket), context)
{
}
void start()
{
do_handshake();
}
private:
void do_handshake()
{
auto self(shared_from_this());
socket_.async_handshake(ssl::stream_base::server,
[this, self](error_code error) {
if (!error) {
do_read();
}
});
}
void do_read()
{
auto self(shared_from_this());
socket_.async_read_some(
boost::asio::buffer(data_),
[this, self](error_code ec, std::size_t length) {
if (!ec) {
std::cout << "get <";
std::cout.write(data_.data(), length);
std::cout << std::endl;
do_write(length);
}
});
}
void do_write(std::size_t length)
{
auto self(shared_from_this());
std::cout << "send <";
std::cout.write(data_.data(), length);
std::cout << std::endl;
boost::asio::async_write(
socket_, boost::asio::buffer(data_.data(), length),
[this, self](error_code ec, std::size_t /*length*/) {
if (!ec) {
do_read();
}
});
}
ssl::stream<tcp::socket> socket_;
std::array<char, 1024> data_;
};
class server {
public:
server(boost::asio::io_context& io_context, unsigned short port)
: acceptor_(io_context, tcp::endpoint(tcp::v4(), port))
, context_(ssl::context::sslv23)
{
context_.set_options(ssl::context::default_workarounds |
ssl::context::no_sslv2 |
ssl::context::single_dh_use);
context_.set_password_callback(std::bind(&server::get_password, this));
context_.use_certificate_chain_file("server.pem");
context_.use_private_key_file("server.pem", ssl::context::pem);
context_.use_tmp_dh_file("dh2048.pem");
do_accept();
}
private:
std::string get_password() const
{
return "test";
}
void do_accept()
{
acceptor_.async_accept([this](error_code error, tcp::socket socket) {
if (!error) {
std::make_shared<session>(std::move(socket), context_)->start();
}
do_accept();
});
}
tcp::acceptor acceptor_;
ssl::context context_;
};
int main(int argc, char* argv[])
{
try {
if (argc != 2) {
std::cerr << "Usage: server <port>\n";
return 1;
}
boost::asio::io_context io_context;
server s(io_context, std::atoi(argv[1]));
io_context.run();
return 0;
} catch (std::exception const& e) {
std::cerr << "Exception: " << e.what() << "\n";
return 1;
}
}
Live Demo:
As you can see (using a uniq -dc trick to suppress all non-repeating lines) now it happily continues in the case where multiple receives come in before send is initiated.

how to create boost::async_read and async_write with timeout

I'm trying to write server with boost::asio but I want that the boost::asio::async_read operation to be with time out if no data is coming, but i can figure how to do it.
this is my code so far
void do_read_header() {
auto self(shared_from_this());
std::cout << "do_read_header\n";
boost::asio::async_read(
socket_, boost::asio::buffer(res.data(), res.header_length),
[this, self](boost::system::error_code ec,
std::size_t length) {
if (!ec && res.decode_header()) {
do_read_body();
}
});
do_write();
}
void do_read_body() {
auto self(shared_from_this());
Message msg;
std::cout << "do_read_body\n";
boost::asio::async_read(
socket_, boost::asio::buffer(res.body(), res.body_length()),
[this, self](boost::system::error_code ec,
std::size_t length) {
if (!length) {
return;
}
if (!ec) {
try {
std::cout << "read " << res.body() << "\n";
request_queue_.send(res.body(), res.body_length(),
0);
} catch (const std::exception& ex) {
std::cout << ex.what() << "\n";
}
} else {
if (ec) {
std::cerr << "read error:" << ec.value()
<< " message: " << ec.message() << "\n";
}
socket_.close();
}
do_read_header();
});
}
void start() {
post(strand_, [this, self = shared_from_this()] {
do_read_header();
do_write();
});
}
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::cout << "accept connection\n";
std::make_shared<Session>(std::move(socket_))
->start();
}
do_accept();
});
}
tcp::acceptor acceptor_;
tcp::socket socket_;
};
You can add a deadline timer that cancels the IO operation. You can observe the cancellation because the completion will be called with error::operation_aborted.
deadline_.expires_from_now(1s);
deadline_.async_wait([self, this] (error_code ec) {
if (!ec) socket_.cancel();
});
I spent about 45 minutes making the rest of your code self-contained:
in this example I'll assume that we
want to wait for max 5s for a new header to arrive (so after a new session was started or until the next request arrives on the same session)
after which the fullbody must be received within 1s
Note also that we avoid closing the socket - that's done in the session's destructor. It's better to shutdown gracefully.
Live Demo
#include <boost/asio.hpp>
#include <boost/endian/arithmetic.hpp>
#include <boost/interprocess/ipc/message_queue.hpp>
#include <iomanip>
#include <iostream>
using namespace std::chrono_literals;
namespace bip = boost::interprocess;
using boost::asio::ip::tcp;
using boost::system::error_code;
using Queue = boost::interprocess::message_queue;
static constexpr auto MAX_MESG_LEN = 100;
static constexpr auto MAX_MESGS = 10;
struct Message {
using Len = boost::endian::big_uint32_t;
struct header_t {
Len len;
};
static const auto header_length = sizeof(header_t);
std::array<char, MAX_MESG_LEN + header_length> buf;
char const* data() const { return buf.data(); }
char* data() { return buf.data(); }
char const* body() const { return data() + header_length; }
char* body() { return data() + header_length; }
static_assert(std::is_standard_layout_v<header_t> and
std::is_trivial_v<header_t>);
Len body_length() const { return std::min(h().len, max_body_length()); }
Len max_body_length() const { return buf.max_size() - header_length; }
bool decode_header() { return h().len <= max_body_length(); }
bool set_body(std::string_view value) {
assert(value.length() <= max_body_length());
h().len = value.length();
std::copy_n(value.begin(), body_length(), body());
return (value.length() == body_length()); // not truncated
}
private:
header_t& h() { return *reinterpret_cast<header_t*>(data()); }
header_t const& h() const { return *reinterpret_cast<header_t const*>(data()); }
};
struct Session : std::enable_shared_from_this<Session> {
Session(tcp::socket&& s) : socket_(std::move(s)) {}
void start() {
post(strand_,
[ this, self = shared_from_this() ] { do_read_header(); });
}
private:
using Strand = boost::asio::strand<tcp::socket::executor_type>;
using Timer = boost::asio::steady_timer;
tcp::socket socket_{strand_};
Strand strand_{make_strand(socket_.get_executor())};
Message res;
Queue request_queue_{bip::open_or_create, "SendQueue", MAX_MESGS, MAX_MESG_LEN};
Timer recv_deadline_{strand_};
void do_read_header() {
auto self(shared_from_this());
std::cout << "do_read_header: " << res.header_length << std::endl;
recv_deadline_.expires_from_now(5s);
recv_deadline_.async_wait([ self, this ](error_code ec) {
if (!ec) {
std::cerr << "header timeout" << std::endl;
socket_.cancel();
}
});
boost::asio::async_read(
socket_, boost::asio::buffer(res.data(), res.header_length),
[ this, self ](error_code ec, size_t /*length*/) {
std::cerr << "header: " << ec.message() << std::endl;
recv_deadline_.cancel();
if (!ec && res.decode_header()) {
do_read_body();
} else {
socket_.shutdown(tcp::socket::shutdown_both);
}
});
}
void do_read_body() {
auto self(shared_from_this());
// Message msg;
std::cout << "do_read_body: " << res.body_length() << std::endl;
recv_deadline_.expires_from_now(1s);
recv_deadline_.async_wait([self, this] (error_code ec) {
if (!ec) {
std::cerr << "body timeout" << std::endl;
socket_.cancel();
}
});
boost::asio::async_read(
socket_,
boost::asio::buffer(res.body(), res.body_length()),
boost::asio::transfer_exactly(res.body_length()),
[ this, self ](error_code ec, std::size_t length) {
std::cerr << "body: " << ec.message() << std::endl;
recv_deadline_.cancel();
if (!ec) {
try {
// Not safe to print unless NUL-terminated, see e.g.
// https://stackoverflow.com/questions/66278813/boost-deadline-timer-causes-stack-buffer-overflow/66279497#66279497
if (length)
request_queue_.send(res.body(), res.body_length(), 0);
} catch (const std::exception& ex) {
std::cout << ex.what() << std::endl;
}
do_read_header();
} else {
socket_.shutdown(tcp::socket::shutdown_both);
}
});
}
};
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 ](error_code ec) {
std::cerr << "async_accept: " << ec.message() << std::endl;
if (!ec) {
std::cerr << "session: " << socket_.remote_endpoint() << std::endl;
std::make_shared<Session>(std::move(socket_))->start();
}
do_accept();
});
}
tcp::acceptor acceptor_;
tcp::socket socket_;
};
int main(int argc, char**) {
Queue queue{bip::open_or_create, "SendQueue", MAX_MESGS, MAX_MESG_LEN}; // ensure it exists
if (argc == 1) {
boost::asio::io_context ioc;
Server s(ioc, 8989);
ioc.run_for(10s);
} else {
while (true) {
using Buf = std::array<char, MAX_MESG_LEN>;
Buf buf;
unsigned prio;
size_t n;
queue.receive(buf.data(), buf.size(), n, prio);
std::cout << "Received: " << std::quoted(std::string_view(buf.data(), n)) << std::endl;
}
}
}
Testable with
./sotest
In another terminal:
./sotest consumer
And somewhere else e.g. some requests that don't timeout:
for msg in '0000 0000' '0000 0001 31' '0000 000c 6865 6c6c 6f20 776f 726c 640a'
do
xxd -r -p <<< "$msg" |
netcat localhost 8989 -w 1
done
Or, multi-request on single session, then session times out (-w 6 exceeds 5s):
msg='0000 0000 0000 0001 31 0000 000c 6865 6c6c 6f20 776f 726c 640a'; xxd -r -p <<< "$msg"| netcat localhost 8989 -w 6
I had to solve a similar issue when implementing the Serial Port wrapper and this is the simplified version of the code:
// Possible outcome of a read. Set by callback
enum class ReadResult
{
ResultInProgress,
ResultSuccess,
ResultError,
ResultTimeout
};
std::streamsize read(char* s, std::streamsize n, boost::posix_time::time_duration timeout)
{
boost::asio::io_service io;
boost::asio::serial_port port(io);
// result is atomic to avoid race condition
std::atomic<ReadResult> result(ReadResult::ResultInProgress);
std::streamsize bytesTransferred = 0;
// Create async timer that fires after duration
boost::asio::deadline_timer timer(io);
timer.expires_from_now(timeout);
timer.async_wait(boost::bind([&result](const boost::system::error_code& error){
// If there wasn't any error and reading is still in progress, set result as timeout
if (!error && result == ReadResult::ResultInProgress)
result = ReadResult::ResultTimeout;
},boost::asio::placeholders::error));
// Read asynchronously
port.async_read_some(boost::asio::buffer(s, n), boost::bind([&result,&bytesTransferred](const boost::system::error_code& error,
const size_t transferred){
// If there wasn't any error on read finish set result as sucess else as error
if (!error){
result = ReadResult::ResultSuccess;
bytesTransferred = transferred;
return;
}
result = ReadResult::ResultError;
},boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
// Run loop until timeout, error or success occurs
for (;;)
{
io.run_one();
switch (result)
{
case ReadResult::ResultSuccess:
// Success, cancel timer and return amount of bytes read
timer.cancel();
return bytesTransferred;
case ReadResult::ResultTimeout:
// Timeout occured, cancel read and throw exception
port.cancel();
throw(TimeoutException("Timeout expired"));
case ReadResult::ResultError:
// Error occured, cancel read and timer and throw exception
port.cancel();
timer.cancel();
throw(std::ios_base::failure("Error while reading"));
default:
//if result is still in progress remain in the loop
break;
}
}
}
So basically, what you have to do is :
initialize timer with io_service
call async_wait on a timer with a callback function that sets result timeout flag
call async_read on your socket with a callback that sets result success flag
loop until result is no longer "InProgress", note that io.run_one() in loop is important
handle result the result
you could use it for any asynchronous function

Gracefully shutdown boost::beast HTTPServer

I have a http server (boost beast) taken from here Boost Beast HTTP Server. The function void
http_server(tcp::acceptor& acceptor, tcp::socket& socket) keeps the server always running. I want to know if there is a graceful way to shutdown the server.
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio.hpp>
#include <chrono>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <memory>
#include <string>
namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp>
namespace net = boost::asio; // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace my_program_state
{
std::size_t
request_count()
{
static std::size_t count = 0;
return ++count;
}
std::time_t
now()
{
return std::time(0);
}
}
class http_connection : public std::enable_shared_from_this<http_connection>
{
public:
http_connection(tcp::socket socket)
: socket_(std::move(socket))
{
}
// Initiate the asynchronous operations associated with the connection.
void
start()
{
read_request();
check_deadline();
}
private:
// The socket for the currently connected client.
tcp::socket socket_;
// The buffer for performing reads.
beast::flat_buffer buffer_{8192};
// The request message.
http::request<http::dynamic_body> request_;
// The response message.
http::response<http::dynamic_body> response_;
// The timer for putting a deadline on connection processing.
net::steady_timer deadline_{
socket_.get_executor(), std::chrono::seconds(60)};
// Asynchronously receive a complete request message.
void
read_request()
{
auto self = shared_from_this();
http::async_read(
socket_,
buffer_,
request_,
[self](beast::error_code ec,
std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
if(!ec)
self->process_request();
});
}
// Determine what needs to be done with the request message.
void
process_request()
{
response_.version(request_.version());
response_.keep_alive(false);
switch(request_.method())
{
case http::verb::get:
response_.result(http::status::ok);
response_.set(http::field::server, "Beast");
create_response();
break;
default:
// We return responses indicating an error if
// we do not recognize the request method.
response_.result(http::status::bad_request);
response_.set(http::field::content_type, "text/plain");
beast::ostream(response_.body())
<< "Invalid request-method '"
<< std::string(request_.method_string())
<< "'";
break;
}
write_response();
}
// Construct a response message based on the program state.
void
create_response()
{
if(request_.target() == "/count")
{
response_.set(http::field::content_type, "text/html");
beast::ostream(response_.body())
<< "<html>\n"
<< "<head><title>Request count</title></head>\n"
<< "<body>\n"
<< "<h1>Request count</h1>\n"
<< "<p>There have been "
<< my_program_state::request_count()
<< " requests so far.</p>\n"
<< "</body>\n"
<< "</html>\n";
}
else if(request_.target() == "/time")
{
response_.set(http::field::content_type, "text/html");
beast::ostream(response_.body())
<< "<html>\n"
<< "<head><title>Current time</title></head>\n"
<< "<body>\n"
<< "<h1>Current time</h1>\n"
<< "<p>The current time is "
<< my_program_state::now()
<< " seconds since the epoch.</p>\n"
<< "</body>\n"
<< "</html>\n";
}
else
{
response_.result(http::status::not_found);
response_.set(http::field::content_type, "text/plain");
beast::ostream(response_.body()) << "File not found\r\n";
}
}
// Asynchronously transmit the response message.
void
write_response()
{
auto self = shared_from_this();
response_.set(http::field::content_length, response_.body().size());
http::async_write(
socket_,
response_,
[self](beast::error_code ec, std::size_t)
{
self->socket_.shutdown(tcp::socket::shutdown_send, ec);
self->deadline_.cancel();
});
}
// Check whether we have spent enough time on this connection.
void
check_deadline()
{
auto self = shared_from_this();
deadline_.async_wait(
[self](beast::error_code ec)
{
if(!ec)
{
// Close socket to cancel any outstanding operation.
self->socket_.close(ec);
}
});
}
};
// "Loop" forever accepting new connections.
void
http_server(tcp::acceptor& acceptor, tcp::socket& socket)
{
acceptor.async_accept(socket,
[&](beast::error_code ec)
{
if(!ec)
std::make_shared<http_connection>(std::move(socket))->start();
http_server(acceptor, socket);
});
}
int
main(int argc, char* argv[])
{
try
{
// Check command line arguments.
if(argc != 3)
{
std::cerr << "Usage: " << argv[0] << " <address> <port>\n";
std::cerr << " For IPv4, try:\n";
std::cerr << " receiver 0.0.0.0 80\n";
std::cerr << " For IPv6, try:\n";
std::cerr << " receiver 0::0 80\n";
return EXIT_FAILURE;
}
auto const address = net::ip::make_address(argv[1]);
unsigned short port = static_cast<unsigned short>(std::atoi(argv[2]));
net::io_context ioc{1};
tcp::acceptor acceptor{ioc, {address, port}};
tcp::socket socket{ioc};
http_server(acceptor, socket);
ioc.run();
}
catch(std::exception const& e)
{
std::cerr << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
Call the stop method on your io_context object to make it break out of the run loop.
That is:
ioc.stop();
https://www.boost.org/doc/libs/1_72_0/doc/html/boost_asio/reference/io_context/stop.html

ASIO - How to stop simple coroutine based server?

I have the following simple coroutine-based server:
class Server
{
private:
boost::asio::io_service Service;
boost::asio::ip::tcp::acceptor Acceptor;
boost::asio::ip::tcp::socket Socket;
private:
void Accept(boost::asio::yield_context Yield);
void Write(boost::asio::yield_context Yield);
public:
Server(): Acceptor(Service), Socket(Service) {}
void Open(unsigned short PortNum);
void Run();
void Stop();
};
void Server::Accept(boost::asio::yield_context Yield)
{
boost::system::error_code ec;
for (;;)
{
Socket.close();
Acceptor.async_accept(Socket,Yield[ec]);
spawn(Yield,std::bind(&Server::Write,this,Yield[ec]));
}
}
void Server::Write(boost::asio::yield_context Yield)
{
char InBuffer[1024]= {};
std::size_t Size;
boost::system::error_code ec;
double Data= 6.66;
for (;;)
{
boost::asio::streambuf OutBuffer;
std::ostream os(&OutBuffer);
Size= Socket.async_read_some(boost::asio::buffer(InBuffer),Yield[ec]);
if (ec)
break;
os.write(reinterpret_cast<const char *>(&Data),sizeof(double));
Socket.async_write_some(OutBuffer.data(),Yield[ec]);
if (ec)
break;
}
}
void Server::Open(unsigned short PortNum)
{
Acceptor.open(boost::asio::ip::tcp::v4());
Acceptor.bind({{},PortNum});
Acceptor.listen();
}
void Server::Run()
{
spawn(Service,std::bind(&Server::Accept,this,std::placeholders::_1));
Service.run();
}
void Server::Stop()
{
Service.stop();
}
I want to run this server on a thread and stop it cleanly when the main program is about to finish:
int main()
{
Server s;
s.Open(1024);
std::thread Thread(&Server::Run,&s);
Sleep(10'000);
s.Stop();
Thread.join();
}
Unfortunately, if there is a connected socket, when I call Stop an exception boost::coroutines::detail::forced_unwind is thrown.
I have also tried creating an explicit strand and dispatching a Socket.close() before stopping with the same result.
Is there something wrong with this approach?
I’m having trouble trying to stop gracefully a similar server ( stackoverflow.com/questions/50833730/…). – metalfox 4 hours ago
Here's a minimal change that shows how to handle
an Exit command that closes a session
a Shutdown command that closes the server (so it stops accepting connections and terminates after the last session exits)
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
using boost::asio::ip::tcp;
using boost::system::error_code;
using boost::asio::streambuf;
int main() {
boost::asio::io_service svc;
tcp::acceptor a(svc);
a.open(tcp::v4());
a.set_option(tcp::acceptor::reuse_address(true));
a.bind({{}, 6767}); // bind to port 6767 on localhost
a.listen(5);
using session = std::shared_ptr<tcp::socket>;
std::function<void()> do_accept;
std::function<void(session)> do_session;
do_session = [&](session s) {
// do a read
auto buf = std::make_shared<boost::asio::streambuf>();
async_read_until(*s, *buf, "\n", [&,s,buf](error_code ec, size_t /*bytes*/) {
if (ec)
std::cerr << "read failed: " << ec.message() << "\n";
else {
std::istream is(buf.get());
std::string line;
while (getline(is, line)) // FIXME being sloppy with partially read lines
{
async_write(*s, boost::asio::buffer("Ack\n", 4), [&,s,buf](error_code ec, size_t) {
if (ec) std::cerr << "write failed: " << ec.message() << "\n";
});
if (line == "Exit") {
std::cout << "Exit received\n";
return;
}
if (line == "Shutdown") {
std::cout << "Server shutdown requested\n";
a.close();
return;
}
}
do_session(s); // full duplex, can read while writing, using a second buffer
}
});
};
do_accept = [&] {
auto s = std::make_shared<session::element_type>(svc);
a.async_accept(*s, [&,s](error_code ec) {
if (ec)
std::cerr << "accept failed: " << ec.message() << "\n";
else {
do_session(s);
do_accept(); // accept the next
}
});
};
do_accept(); // kick-off
svc.run(); // wait for shutdown (Ctrl-C or failure)
}
Note the sample sessions
echo -en "hello world\nExit\n" | netcat 127.0.0.1 6767
echo -en "hello world\nShutdown\n" | netcat 127.0.0.1 6767
Printing
Ack
Ack
Ack
Ack
Exit received
Server shutdown requested
accept failed: Operation canceled
A Terminate Command
If you want a "Terminate" command that actively closes all open sessions and shuts down the server, you'll have to
keep a list of sessions
or use signal
You can see code for both approaches here: Boost ASIO: Send message to all connected clients
The simplest way to integrate with the current sample:
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
#include <list>
using boost::asio::ip::tcp;
using boost::system::error_code;
using boost::asio::streambuf;
int main() {
boost::asio::io_service svc;
tcp::acceptor a(svc);
a.open(tcp::v4());
a.set_option(tcp::acceptor::reuse_address(true));
a.bind({{}, 6767}); // bind to port 6767 on localhost
a.listen(5);
using session = std::shared_ptr<tcp::socket>;
using sessref = std::weak_ptr<tcp::socket>;
std::function<void()> do_accept;
std::function<void(session)> do_session;
std::list<sessref> session_list;
auto garbage_collect_sessions = [&session_list] {
session_list.remove_if(std::mem_fn(&sessref::expired));
};
do_session = [&](session s) {
// do a read
auto buf = std::make_shared<boost::asio::streambuf>();
async_read_until(*s, *buf, "\n", [&,s,buf](error_code ec, size_t /*bytes*/) {
if (ec)
std::cerr << "read failed: " << ec.message() << "\n";
else {
std::istream is(buf.get());
std::string line;
while (getline(is, line)) // FIXME being sloppy with partially read lines
{
async_write(*s, boost::asio::buffer("Ack\n", 4), [&,s,buf](error_code ec, size_t) {
if (ec) std::cerr << "write failed: " << ec.message() << "\n";
});
if (line == "Exit") {
std::cout << "Exit received\n";
return;
}
if (line == "Shutdown") {
std::cout << "Server shutdown requested\n";
a.close();
return;
}
if (line == "Terminate") {
std::cout << "Server termination requested\n";
a.close();
for (auto wp : session_list) {
if (auto session = wp.lock())
session->close();
}
return;
}
}
do_session(s); // full duplex, can read while writing, using a second buffer
}
});
};
do_accept = [&] {
auto s = std::make_shared<session::element_type>(svc);
a.async_accept(*s, [&,s](error_code ec) {
if (ec)
std::cerr << "accept failed: " << ec.message() << "\n";
else {
garbage_collect_sessions();
session_list.push_back(s);
do_session(s);
do_accept(); // accept the next
}
});
};
do_accept(); // kick-off
svc.run(); // wait for shutdown (Ctrl-C or failure)
}
Which obvioiusly uses a session_list to implement the "Terminate" command:
if (line == "Terminate") {
std::cout << "Server termination requested\n";
a.close();
for (auto wp : session_list) {
if (auto session = wp.lock())
session->close();
}
return;
}

Asio: Prevent asynchronous client from being deleted?

I have the following code, trying to code an asynchronous client.
The problem is that in main(), the Client gets deleted in the try-catch block, because execution leaves the scope.
I've tried to come up with a solution to this problem, like adding a while(true), but I don't like this approach. Also, I don't prefer a getchar().
Due to the asynchronous nature of the calls, both connect() and loop() returns immediately.
How can I fix this?
#include <iostream>
#include <thread>
#include <string>
#include <boost\asio.hpp>
#include <Windows.h>
#define DELIM "\r\n"
using namespace boost;
class Client {
public:
Client(const std::string& raw_ip_address, unsigned short port_num) :
m_ep(asio::ip::address::from_string(raw_ip_address), port_num), m_sock(m_ios)
{
m_work.reset(new asio::io_service::work(m_ios));
m_thread.reset(new std::thread([this]() {
m_ios.run();
}));
m_sock.open(m_ep.protocol());
}
void connect()
{
m_sock.async_connect(m_ep, [this](const system::error_code& ec)
{
if (ec != 0) {
std::cout << "async_connect() error: " << ec.message() << " (" << ec.value() << ") " << std::endl;
return;
}
std::cout << "Connection to server has been established." << std::endl;
});
}
void loop()
{
std::thread t = std::thread([this]()
{
recv();
});
t.join();
}
void recv()
{
asio::async_read_until(m_sock, buf, DELIM, [this](const system::error_code& ec, std::size_t bytes_transferred)
{
if (ec != 0) {
std::cout << "async_read_until() error: " << ec.message() << " (" << ec.value() << ") " << std::endl;
return;
}
std::istream is(&buf);
std::string req;
std::getline(is, req, '\r');
is.get(); // discard newline
std::cout << "Received: " << req << std::endl;
if (req == "alive") {
recv();
}
else if (req == "close") {
close();
return;
}
else {
send(req + DELIM);
}
});
}
void send(std::string resp)
{
auto s = std::make_shared<std::string>(resp);
asio::async_write(m_sock, asio::buffer(*s), [this, s](const system::error_code& ec, std::size_t bytes_transferred)
{
if (ec != 0) {
std::cout << "async_write() error: " << ec.message() << " (" << ec.value() << ") " << std::endl;
return;
}
else {
recv();
}
});
}
void close()
{
m_sock.close();
m_work.reset();
m_thread->join();
}
private:
asio::io_service m_ios;
asio::ip::tcp::endpoint m_ep;
asio::ip::tcp::socket m_sock;
std::unique_ptr<asio::io_service::work> m_work;
std::unique_ptr<std::thread> m_thread;
asio::streambuf buf;
};
int main()
{
const std::string raw_ip_address = "127.0.0.1";
const unsigned short port_num = 8001;
try {
Client client(raw_ip_address, port_num);
client.connect();
client.loop();
}
catch (system::system_error &err) {
std::cout << "main() error: " << err.what() << " (" << err.code() << ") " << std::endl;
return err.code().value();
}
return 0;
}
You've not really understood how asio works. Typically in the main thread(s) you will call io_service::run() (which will handle all the asynchronous events.)
To ensure the lifetime of the Client, use a shared_ptr<> and ensure this shared pointer is used in the handlers. For example..
io_service service;
{
// Create the client - outside of this scope, asio will manage
// the life time of the client
auto client = make_shared<Client>(service);
client->connect(); // setup the connect operation..
}
// Now run the io service event loop - this will block until there are no more
// events to handle
service.run();
Now you need to refactor your Client code:
class Client : public std::enable_shared_from_this<Client> {
Client(io_service& service): socket_(service) ...
{ }
void connect() {
// By copying the shared ptr to the lambda, the life time of
// Client is guaranteed
socket_.async_connect(endpoint_, [self = this->shared_from_this()](auto ec)
{
if (ec) {
return;
}
// Read
self->read(self);
});
}
void read(shared_ptr<Client> self) {
// By copying the shared ptr to the lambda, the life time of
// Client is guaranteed
asio::async_read_until(socket_, buffer_, DELIM, [self](auto ec, auto size)
{
if (ec) {
return;
}
// Handle the data
// Setup the next read operation
self->read(self)
});
}
};
You have a thread for the read operation - which is not necessary. That will register one async read operation and return immediately. You need to register a new read operation to continue reading the socket (as I've sketched out..)
You can post any function to io_service via post(Handler)
http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/io_service/post.html
Then in the main() do something like:
while (!exit) {
io_service.run_one();
}
Or call io_service::run_one or io_service::run in the main()