ASIO example code closing socket before it should - c++

I need a parallel synchronous TCP solution using ASIO. I'm trying to get the example code from these examples working: https://github.com/jvillasante/asio-network-programming-cookbook/tree/master/src (using the server in ch04: 02_Sync_parallel_tcp_server.cpp and the client in ch03: 01_Sync_tcp_client.cpp).
The only thing I changed is the logging to append to text files.
The problem is that while the server runs fine, the client dies after returning a single response from the server:
libc++abi.dylib: terminating with uncaught exception of type boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::system::system_error> >: shutdown: Socket is not connected
Code for the server:
#include <boost/asio.hpp>
#include <atomic>
#include <memory>
#include <thread>
#include <iostream>
#include <fstream>
using namespace boost;
class Service {
public:
Service() = default;
void StartHandlingClient(std::shared_ptr<asio::ip::tcp::socket> sock) {
std::thread th{[this, sock]() { HandleClient(sock); }};
th.detach();
}
private:
void HandleClient(std::shared_ptr<asio::ip::tcp::socket> sock) {
try {
asio::streambuf request;
asio::read_until(*sock.get(), request, '\n');
std::istream is(&request);
std::string line;
std::getline(is, line);
std::ofstream log("logfile2.txt", std::ios_base::app | std::ios_base::out);
log << "Request: " << line << "\n" << std::flush;
// Emulate request processing.
int i = 0;
while (i != 1000000) i++;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// Sending response.
std::string response = "Response\n";
asio::write(*sock.get(), asio::buffer(response));
} catch (std::system_error& e) {
std::ofstream log("logfile1.txt", std::ios_base::app | std::ios_base::out);
log << "Error occurred! Error code = " << e.code().value() << ". Message: " << e.what() << "\n" << std::flush;
}
// Clean up
delete this;
}
};
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_acceptor.listen();
}
void Accept() {
auto sock = std::make_shared<asio::ip::tcp::socket>(m_ios);
m_acceptor.accept(*sock.get());
(new Service)->StartHandlingClient(sock);
}
private:
asio::io_service& m_ios;
asio::ip::tcp::acceptor m_acceptor;
};
class Server {
public:
Server() : m_stop{false} {}
void Start(unsigned short port_num) {
m_thread.reset(new std::thread([this, port_num]() { Run(port_num); }));
}
void Stop() {
m_stop.store(true);
m_thread->join();
}
private:
void Run(unsigned short port_num) {
Acceptor acc{m_ios, port_num};
while (!m_stop.load()) {
acc.Accept();
}
}
private:
std::unique_ptr<std::thread> m_thread;
std::atomic<bool> m_stop;
asio::io_service m_ios;
};
int main() {
unsigned short port_num = 3333;
try {
Server srv;
srv.Start(port_num);
std::this_thread::sleep_for(std::chrono::seconds(60));
srv.Stop();
} catch (std::system_error& e) {
std::ofstream log("logfile1.txt", std::ios_base::app | std::ios_base::out);
log << "Error occurred! Error code = " << e.code().value() << ". Message: " << e.what() << "\n" << std::flush;
}
return 0;
}
Code for the client:
#include <boost/asio.hpp>
#include <iostream>
#include <fstream>
using namespace boost;
class SyncTCPClient {
public:
SyncTCPClient(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_sock.open(m_ep.protocol());
}
~SyncTCPClient() { close(); }
void connect() { m_sock.connect(m_ep); }
std::string emulateLongComputationOp(unsigned int duration_sec) {
std::string request = "EMULATE_LONG_COMP_OP " + std::to_string(duration_sec) + "\n";
sendRequest(request);
return receiveResponse();
}
private:
void close() {
if (m_sock.is_open()) {
std::ofstream log("logfile1.txt", std::ios_base::app | std::ios_base::out);
log << "shutting down\n" << std::flush;
m_sock.shutdown(asio::ip::tcp::socket::shutdown_both);
log << "closing the socket\n" << std::flush;
m_sock.close();
log << "socket closed\n" << std::flush;
}
}
void sendRequest(const std::string& request) { asio::write(m_sock, asio::buffer(request)); }
std::string receiveResponse() {
asio::streambuf buf;
asio::read_until(m_sock, buf, '\n');
std::istream input(&buf);
std::string response;
std::getline(input, response);
return response;
}
private:
asio::io_service m_ios;
asio::ip::tcp::endpoint m_ep;
asio::ip::tcp::socket m_sock;
};
int main() {
const std::string raw_ip_address = "127.0.0.1";
const unsigned short port_num = 3333;
try {
SyncTCPClient client{raw_ip_address, port_num};
// Sync connect.
client.connect();
std::cout << "Sending request to the server...\n";
std::string response = client.emulateLongComputationOp(10);
std::cout << "Response received: " << response << "\n";
} catch (std::system_error& e) {
std::ofstream log("logfile1.txt", std::ios_base::app | std::ios_base::out);
log << "Error occurred! Error code = " << e.code().value() << ". Message: " << e.what() << "\n" << std::flush;
return e.code().value();
}
return 0;
}

I don't see a lot wrong, and I cannot reproduce the problem with the code shown.
Things I do see:
the thread procedure could be a static because it's stateless (delete this is a code smell)
the thread needn't be detached (using boost::thread_group::join_all would be much better)
you were writing to the same logfile from server as well as client; results are undefined
spelling .store() and .load() on an atomic<bool> is un-idiomatic
spelling out *sock.get() on any kind of smart pointer is unforgivably un-idiomatic
writing code().value() - swallowing the category - is a BAD thing to do, and e.what() is NOT the way to get the message (use e.code().message()).
If you need flush, you might as well use std::endl
There's really no reason to use a shared_ptr in c++14:
asio::ip::tcp::socket sock(m_ios);
m_acceptor.accept(sock);
std::thread([sock=std::move(sock)]() mutable { HandleClient(sock); }).detach();
In C++11 stick to:
auto sock = std::make_shared<asio::ip::tcp::socket>(m_ios);
m_acceptor.accept(*sock);
std::thread([sock] { HandleClient(*sock); }).detach();
This means HandleClient can just take a ip::tcp::socket& instead of a smart pointer.
INTEGRATING
Server.cpp
#include <atomic>
#include <boost/asio.hpp>
#include <fstream>
#include <iostream>
#include <memory>
#include <thread>
using namespace boost;
static void HandleClient(asio::ip::tcp::socket& sock) {
try {
asio::streambuf buf;
asio::read_until(sock, buf, '\n');
std::string request;
getline(std::istream(&buf), request);
std::ofstream log("server.log", std::ios_base::app | std::ios_base::out);
log << "Request: " << request << std::endl;
// Emulate request processing.
int i = 0;
while (i != 1000000)
i++;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// Sending response.
std::string response = "Response\n";
asio::write(sock, asio::buffer(response));
} catch (std::system_error &e) {
std::ofstream log("server.log", std::ios_base::app | std::ios_base::out);
log << e.what() << " " << e.code() << ": " << e.code().message() << std::endl;
}
}
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_acceptor.listen();
}
void Accept() {
auto sock = std::make_shared<asio::ip::tcp::socket>(m_ios);
m_acceptor.accept(*sock);
std::thread([sock] { HandleClient(*sock); }).detach();
}
private:
asio::io_service &m_ios;
asio::ip::tcp::acceptor m_acceptor;
};
class Server {
public:
Server() : m_stop{ false } {}
void Start(unsigned short port_num) {
m_thread.reset(new std::thread([this, port_num]() { Run(port_num); }));
}
void Stop() {
m_stop = true;
m_thread->join();
}
private:
void Run(unsigned short port_num) {
Acceptor acc{ m_ios, port_num };
while (!m_stop) {
acc.Accept();
}
}
private:
std::unique_ptr<std::thread> m_thread;
std::atomic<bool> m_stop;
asio::io_service m_ios;
};
int main() {
unsigned short port_num = 3333;
try {
Server srv;
srv.Start(port_num);
std::this_thread::sleep_for(std::chrono::seconds(60));
srv.Stop();
} catch (std::system_error &e) {
std::ofstream log("server.log", std::ios_base::app | std::ios_base::out);
log << e.what() << " " << e.code() << ": " << e.code().message() << std::endl;
}
}
Client.cpp
#include <boost/asio.hpp>
#include <fstream>
#include <iostream>
using namespace boost;
class SyncTCPClient {
public:
SyncTCPClient(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_sock.open(m_ep.protocol());
}
~SyncTCPClient() { close(); }
void connect() { m_sock.connect(m_ep); }
std::string emulateLongComputationOp(unsigned int duration_sec) {
std::string request = "EMULATE_LONG_COMP_OP " + std::to_string(duration_sec) + "\n";
sendRequest(request);
return receiveResponse();
}
private:
void close() {
if (m_sock.is_open()) {
std::ofstream log("client.log", std::ios_base::app | std::ios_base::out);
log << "shutting down" << std::endl;
m_sock.shutdown(asio::ip::tcp::socket::shutdown_both);
log << "closing the socket" << std::endl;
m_sock.close();
log << "socket closed" << std::endl;
}
}
void sendRequest(const std::string &request) { asio::write(m_sock, asio::buffer(request)); }
std::string receiveResponse() {
asio::streambuf buf;
asio::read_until(m_sock, buf, '\n');
std::string response;
getline(std::istream(&buf), response);
return response;
}
private:
asio::io_service m_ios;
asio::ip::tcp::endpoint m_ep;
asio::ip::tcp::socket m_sock;
};
int main() {
const std::string raw_ip_address = "127.0.0.1";
const unsigned short port_num = 3333;
try {
SyncTCPClient client{ raw_ip_address, port_num };
// Sync connect.
client.connect();
std::cout << "Sending request to the server...\n";
std::string response = client.emulateLongComputationOp(10);
std::cout << "Response received: " << response << std::endl;
} catch (std::system_error &e) {
std::ofstream log("client.log", std::ios_base::app | std::ios_base::out);
log << e.what() << " " << e.code() << ": " << e.code().message() << std::endl;
return e.code().value();
}
}

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

How to use Scatter/Gather IO in Boost Asio for sending a struct containing a vector?

I am trying to build a simple IPC protocol using Boost Asio where the server side will be sending a struct that contains a vector<uint8_t> to the client. I was suggested to use a scatter/gather IO approach, but I can't get it working, as it seems the client is only receiving part of the data it is expecting and it keeps waiting indefinitely for the rest of the data to arrive even though it should already be there.
This is what I have right now:
// File: client.cpp
#include <iostream>
#include <vector>
#include <boost/asio.hpp>
#include "ipc_common.hpp"
namespace ba = boost::asio;
using boost::asio::ip::tcp;
int main(int argc, char *argv[])
{
ba::io_context io;
std::vector<std::string> args(argv, argv + argc);
switch (args.size()) {
case 1:
args = {args.at(0), "localhost", "6869"};
break;
case 2:
args = {args.at(0), args.at(1), "6869"};
break;
case 3:
args = {args.at(0), args.at(1), args.at(2)};
break;
default:
std::clog << "usage: " << args.at(0) << " [host = localhost] [port = 6869]" << std::endl;
return 1;
}
try {
propertiesPacket properties;
properties.val1 = 9;
properties.val2 = 45;
tcp::socket socket(io);
tcp::resolver resolver(io);
connect(socket, resolver.resolve(args.at(1), args.at(2)));
write(socket, ba::buffer(&properties, sizeof(properties)));
uint16_t responseSize {};
ba::read(socket, ba::buffer(&responseSize, sizeof(uint16_t)));
std::clog << "client responseSize: " << responseSize << std::endl;
processedData response {};
std::vector<ba::mutable_buffer> responseBuffers {
ba::buffer(&response.size, sizeof(uint16_t)),
ba::buffer(&response.values, responseSize - sizeof(uint8_t))
};
ba::read(socket, responseBuffers);
std::clog << response.serialize();
return 0;
} catch (std::exception &e) {
std::clog << e.what() << std::endl;
return 1;
}
}
// File: server.cpp
#include <vector>
#include <boost/asio.hpp>
#include "ipc_common.hpp"
namespace ba = boost::asio;
using boost::asio::ip::tcp;
using boost::system::error_code;
using TCPSocket = tcp::socket;
class ServerConnection
: public std::enable_shared_from_this<ServerConnection>
{
public:
ServerConnection(TCPSocket socket)
: socket_(std::move(socket))
{ }
void start()
{
std::clog << __PRETTY_FUNCTION__ << std::endl;
doRead();
}
private:
void doRead()
{
std::clog << __PRETTY_FUNCTION__ << std::endl;
auto self(shared_from_this());
socket_.async_read_some(ba::buffer(&properties_, sizeof(properties_)),
[this, self](error_code ec, std::size_t length)
{
std::clog << "received " << length << std::endl;
if (!ec) {
processData();
std::vector<ba::const_buffer> msg {
ba::buffer(&filePacketSize_, sizeof(uint16_t)),
ba::buffer(&filePacket_.val, sizeof(filePacket_.val)),
ba::buffer(&filePacket_.values, sizeof(filePacket_.values))};
std::clog << "filePacketSize_: " << filePacketSize_ << std::endl;
ba::async_write(socket_, msg,
[this, self = shared_from_this()](error_code ec, std::size_t length)
{
std::clog << "written " << length << std::endl;
if (!ec) doRead();
});
}
});
}
void processData()
{
filePacket_.val = properties_.val1;
// Just for demonstration, we fill the vector with random values
std::random_device rd;
std::mt19937 re(rd()) ;
std::uniform_int_distribution<uint8_t> dist(0, 255);
for (size_t i {}; i < filePacket_.val; ++i) {
processedData.values.push_back(dist(re));
}
}
TCPSocket socket_;
propertiesPacket properties_;
processedData filePacket_;
uint16_t filePacketSize_;
};
class Server
{
public:
using IOContext = ba::io_context;
using TCPAcceptor = tcp::acceptor;
Server(IOContext& io, uint16_t port)
: socket_(io),
acceptor_(io, {tcp::v4(), port})
{
doAccept();
}
private:
void doAccept()
{
std::clog << __PRETTY_FUNCTION__ << std::endl;
acceptor_.async_accept(socket_,
[this](error_code ec)
{
if (!ec) {
std::clog << "Accepted " << socket_.remote_endpoint() << std::endl;
std::make_shared<ServerConnection>(std::move(socket_))->start();
doAccept();
}
else {
std::clog << "Accept " << ec.message() << std::endl;
}
});
}
TCPSocket socket_;
TCPAcceptor acceptor_;
};
int main(int argc, char* argv[])
{
std::vector<std::string> args(argv, argv + argc);
switch (args.size()) {
case 1:
args = {args.at(0), "6869"};
break;
case 2:
args = {args.at(0), args.at(1)};
break;
default:
std::clog << "usage: " << args.at(0) << " [port = 6869]" << std::endl;
return 1;
}
try {
ba::io_context io;
Server server(io, std::stoi(args.at(1)));
io.run();
} catch (std::exception &e) {
std::clog << e.what() << std::endl;
return 1;
}
return 0;
}
// File: ipc_common.hpp
#include <cstdint>
#include <vector>
#include <sstream>
#include <string>
struct propertiesPacket
{
uint8_t val1;
uint8_t val2;
};
struct processedData
{
uint8_t val;
std::vector<uint8_t> values;
std::string serialize()
{
std::stringstream sstream;
sstream << "val: " << (unsigned int)val << std::endl;
for (const auto &i : values)
{
sstream << i << " ";
}
sstream << std::endl;
return sstream.str();
}
};
What am I doing wrong?
The sample seems corrupted.
For one, args.at(3) and args.at(4) will by definition always throw, because by definition the switch statement earlier will always exit the client when there are more than 2 command line arguments (default:).
Secondly, the client read uses &response.size but no such member exists at all.
Thirdly, server processData uses a .val property of procesedData which isn't even a member (it's a type, likely should be filePacket_.val instead).
Fourthly, it assigns that from properties_.val; which ALSO doesn't exist at all (there's only val1 and val2).
Next up, rd isn't used to initialize the URBG (random engine, re). Instead it calls an unknown identifier named random_device(). Likely ought to be rd() instead.
Again, where it sais processedData.val you probably meant filePacket_.val
And where you write processedData.push_back(...) you probably meant to say filePacket_.values.push_back(...)...
There's a spurious ; behind void doAccept() in the Server
By contrast, the ; is missing after each struct definition in ipc_common.hpp
The processedData struct defines a serialize() method that is never used. It also uses a C-style cast where static_cast<unsigned>(val) would be safe.
Weirdly, the server "parses" args, and provides an optional default value BUT it never uses that. Instead, it uses argv[1] without checking argc at all. Oops.
That all aside, now comes the confusing part: how did you want the values to be written? This is not correct:
std::vector<ba::const_buffer> msg{
ba::buffer(&filePacketSize_, sizeof(uint16_t)),
ba::buffer(&filePacket_.val, sizeof(filePacket_.val)),
ba::buffer(&filePacket_.values, sizeof(filePacket_.values))};
values is a std::vector<> so you cannot hope to use it in a bitwise way. It'll just invoke Undefined Behaviour.
Besides, it's pretty unclear why filePacketSize_ is being written (it's never even assigned, or even initialized to a determinate value).
On the client side you read a responseSize as if one would be sent... Maybe you want to keep those two in sync.
Suggested Appraoch
I'd do away with the separate size value(s), since a vector already keeps track of that. I'd also make sure your processData doesn't always push_back because the vector would always keep growing.
I'd make a protocol that actually sends the message size before the message itself, and makes sure it's correct.
Let's also make the random data naturally printable (a..z) for simplicity:
void processData()
{
// Just for demonstration, we fill the vector with random characters
std::mt19937 re(std::random_device{}());
std::uniform_int_distribution<uint8_t> dist('a', 'z');
filePacket_.values.clear();
std::generate_n(back_inserter(filePacket_.values), properties_.val1,
[&] { return dist(re); });
}
Then in writing, let's do:
processData();
size_t length[] { filePacket_.values.size() };
std::vector<ba::const_buffer> msg{
ba::buffer(length),
ba::buffer(filePacket_.values)};
Note how, again, we avoid manually specifying any buffer sizes. Also, we let
the library figure out that values is a vector of POD elements and do the
math to convert the calculate the correct start address and buffer size for the
element data.
On the client side, we do the inverse:
size_t length = 0;
ba::read(socket, ba::buffer(&length, sizeof(length)));
response.values.resize(length);
ba::read(socket, ba::buffer(response.values));
(Here we can't avoid writing sizeof(length) without getting more clumsy than I'd like).
Full Demo
File ipc_common.hpp
// File: ipc_common.hpp
#include <cstdint>
#include <sstream>
#include <string>
#include <vector>
struct propertiesPacket {
uint8_t val1;
uint8_t val2;
};
struct processedData {
std::vector<uint8_t> values;
};
File server.cpp
#include <boost/asio.hpp>
#include <vector>
#include <iostream>
#include <random>
#include "ipc_common.hpp"
namespace ba = boost::asio;
using boost::asio::ip::tcp;
using boost::system::error_code;
using TCPSocket = tcp::socket;
class ServerConnection : public std::enable_shared_from_this<ServerConnection> {
public:
ServerConnection(TCPSocket socket) : socket_(std::move(socket))
{
}
void start()
{
std::clog << __PRETTY_FUNCTION__ << std::endl;
doRead();
}
private:
void doRead()
{
std::clog << __PRETTY_FUNCTION__ << std::endl;
auto self(shared_from_this());
socket_.async_read_some(
ba::buffer(&properties_, sizeof(properties_)),
[this, self](error_code ec, std::size_t length) {
std::clog << "received " << length << std::endl;
if (!ec) {
processData();
size_t length[] { filePacket_.values.size() };
std::vector<ba::const_buffer> msg{
ba::buffer(length), ba::buffer(filePacket_.values)};
ba::async_write(socket_, msg,
[this, self = shared_from_this()](
error_code ec, std::size_t length) {
std::clog << "written " << length
<< std::endl;
if (!ec)
doRead();
});
}
});
}
void processData()
{
// Just for demonstration, we fill the vector with random characters
std::mt19937 re(std::random_device{}());
std::uniform_int_distribution<uint8_t> dist('a', 'z');
filePacket_.values.clear();
std::generate_n(back_inserter(filePacket_.values), properties_.val1,
[&] { return dist(re); });
}
TCPSocket socket_;
propertiesPacket properties_;
processedData filePacket_;
};
class Server {
public:
using IOContext = ba::io_context;
using TCPAcceptor = tcp::acceptor;
Server(IOContext& io, uint16_t port)
: socket_(io)
, acceptor_(io, {tcp::v4(), port})
{
doAccept();
}
private:
void doAccept()
{
std::clog << __PRETTY_FUNCTION__ << std::endl;
acceptor_.async_accept(socket_, [this](error_code ec) {
if (!ec) {
std::clog << "Accepted " << socket_.remote_endpoint() << std::endl;
std::make_shared<ServerConnection>(std::move(socket_))->start();
doAccept();
} else {
std::clog << "Accept " << ec.message() << std::endl;
}
});
}
TCPSocket socket_;
TCPAcceptor acceptor_;
};
int main(int argc, char* argv[])
{
std::vector<std::string> args(argv, argv + argc);
switch (args.size()) {
case 1: args.push_back("6869"); break;
case 2: break;
default:
std::clog << "usage: " << args.at(0) << " [port = 6869]" << std::endl;
return 1;
}
try {
ba::io_context io;
Server server(io, std::stoi(args.at(1)));
io.run();
} catch (std::exception const &e) {
std::clog << e.what() << std::endl;
return 1;
}
}
File client.cpp
#include <iostream>
#include <vector>
#include <boost/asio.hpp>
#include "ipc_common.hpp"
namespace ba = boost::asio;
using boost::asio::ip::tcp;
int main(int argc, char *argv[])
{
ba::io_context io;
std::vector<std::string> args(argv, argv + argc);
switch (args.size()) {
case 1: args.push_back("localhost"); [[fallthrough]];
case 2: args.push_back("6869"); [[fallthrough]];
case 3: args.push_back("42"); [[fallthrough]];
case 4: args.push_back("99"); [[fallthrough]];
case 5: break;
default:
std::clog << "usage: " << args.at(0)
<< " [host = localhost] [port = 6869] [val1=42] [val2=99]"
<< std::endl;
return 1;
}
try {
propertiesPacket properties;
properties.val1 = std::stoul(args.at(3));
properties.val2 = std::stoul(args.at(4));
tcp::socket socket(io);
tcp::resolver resolver(io);
connect(socket, resolver.resolve({args.at(1), args.at(2)}));
write(socket, ba::buffer(&properties, sizeof(properties)));
processedData response{};
{
size_t length = 0;
ba::read(socket, ba::buffer(&length, sizeof(length)));
response.values.resize(length);
}
std::clog << "client response size: " << response.values.size() << std::endl;
ba::read(socket, ba::buffer(response.values));
std::clog.write(reinterpret_cast<char const*>(response.values.data()),
response.values.size()) << "\n";
// return 0;
} catch (std::exception &e) {
std::clog << e.what() << std::endl;
return 1;
}
}
Demo output:
Portability
You should probably keep byte ordering in mind as well. You could consider using JSON or another Well Known serialization format.

Trying to write UDP server class, io_context doesn't block

I try to open a UDP server. A baby example works (I receive what I expect and what wireshark also shows):
Baby example:
int main(int argc, char* const argv[])
{
try
{
boost::asio::io_context io_context;
boost::asio::ip::udp::endpoint ep(boost::asio::ip::udp::v4(), 60001);
boost::asio::ip::udp::socket sock(io_context, ep);
UDPServer server(std::move(sock), callbackUDP);
io_context.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
UDPServer.hpp:
#include <boost/asio.hpp>
#include <functional>
#include <vector>
#include <thread>
#define BUFFERSIZE 1501
class UDPServer
{
public:
explicit UDPServer(boost::asio::ip::udp::socket socket, std::function<void(const std::vector<char>&)> callbackFunction);
virtual ~UDPServer();
private:
void read();
boost::asio::ip::udp::socket socket_;
boost::asio::ip::udp::endpoint endpoint_;
std::function<void(const std::vector<char>&)> callbackFunction_;
char data_[1500 + 1]; // 1500 bytes is safe limit as it is max of ethernet frame, +1 is for \0 terminator
};
UDPServer.cpp:
#include <iostream>
#include "UDPServer.h"
UDPServer::UDPServer(boost::asio::ip::udp::socket socket, std::function<void(const std::vector<char>&)> callbackFunction):
socket_(std::move(socket)),
callbackFunction_(callbackFunction)
{
read();
}
UDPServer::~UDPServer()
{
}
void UDPServer::read()
{
socket_.async_receive_from(boost::asio::buffer(data_, 1500), endpoint_,
[this](boost::system::error_code ec, std::size_t length)
{
if (ec)
{
return;
}
data_[length] = '\0';
if (strcmp(data_, "\n") == 0)
{
return;
}
std::vector<char> dataVector(data_, data_ + length);
callbackFunction_(dataVector);
read();
}
);
}
Now what I want to convert this to is a class with as constructor only the port and a callback function (let forget about the latter and just print the message for now, adding the callback is normally no problem).
I tried the following, but it doesn't work:
int main(int argc, char* const argv[])
{
UDPServer server(60001);
}
UDPServer.h:
#include <boost/asio.hpp>
#include <functional>
#include <vector>
#include <thread>
#define BUFFERSIZE 1501
class UDPServer
{
public:
explicit UDPServer(uint16_t port);
virtual ~UDPServer();
private:
boost::asio::io_context io_context_;
boost::asio::ip::udp::socket socket_;
boost::asio::ip::udp::endpoint endpoint_;
std::array<char, BUFFERSIZE> recv_buffer_;
std::thread thread_;
void run();
void start_receive();
void handle_reply(const boost::system::error_code& error, std::size_t bytes_transferred);
};
UDPServer.cpp:
#include <iostream>
#include "UDPServer.h"
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <iostream>
UDPServer::UDPServer(uint16_t port):
endpoint_(boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), port)),
io_context_(),
socket_(io_context_, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), port)),
thread_(&UDPServer::run, this)
{
start_receive();
}
UDPServer::~UDPServer()
{
io_context_.stop();
thread_.join();
}
void UDPServer::start_receive()
{
socket_.async_receive_from(boost::asio::buffer(recv_buffer_), endpoint_,
boost::bind(&UDPServer::handle_reply, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void UDPServer::handle_reply(const boost::system::error_code& error, std::size_t bytes_transferred)
{
if (!error)
{
try {
std::string string(recv_buffer_.data(), recv_buffer_.data() + bytes_transferred);
std::cout << "Message received: " << std::to_string(bytes_transferred) << ", " << string << std::endl;
}
catch (std::exception ex) {
std::cout << "handle_reply: Error parsing incoming message:" << ex.what() << std::endl;
}
catch (...)
{
std::cout << "handle_reply: Unknown error while parsing incoming message" << std::endl;
}
}
else
{
std::cout << "handle_reply: error: " << error.message() << std::endl;
}
start_receive();
}
void UDPServer::run()
{
try {
io_context_.run();
} catch( const std::exception& e )
{
std::cout << "Server network exception: " << e.what() << std::endl;
}
catch(...)
{
std::cout << "Unknown exception in server network thread" << std::endl;
}
std::cout << "Server network thread stopped" << std::endl;
};
When running I get "Server network thread stopped". io_context doesn't seem to start and doesn't block. Someone an idea what I do wrong? Thanks a lot!
EDIT tried this after comment, same result (except that message comes after 1 second)
UDPServer::UDPServer(uint16_t port):
endpoint_(boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), port)),
io_context_(),
socket_(io_context_, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), port))
{
start_receive();
std::this_thread::sleep_for (std::chrono::seconds(1));
thread_ = std::thread(&UDPServer::run, this);
}
Your destructor explicitly tells the service to stop:
UDPServer::~UDPServer() {
io_context_.stop();
thread_.join();
}
That's part of your problem. The other part is as pointed out in the comment: you have a race condition where the thread exits before you even post your first async operation.
Solve it by adding a work guard:
boost::asio::io_context io_;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> work_ {io_.get_executor()};
Now the destructor can be:
UDPServer::~UDPServer() {
work_.reset(); // allow service to run out of work
thread_.join();
}
Other notes:
avoid chaining back to start_receive when there was an error
std::to_string was redundant
the order of initialization for members is defined by the order of their declaration, not their initializers in the initializer list. Catch these bug sources with -Wall -Wextra -pedantic
= handle exceptions in your service thread (see Should the exception thrown by boost::asio::io_service::run() be caught?)
I'd suggest std::bind over boost::bind:
std::bind(&UDPServer::handle_reply, this,
std::placeholders::_1,
std::placeholders::_2));
Or just use a lambda:
[this](error_code ec, size_t xfer) { handle_reply(ec, xfer); });
LIVE DEMO
Compiler Explorer
#include <boost/asio.hpp>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <thread>
#include <vector>
using boost::asio::ip::udp;
using boost::system::error_code;
using boost::asio::io_context;
#define BUFFERSIZE 1501
class UDPServer {
public:
explicit UDPServer(uint16_t port);
virtual ~UDPServer();
private:
io_context io_;
boost::asio::executor_work_guard<io_context::executor_type> work_ {io_.get_executor()};
udp::endpoint endpoint_;
udp::socket socket_;
std::array<char, BUFFERSIZE> recv_buffer_;
std::thread thread_;
void run();
void start_receive();
void handle_reply(const error_code& error, size_t transferred);
};
UDPServer::UDPServer(uint16_t port)
: endpoint_(udp::endpoint(udp::v4(), port)),
socket_(io_, endpoint_),
thread_(&UDPServer::run, this) {
start_receive();
}
UDPServer::~UDPServer() {
work_.reset(); // allow service to run out of work
thread_.join();
}
void UDPServer::start_receive() {
socket_.async_receive_from(boost::asio::buffer(recv_buffer_), endpoint_,
#if 0
std::bind(&UDPServer::handle_reply, this,
std::placeholders::_1,
std::placeholders::_2));
#else
[this](error_code ec, size_t xfer) { handle_reply(ec, xfer); });
#endif
}
void UDPServer::handle_reply(const error_code& error, size_t transferred) {
if (!error) {
try {
std::string_view s(recv_buffer_.data(), transferred);
std::cout << "Message received: " << transferred << ", "
<< std::quoted(s) << "\n";
} catch (std::exception const& ex) {
std::cout << "handle_reply: Error parsing incoming message:"
<< ex.what() << "\n";
} catch (...) {
std::cout
<< "handle_reply: Unknown error while parsing incoming message\n";
}
start_receive();
} else {
std::cout << "handle_reply: error: " << error.message() << "\n";
}
}
void UDPServer::run() {
while (true) {
try {
if (io_.run() == 0u) {
break;
}
} catch (const std::exception& e) {
std::cout << "Server network exception: " << e.what() << "\n";
} catch (...) {
std::cout << "Unknown exception in server network thread\n";
}
}
std::cout << "Server network thread stopped\n";
}
int main() {
std::cout << std::unitbuf;
UDPServer server(60001);
}
Testing with random words:
sort -R /etc/dictionaries-common/words | while read w; do sleep 1; netcat -u localhost 60001 -w 0 <<<"$w"; done
Live output:

Crash : terminate called after throwing an instance of 'std::system_error' what(): Resource deadlock avoided

I have a simple client /server application the code of which is mentioned below.
Please run the server in one shell and the client in another shell in linux.
First start the server and then the client.
When the server is done with it's work, it crashes with following exception:
terminate called after throwing an instance of 'std::system_error'
what(): Resource deadlock avoided
This happens from the line m_thread->join() from inside the function Service::HandleClient
I have no clue on what's going on.. Can someone please check the code.. I just want that the server application should also get closed correctly the way the client application got closed.
**Server Code : **
#include <boost/asio.hpp>
#include <thread>
#include <atomic>
#include <memory>
#include <iostream>
using namespace boost;
class Service {
public:
Service(){}
void StartHandligClient(
std::shared_ptr<asio::ip::tcp::socket> sock) {
m_thread.reset(new std::thread (([this, sock]() {
HandleClient(sock);
})) );
}
private:
void HandleClient(std::shared_ptr<asio::ip::tcp::socket> sock) {
while(1)
{
try {
asio::streambuf request;
std::cout << "Waiting to read \n";
asio::read_until(*sock.get(), request, '\n');
std::string s( (std::istreambuf_iterator<char>(&request)), std::istreambuf_iterator<char>() );
std::cout << "Server got : " << s << "\n";
// Emulate request processing.
int i = 0;
while (i != 1000000)
i++;
std::this_thread::sleep_for(
std::chrono::milliseconds(500));
// Sending response.
std::string response = "Response\n";
asio::write(*sock.get(), asio::buffer(response));
}
catch (system::system_error &e) {
boost::system::error_code ec = e.code();
if(ec == asio::error::eof)
{
std::cout << "Breaking loop \n";
break;
}
std::cout << "Error occured! Error code = "
<< e.code() << ". Message: "
<< e.what();
}
}
m_thread->join();
// Clean-up.
delete this;
}
std::unique_ptr<std::thread> m_thread;
};
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_acceptor.listen();
}
void Accept() {
std::cout << "Server Accept() \n" << std::flush;
std::shared_ptr<asio::ip::tcp::socket>
sock(new asio::ip::tcp::socket(m_ios));
std::cout << "BEFORE calling acceptor's accept function \n" << std::flush;
m_acceptor.accept(*sock.get());
std::cout << "AFTER calling acceptor's accept function \n" << std::flush;
(new Service)->StartHandligClient(sock);
}
void close()
{
std::cout << "Inside Acceptor.close() \n" << std::flush;
m_acceptor.close();
}
private:
asio::io_service& m_ios;
asio::ip::tcp::acceptor m_acceptor;
};
class Server {
public:
Server() : m_stop(false) {}
void Start(unsigned short port_num) {
m_thread.reset(new std::thread([this, port_num]() {
Run(port_num);
}));
}
void Stop() {
m_stop.store(true);
m_thread->join();
}
private:
void Run(unsigned short port_num) {
Acceptor acc(m_ios, port_num);
while (!m_stop.load()) {
std::cout << "Server accept\n" << std::flush;
acc.Accept();
}
acc.close();
}
std::unique_ptr<std::thread> m_thread;
std::atomic<bool> m_stop;
asio::io_service m_ios;
};
int main()
{
unsigned short port_num = 3333;
try {
Server srv;
srv.Start(port_num);
std::this_thread::sleep_for(std::chrono::seconds(4));
srv.Stop();
}
catch (system::system_error &e) {
std::cout << "Error occured! Error code = "
<< e.code() << ". Message: "
<< e.what();
}
return 0;
}
**Client Code : **
#include <boost/asio.hpp>
#include <iostream>
using namespace boost;
class SyncTCPClient {
public:
SyncTCPClient(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_sock.open(m_ep.protocol());
}
void connect() {
m_sock.connect(m_ep);
}
void close() {
m_sock.shutdown(
boost::asio::ip::tcp::socket::shutdown_both);
m_sock.close();
}
std::string emulateLongComputationOp(
unsigned int duration_sec) {
std::string request = "EMULATE_LONG_COMP_OP "
+ std::to_string(duration_sec)
+ "\n";
sendRequest(request);
return receiveResponse();
};
private:
void sendRequest(const std::string& request)
{
std::cout << "Inside sendRequest : " << request << "\n";
asio::write(m_sock, asio::buffer(request));
}
std::string receiveResponse() {
asio::streambuf buf;
asio::read_until(m_sock, buf, '\n');
std::istream input(&buf);
std::string response;
std::getline(input, response);
return response;
}
private:
asio::io_service m_ios;
asio::ip::tcp::endpoint m_ep;
asio::ip::tcp::socket m_sock;
};
int main()
{
const std::string raw_ip_address = "127.0.0.1";
const unsigned short port_num = 3333;
try {
SyncTCPClient client(raw_ip_address, port_num);
// Sync connect.
client.connect();
std::cout << "Sending request to the server... " << std::endl;
std::string response = client.emulateLongComputationOp(10);
std::cout << "Response received: " << response << std::endl;
sleep(2);
// Close the connection and free resources.
client.close();
}
catch (system::system_error &e) {
std::cout << "Error occured! Error code = " << e.code()
<< ". Message: " << e.what();
return e.code().value();
}
return 0;
}
#sehe .. can you run the code and let me know how to overcome the crash that I mentioned ? – Nishant Sharma
Actually, no I won't. The problem has already been analyzed: you can't join the current thread (it would deadlock).
But I can do something better:
Grabbing my crystal ball, I can guess you got this example from a particular book, named Boost.Asio C++ Network Programming Cookbook¹, around page 139.
I recognized it after a while when I added up all the code smells (delete this and m_stop.load() tipped me over the edge).
The good news is, I reviewed that code before:
ASIO example code closing socket before it should
You can probably profit from the particular comments I made there.
¹ from packtpub: https://www.packtpub.com/application-development/boostasio-c-network-programming-cookbook

boost::asio + std::future - Access violation after closing socket

I am writing a simple tcp client to send and receive single lines of text. The asynchronous operations are handled by std::future in order to faciliate blocking queries with timeouts. Unfortunately, my test application crashes with an access violation when destructing the server object.
Here is my code:
TCPClient.hpp
#ifndef __TCPCLIENT_H__
#define __TCPCLIENT_H__
#include <boost/asio.hpp>
#include <boost/asio/use_future.hpp>
#include <memory>
#include <vector>
#include <future>
#include <thread>
#include <chrono>
#include <iostream>
#include <iterator>
using namespace boost::asio;
class TCPClient {
public:
TCPClient();
~TCPClient();
void connect(const std::string& address, const std::string& port);
void disconnect();
std::string sendMessage(const std::string& msg);
private:
boost::asio::io_service ioservice;
boost::asio::io_service::work work;
std::thread t;
std::unique_ptr<boost::asio::ip::tcp::socket> socket;
};
inline TCPClient::TCPClient() : ioservice(), work(ioservice) {
t = std::thread([&]() {
try {
ioservice.run();
}
catch (const boost::system::system_error& e) {
std::cerr << e.what() << std::endl;
}
});
}
inline TCPClient::~TCPClient() {
disconnect();
ioservice.stop();
if (t.joinable()) t.join();
}
inline void TCPClient::connect(const std::string& address, const std::string& port) {
socket.reset(new ip::tcp::socket(ioservice));
ip::tcp::resolver::query query(address, port);
std::future<ip::tcp::resolver::iterator> conn_result = async_connect(*socket, ip::tcp::resolver(ioservice).resolve(query), use_future);
if (conn_result.wait_for(std::chrono::seconds(6)) != std::future_status::timeout) {
conn_result.get(); // throws boost::system::system_error if the operation fails
}
else {
//socket->close();
// throw timeout_error("Timeout");
throw std::exception("timeout");
}
}
inline void TCPClient::disconnect() {
if (socket) {
try {
socket->shutdown(ip::tcp::socket::shutdown_both);
std::cout << "socket points to " << std::addressof(*socket) << std::endl;
socket->close();
}
catch (const boost::system::system_error& e) {
// ignore
std::cerr << "ignored error " << e.what() << std::endl;
}
}
}
inline std::string TCPClient::sendMessage(const std::string& msg) {
auto time_over = std::chrono::system_clock::now() + std::chrono::seconds(4);
/*
// Doesn't affect the error
std::future<size_t> write_fut = boost::asio::async_write(*socket, boost::asio::buffer(msg), boost::asio::use_future);
try {
write_fut.get();
}
catch (const boost::system::system_error& e) {
std::cerr << e.what() << std::endl;
}
*/
boost::asio::streambuf response;
std::future<std::size_t> read_fut = boost::asio::async_read_until(*socket, response, '\n', boost::asio::use_future);
if (read_fut.wait_until(time_over) != std::future_status::timeout) {
std::cout << "read " << read_fut.get() << " bytes" << std::endl;
return std::string(std::istreambuf_iterator<char>(&response), std::istreambuf_iterator<char>());
}
else {
std::cout << "socket points to " << std::addressof(*socket) << std::endl;
throw std::exception("timeout");
}
}
#endif
main.cpp
#include <iostream>
#include "TCPClient.hpp"
int main(int argc, char* argv[]) {
TCPClient client;
try {
client.connect("localhost", "27015");
std::cout << "Response: " << client.sendMessage("Hello!") << std::endl;
}
catch (const boost::system::system_error& e) {
std::cerr << e.what() << std::endl;
}
catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
}
system("pause");
return 0;
}
The output is "timeout" as expected (test server sends no data on purpose), but ioservice.run() crashes immediately (access violation) after closing the socket in TCPClient::disconnect(). Am I doing some memory mismanagment here?
Compiler is MSVC 12.0.31101.00 Update 4 (Visual Studio 2013)
recvmsg is receiving into a buffer (streambuf) that was freed after throwing the exception in TCPClient::sendMessage (line 105, end of scope).
You forgot to cancel the asynchronous operation (async_read_until) started in line 97. Fix it:
else {
socket->cancel(); // ADDED
std::cout << "socket points to " << std::addressof(*socket) << std::endl;
throw std::runtime_error("timeout");
}
Or even, just
socket.reset(); // ADDED
Same goes for other timeout paths.
The other answer addresses what went wrong.
On a higher level, though, you're using futures, just to immediately await their return.
It struck me that this is actually not asynchrony at all, and you should be able to do:
without threading, and joining
without .stop()
without work and work.reset()
without a explicit constructor or destructor
without the unique_ptr<socket> and the lifetime management that came with it
without the future<>, and the .get() and future_status checking that come with it
All in all, you can do a lot simpler, e.g. using a simple helper function like this:
class TCPClient {
public:
void disconnect();
void connect(const std::string& address, const std::string& port);
std::string sendMessage(const std::string& msg);
private:
using error_code = boost::system::error_code;
template<typename AllowTime> void await_operation(AllowTime const& deadline_or_duration) {
using namespace boost::asio;
ioservice.reset();
{
high_resolution_timer tm(ioservice, deadline_or_duration);
tm.async_wait([this](error_code ec) { if (ec != error::operation_aborted) socket.cancel(); });
ioservice.run_one();
}
ioservice.run();
}
boost::asio::io_service ioservice { };
boost::asio::ip::tcp::socket socket { ioservice };
};
E.g. connect(...) used to be:
socket.reset(new ip::tcp::socket(ioservice));
ip::tcp::resolver::query query(address, port);
std::future<ip::tcp::resolver::iterator> conn_result = async_connect(*socket, ip::tcp::resolver(ioservice).resolve(query), use_future);
if (conn_result.wait_for(std::chrono::seconds(6)) != std::future_status::timeout) {
conn_result.get(); // throws boost::system::system_error if the operation fails
}
else {
socket->cancel();
// throw timeout_error("Timeout");
throw std::runtime_error("timeout");
}
It now becomes:
async_connect(socket,
ip::tcp::resolver(ioservice).resolve({address, port}),
[&](error_code ec, ip::tcp::resolver::iterator it) { if (ec) throw std::runtime_error(ec.message()); });
await_operation(std::chrono::seconds(6));
Like wise, sendMessage becomes:
streambuf response;
async_read_until(socket, response, '\n', [&](error_code ec, size_t bytes_read) {
if (ec) throw std::runtime_error(ec.message());
std::cout << "read " << bytes_read << " bytes" << std::endl;
});
await_operation(std::chrono::system_clock::now() + std::chrono::seconds(4));
return {std::istreambuf_iterator<char>(&response), {}};
Note these are significantly simpler. Note, also, that correct exception messages are now thrown, depending on the cause of the failures.
Full Demo
Live On Coliru
#ifndef __TCPCLIENT_H__
#define __TCPCLIENT_H__
#include <boost/asio.hpp>
#include <boost/asio/high_resolution_timer.hpp>
#include <iostream>
class TCPClient {
public:
void disconnect();
void connect(const std::string& address, const std::string& port);
std::string sendMessage(const std::string& msg);
private:
using error_code = boost::system::error_code;
template<typename AllowTime> void await_operation(AllowTime const& deadline_or_duration) {
using namespace boost::asio;
ioservice.reset();
{
high_resolution_timer tm(ioservice, deadline_or_duration);
tm.async_wait([this](error_code ec) { if (ec != error::operation_aborted) socket.cancel(); });
ioservice.run_one();
}
ioservice.run();
}
boost::asio::io_service ioservice { };
boost::asio::ip::tcp::socket socket { ioservice };
};
inline void TCPClient::connect(const std::string& address, const std::string& port) {
using namespace boost::asio;
async_connect(socket,
ip::tcp::resolver(ioservice).resolve({address, port}),
[&](error_code ec, ip::tcp::resolver::iterator it) { if (ec) throw std::runtime_error(ec.message()); });
await_operation(std::chrono::seconds(6));
}
inline void TCPClient::disconnect() {
using namespace boost::asio;
if (socket.is_open()) {
try {
socket.shutdown(ip::tcp::socket::shutdown_both);
socket.close();
}
catch (const boost::system::system_error& e) {
// ignore
std::cerr << "ignored error " << e.what() << std::endl;
}
}
}
inline std::string TCPClient::sendMessage(const std::string& msg) {
using namespace boost::asio;
streambuf response;
async_read_until(socket, response, '\n', [&](error_code ec, size_t bytes_read) {
if (ec) throw std::runtime_error(ec.message());
std::cout << "read " << bytes_read << " bytes" << std::endl;
});
await_operation(std::chrono::system_clock::now() + std::chrono::seconds(4));
return {std::istreambuf_iterator<char>(&response), {}};
}
#endif
#include <iostream>
//#include "TCPClient.hpp"
int main(/*int argc, char* argv[]*/) {
TCPClient client;
try {
client.connect("127.0.0.1", "27015");
std::cout << "Response: " << client.sendMessage("Hello!") << std::endl;
}
catch (const boost::system::system_error& e) {
std::cerr << e.what() << std::endl;
}
catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
}
}
BONUS
If you want even more convenience, have a generalized callback handler that just raises the exception:
struct raise {
template <typename... A> void operator()(error_code ec, A...) const {
if (ec) throw std::runtime_error(ec.message());
}
};
Now, the bodies become even simpler in absense of lambdas:
inline void TCPClient::connect(const std::string& address, const std::string& port) {
async_connect(socket, ip::tcp::resolver(ioservice).resolve({address, port}), raise());
await_operation(std::chrono::seconds(6));
}
inline std::string TCPClient::sendMessage(const std::string& msg) {
streambuf response;
async_read_until(socket, response, '\n', raise());
await_operation(std::chrono::system_clock::now() + std::chrono::seconds(4));
return {std::istreambuf_iterator<char>(&response), {}};
}
See the adapted demo: Live On Coliru too