I'm working on a client/server TCP applications. It works very well under Windows but I'm encountering problems under Linux: the server does not get notified when a new connection is established with the client (very rarely it is, but most often it is not).
I was able to isolate the issue in a very simple program. This program creates and runs a server (with its own boost::asio::io_service
) accepting new connections and then establishs a connection from a client (also with its own boost::asio::io_service
, as it would behave in seperate applications).
Here is the code:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <assert.h>
using boost::asio::ip::tcp;
std::shared_ptr<tcp::socket> m_nextConnection;
std::atomic_bool m_continueServerThread = true;
void server_thread_func(boost::asio::io_service* service)
{
// http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/io_service.html#boost_asio.reference.io_service.effect_of_exceptions_thrown_from_handlers
while (m_continueServerThread) {
try {
service->run();
// don't break, keep looping, else we may exit before a connection is actually established
//break; // exited normally
}
catch (std::exception const& e) {
std::cerr << "[eventloop] error: " << e.what();
}
catch (...) {
std::cerr << "[eventloop] unexpected error";
}
}
}
void on_accept_connection(std::error_code ec)
{
if (!ec)
{
std::cout << "SERVER ACCEPTED CONNECTION" << std::endl;
}
else
{
std::cout << "Reader connection error " << ec << " (" << ec.message() << ")" << std::endl;
}
}
void do_accept_connection(boost::asio::ip::tcp::acceptor& acceptor, boost::asio::io_service& service)
{
m_nextConnection = std::shared_ptr<tcp::socket>(new boost::asio::ip::tcp::socket(service));
acceptor.async_accept(
*m_nextConnection, boost::bind(on_accept_connection, boost::asio::placeholders::error));
}
int main( int argc, char* argv[] )
{
auto endpoint = boost::asio::ip::tcp::endpoint{ boost::asio::ip::address::from_string("127.0.0.1"), 1900 };
try
{
// start server:
boost::asio::io_service IOServiceServer;
boost::thread serverThread( boost::bind(server_thread_func,&IOServiceServer) );
boost::this_thread::sleep(boost::posix_time::milliseconds(500));
boost::asio::ip::tcp::acceptor m_acceptor{ IOServiceServer, endpoint };
// dunno if this is needed or not here
//m_acceptor.set_option(tcp::acceptor::reuse_address(true));
do_accept_connection(m_acceptor, IOServiceServer);
boost::this_thread::sleep(boost::posix_time::milliseconds(500));
// start client:
boost::asio::io_service IOServiceClient;
tcp::socket socket(IOServiceClient);
std::cout << "Connecting socket..." << std::endl;
socket.connect(endpoint);
std::cout << "Connected socket" << std::endl;
boost::this_thread::sleep(boost::posix_time::milliseconds(500));
// stop/close client:
IOServiceClient.stop();
socket.close();
// stop server:
IOServiceServer.stop();
m_continueServerThread = false;
serverThread.join();
return 0;
}
catch (std::exception const& e) {
std::cerr << "error: " << e.what();
}
catch (...) {
std::cerr << "unexpected error";
}
return 1;
}
Ran under Windows, this shows:
Connecting socket...
Connected socket
SERVER ACCEPTED CONNECTION
Ran under Linux, this only shows:
Connecting socket...
Connected socket
As you can see on_accept_connection
is not being called, so the server does not get notified that a new connection was established.
Am I doing something wrong?
Edit: Also tried without m_nextConnection:
void on_accept_connection(std::error_code ec, tcp::socket socket)
{
if (!ec)
{
std::cout << "SERVER ACCEPTED CONNECTION" << std::endl;
}
else
{
std::cout << "Reader connection error " << ec << " (" << ec.message() << ")" << std::endl;
}
}
void do_accept_connection(boost::asio::ip::tcp::acceptor& acceptor, boost::asio::io_service& service)
{
acceptor.async_accept(service, [&](const std::error_code& ec, tcp::socket newSocket) {
on_accept_connection(ec, std::move(newSocket));
});
}
Does not solve the problem!
The server thread was modified from here. The comment is telling:
// don't break, keep looping, else we may exit before a connection is actually established
This is not true. The reason you see it exit before the connection is established is because the IO service runs out of work. Since you let the service run out of work, looping makes zero sense, unless you actually use restart()
:
Subsequent calls to
run()
,run_one()
,poll()
orpoll_one()
will return immediately unless there is a prior call torestart()
So, the real fix is to keep the loop as it was intended but place work before starting it, e.g. changing
std::thread serverThread(server_thread_func, std::ref(ioc));
do_accept_connection(m_acceptor, ioc);
To
do_accept_connection(m_acceptor, ioc);
std::thread serverThread(server_thread_func, std::ref(ioc));
The global connection variable is a problem, especially once you accept more than 1 connection. Fix it by not using a global, e.g.:
void on_accept_connection(std::error_code ec, std::shared_ptr<tcp::socket> s) {
if (!ec)
std::cout << "SERVER ACCEPTED CONNECTION from " << s->remote_endpoint() << std::endl;
else
std::cout << "Reader connection error " << ec << " (" << ec.message() << ")" << std::endl;
}
void do_accept_connection(tcp::acceptor& acceptor, asio::io_context& service) {
auto s = std::make_shared<tcp::socket>(service);
acceptor.async_accept(*s, bind(on_accept_connection, _1, s));
}
Note how we bind the shared pointer into the completion handler, to keep it alive.
This already works: https://coliru.stacked-crooked.com/a/df135c6936c5e37e
Connecting socket...
Connected socket
SERVER ACCEPTED CONNECTION from 127.0.0.1:45958
[eventloop] exit
Closing socket
However, you should take the overload that moves the socket, use builtin thread pool, not use a second io service, use io_context
instead of the deprecated io_service
and other stuff like not use unncessary raw pointers, boost::bind
(or bind at all) and boost::thread
, passing around execution contexts instead of executors etc.: Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
namespace asio = boost::asio;
using namespace std::chrono_literals;
using boost::asio::ip::tcp;
void on_accept_connection(std::error_code ec, tcp::socket s) {
if (!ec)
std::cout << "Accepted from " << s.remote_endpoint() << std::endl;
else
std::cout << std::endl;
}
void accept_loop(tcp::acceptor& acc) {
acc.async_accept(make_strand(acc.get_executor()), on_accept_connection);
}
int main() {
tcp::endpoint ep{{}, 1900};
using std::this_thread::sleep_for;
asio::thread_pool ioc(1);
tcp::acceptor listener{ioc, ep};
accept_loop(listener);
sleep_for(500ms);
{
// start client:
tcp::socket socket(ioc);
std::cout << "Connecting socket..." << std::endl;
socket.connect(ep);
std::cout << "Connected socket" << std::endl;
sleep_for(500ms);
std::cout << "Closing socket" << std::endl;
}
ioc.join();
}
Printing
Connecting socket...
Connected socket
Accepted from 127.0.0.1:47014
Closing socket
To make it complete, actually make accept_loop
loop:
#include <boost/asio.hpp>
#include <iostream>
namespace asio = boost::asio;
using namespace std::chrono_literals;
using boost::system::error_code;
using boost::asio::ip::tcp;
struct session : std::enable_shared_from_this<session> {
session(tcp::socket s) : s_(std::move(s)) {}
void run() {
std::cout << "Session from " << s_.remote_endpoint() << std::endl;
async_write(s_,
asio::buffer(message_), //
consign(asio::detached, shared_from_this()));
}
private:
tcp::socket s_;
std::string message_ = "Hello from server\n";
};
struct listener {
listener(tcp::endpoint ep) : acc_(ioc_, ep) { accept_loop(); }
private:
asio::thread_pool ioc_{1};
tcp::acceptor acc_;
void accept_loop() {
acc_.async_accept(make_strand(acc_.get_executor()), [this](error_code ec, tcp::socket s) {
if (!ec) {
std::make_shared<session>(std::move(s))->run();
accept_loop();
} else
std::cout << std::endl;
});
}
};
int main() {
tcp::endpoint ep{{}, 1900};
listener server(ep);
std::this_thread::sleep_for(50ms);
for (auto i = 0; i < 10; ++i) {
tcp::iostream is(ep);
std::cout << "Connected: " << is.rdbuf() << std::endl;
}
}
Printing e.g.
Connected: Session from 127.0.0.1:48586
Hello from server
Connected: Session from 127.0.0.1:48588
Hello from server
Connected: Session from 127.0.0.1:48590
Hello from server
Connected: Session from 127.0.0.1:48592
Hello from server
Connected: Session from 127.0.0.1:48594
Hello from server
Connected: Session from 127.0.0.1:48596
Hello from server
Connected: Session from 127.0.0.1:48598
Hello from server
Connected: Session from 127.0.0.1:48600
Hello from server
Connected: Session from 127.0.0.1:48602
Hello from server
Connected: Session from 127.0.0.1:48604
Hello from server