how to create boost::async_read and async_write with timeout - c++

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

Related

Asio async_read_until EOF Error in Asynchronous TCP Server

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

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.

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;
}

Boost::Asio Server reads blank message from client, even after the write handle finishes

I try to write an async message to the server from my client code, the write handler gets called with the correct bytes sent; however, the server receives 0 bytes.
Cliente output:
You are connected
You received the following message from the server:
Sat Aug 20 17:42:01 2016
Sending...
Server output:
Server is online!
127.0.0.1:51973 connected!
Client has received the messaged.
You received the following message from the server:
server source:
using boost::asio::ip::tcp;
std::string make_daytime_string()
{
using namespace std; // For time_t, time and ctime;
time_t now = time(0);
return ctime(&now);
}
class tcp_connection: public boost::enable_shared_from_this<tcp_connection>
{
public:
typedef boost::shared_ptr<tcp_connection> pointer;
static pointer create(boost::asio::io_service& io_service)
{
return pointer(new tcp_connection(io_service));
}
tcp::socket& socket()
{
return socket_;
}
// Call boost::asio::async_write() to serve the data to the client.
// We are using boost::asio::async_write(),
// rather than ip::tcp::socket::async_write_some(),
// to ensure that the entire block of data is sent.
void start()
{
// The data to be sent is stored in the class member m_message
// as we need to keep the data valid
// until the asynchronous operation is complete.
m_message = make_daytime_string();
// When initiating the asynchronous operation,
// and if using boost::bind(),
// we must specify only the arguments
// that match the handler's parameter list.
// In this code, both of the argument placeholders
// (boost::asio::placeholders::error
// and boost::asio::placeholders::bytes_transferred)
// could potentially have been removed,
// since they are not being used in handle_write().
std::cout << socket_.remote_endpoint().address().to_string() << ":" << socket_.remote_endpoint().port() << " connected!" << std::endl;
boost::asio::async_write(socket_, boost::asio::buffer(m_message),
boost::bind(&tcp_connection::handle_write, shared_from_this()));
boost::asio::async_read(socket_, boost::asio::buffer(_buffer), boost::bind(&tcp_connection::handle_receive, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
private:
tcp_connection(boost::asio::io_service& io_service)
: socket_(io_service)
{
}
// handle_write() is responsible for any further actions
// for this client connection.
void handle_write() // call back.. when it finishes sending, we come here
{
std::cout << "Client has received the messaged. " << std::endl;
}
void handle_receive(const boost::system::error_code& ErrorCode, std::size_t bytes_transferred)
{
std::cout << "You received the following message from the server:" << std::endl;
std::cout.write(_buffer.data(), bytes_transferred);
}
tcp::socket socket_;
std::string m_message;
boost::array<char, 126> _buffer;
};
class tcp_server
{
public:
tcp_server(boost::asio::io_service& io_service) : acceptor_(io_service, tcp::endpoint(tcp::v4(), 7171))
{
// start_accept() creates a socket and
// initiates an asynchronous accept operation
// to wait for a new connection.
start_accept();
}
private:
void start_accept()
{
// creates a socket
tcp_connection::pointer new_connection = tcp_connection::create(acceptor_.get_io_service());
// initiates an asynchronous accept operation
// to wait for a new connection.
acceptor_.async_accept(new_connection->socket(),
boost::bind(&tcp_server::handle_accept, this, new_connection,
boost::asio::placeholders::error));
}
// handle_accept() is called when the asynchronous accept operation
// initiated by start_accept() finishes. It services the client request
void handle_accept(tcp_connection::pointer new_connection,
const boost::system::error_code& error)
{
if (!error)
{
new_connection->start();
}
// Call start_accept() to initiate the next accept operation.
start_accept();
}
tcp::acceptor acceptor_;
};
int main()
{
std::cout << "Server is online!" << std::endl;
try
{
boost::asio::io_service io_service;
tcp_server server(io_service);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
client source:
#include <iostream>
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
std::string make_daytime_string()
{
using namespace std; // For time_t, time and ctime;
time_t now = time(0);
return ctime(&now);
}
class Connection
{
public:
Connection(boost::asio::io_service& io) : _socket(io){}
void connect(tcp::resolver::iterator& point)
{
boost::asio::async_connect(_socket, point, boost::bind(&Connection::onConnected, this, boost::asio::placeholders::error));
}
void onConnected(const boost::system::error_code& ErrorCode)
{
std::cout << "You are connected" << std::endl;
// receive first message on onReceive
boost::asio::async_read(_socket, boost::asio::buffer(_buffer), boost::bind(&Connection::onReceive, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void onSend(const boost::system::error_code& ErrorCode, std::size_t bytes_transferred)
{
std::cout << "Sending..." << std::endl;
}
void onReceive(const boost::system::error_code& ErrorCode, std::size_t bytes_transferred)
{
std::cout << "You received the following message from the server:" << std::endl;
std::cout.write(_buffer.data(), bytes_transferred);
// send first message on onSend
m_message = make_daytime_string();
boost::asio::async_write(_socket, boost::asio::buffer(m_message), boost::bind(&Connection::onSend, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
tcp::socket& getSocket()
{
return _socket;
}
private:
tcp::socket _socket;
boost::array<char, 126> _buffer;
std::string m_message;
};
int main()
{
try
{
boost::asio::io_service io_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query("127.0.0.1", "7171");
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
Connection conn(io_service);
conn.connect(endpoint_iterator);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
#edit:
new client code:
class Connection : public boost::enable_shared_from_this<Connection>
{
public:
typedef boost::shared_ptr<Connection> pointer;
static pointer create(boost::asio::io_service& io_service)
{
return pointer(new Connection(io_service));
}
tcp::socket& socket()
{
return _socket;
}
void connect(tcp::resolver::iterator& point)
{
boost::asio::async_connect(_socket, point, boost::bind(&Connection::connect_handler, this, boost::asio::placeholders::error));
}
void connect_handler(const boost::system::error_code& error)
{
if(error)
{
std::cout << "Error on connect_handler: " << error.message() << std::endl;
return;
}
std::cout << "You are connected to the server." << std::endl;
start();
}
void start()
{
start_write();
start_read();
}
private:
// private ctor
Connection(boost::asio::io_service& io) : _socket(io){}
void start_write()
{
_daymessage = make_daytime_string();
boost::asio::async_write(_socket, boost::asio::buffer(_daymessage),
boost::bind(&Connection::handle_write, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_write(const boost::system::error_code& error,
size_t bytes)
{
if(error)
{
std::cout << "Error on handle write: " << error.message() << std::endl;
return;
}
std::cout << "Message has been sent!" << std::endl;
start_write();
}
void start_read()
{
// Start an asynchronous operation to read a newline-delimited message.
boost::asio::async_read_until(_socket, _buffer, '\n',
boost::bind(&Connection::handle_read, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_read(const boost::system::error_code& error, size_t bytes)
{
if(error)
{
std::cout << "Error on handle read: " << error.message() << std::endl;
return;
}
// Extract the newline-delimited message from the buffer.
std::string line;
std::istream is(&_buffer);
std::getline(is, line);
if (!line.empty())
{
std::cout << "Received: " << line << "\n";
}
start_read();
}
tcp::socket _socket;
std::string _daymessage;
boost::asio::streambuf _buffer;
};
int main()
{
std::cout << "Client is running!" << std::endl;
try
{
boost::asio::io_service io_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query("127.0.0.1", "7171");
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
auto connection = Connection::create(io_service);
connection->connect(endpoint_iterator);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
obs: I got a "tr1::bad_weak_ptr" when I use public ctor and instantiate with make_shared. The private ctor and static member func worked fine.
There are many issues with the code you have provided:
In your server connection class, you are using raw this pointer
instead of using shared_from_this. This was resulting in the
operation getting cancelled as your resource was getting out of
scope.
The code is making use of async_read with a buffer of size 126. I
guess the operation would not finish until you receive atleast that
many bytes. Use async_read_until instead. Due to lack of any protocol or predefined byte sequence, I have modified the code
to send '\n' as the delimiter.
Never ignore the error code received.
Modified Server Code:
class tcp_connection: public boost::enable_shared_from_this<tcp_connection>
{
public:
typedef boost::shared_ptr<tcp_connection> pointer;
static pointer create(boost::asio::io_service& io_service)
{
return pointer(new tcp_connection(io_service));
}
tcp::socket& socket()
{
return socket_;
}
// Call boost::asio::async_write() to serve the data to the client.
// We are using boost::asio::async_write(),
// rather than ip::tcp::socket::async_write_some(),
// to ensure that the entire block of data is sent.
void start()
{
// The data to be sent is stored in the class member m_message
// as we need to keep the data valid
// until the asynchronous operation is complete.
m_message = make_daytime_string();
// When initiating the asynchronous operation,
// and if using boost::bind(),
// we must specify only the arguments
// that match the handler's parameter list.
// In this code, both of the argument placeholders
// (boost::asio::placeholders::error
// and boost::asio::placeholders::bytes_transferred)
// could potentially have been removed,
// since they are not being used in handle_write().
std::cout << socket_.remote_endpoint().address().to_string() << ":" << socket_.remote_endpoint().port() << " connected!" << std::endl;
boost::asio::async_write(socket_, boost::asio::buffer(m_message),
boost::bind(&tcp_connection::handle_write, shared_from_this()));
boost::asio::async_read_until(socket_,
_buffer,
'\n',
boost::bind(&tcp_connection::handle_receive,
shared_from_this(),
boost::asio::placeholders::error));
}
private:
tcp_connection(boost::asio::io_service& io_service)
: socket_(io_service)
{
}
// handle_write() is responsible for any further actions
// for this client connection.
void handle_write() // call back.. when it finishes sending, we come here
{
std::cout << "Client has received the messaged. " << std::endl;
}
void handle_receive(const boost::system::error_code& ErrorCode)
{
std::cout << "You received the following message from the server: "<< std::endl;
if (ErrorCode) {
std::cout << "Error occured: " << ErrorCode.message() << std::endl;
return;
}
std::string line;
std::istream is(&_buffer);
std::getline(is, line);
std::cout << line << std::endl;
}
tcp::socket socket_;
std::string m_message;
boost::asio::streambuf _buffer;
};
Modified Client Code:
class Connection: public boost::enable_shared_from_this<Connection>
{
public:
Connection(boost::asio::io_service& io) : _socket(io){}
void connect(tcp::resolver::iterator& point)
{
boost::asio::async_connect(_socket, point, boost::bind(&Connection::onConnected, this, boost::asio::placeholders::error));
}
void onConnected(const boost::system::error_code& ErrorCode)
{
std::cout << "You are connected" << std::endl;
// receive first message on onReceive
boost::asio::async_read_until(_socket,
_buffer,
'\n',
boost::bind(&Connection::onReceive,
this, boost::asio::placeholders::error));
}
void onSend(const boost::system::error_code& ErrorCode, std::size_t bytes_transferred)
{
std::cout << "Sending..." << std::endl;
}
void onReceive(const boost::system::error_code& ErrorCode)
{
std::cout << "You received the following message from the server:" << std::endl;
//std::cout.write(_buffer.data(), bytes_transferred);
// send first message on onSend
m_message = make_daytime_string() + '\n';
std::cout << "Sending " << m_message << std::endl;
boost::asio::async_write(_socket, boost::asio::buffer(m_message), boost::bind(&Connection::onSend, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
tcp::socket& getSocket()
{
return _socket;
}
private:
tcp::socket _socket;
boost::asio::streambuf _buffer;
std::string m_message;
};

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()