I'm working on an application that uses boost::asio to listen on different ports for TCP and UDP packets. I did this by using similar server classes as in the asio tutorial examples and I point them all to a single io_service.
So overall my main function (win32 console application) looks like this:
int main(int argc, char* argv[])
{
//some initializations here
try
{
asio::io_service io_service;
TcpServer ServerTCP_1(io_service, /*port number here*/);
TcpServer ServerTCP_2(io_service, /*port number here*/);
UdpServer ServerUDP(io_service, /*port number here*/);
io_service.run();
}
catch(std::exception& e)
{
std::cerr << e.what() << std::endl;
}
//cleanup code here
return 0;
}
I also read through the HTTP server example where a signal_set
is used for performing an asynchronous operation that gets activated when the quit signal comes in (which I also implemented in the server class).
Here is my TcpServer
class:
class TcpServer : private noncopyable
{
public:
TcpServer(asio::io_service& io_service, int port) :
acceptor_(io_service, tcp::endpoint(tcp::v4(), port)),
signals_(io_service), context_(asio::ssl::context::sslv3)
{
SSL_CTX_set_cipher_list(context_.native_handle(), "ALL");
SSL_CTX_set_options(context_.native_handle(), SSL_OP_ALL);
SSL_CTX_use_certificate_ASN1(context_.native_handle(), sizeof(SSL_CERT_X509), SSL_CERT_X509);
SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_RSA, context_.native_handle(), SSL_CERT_RSA, sizeof(SSL_CERT_RSA));
SSL_CTX_set_verify_depth(context_.native_handle(), 1);
signals_.add(SIGINT);
signals_.add(SIGTERM);
signals_.add(SIGBREAK);
#if defined(SIGQUIT)
signals_.add(SIGQUIT);
#endif // defined(SIGQUIT)
signals_.async_wait(boost::bind(&TcpServer::handle_stop, this));
start_accept();
}
private:
tcp::acceptor acceptor_;
asio::ssl::context context_;
asio::signal_set signals_;
TcpConnection::pointer new_ssl_connection;
void start_accept(){
new_ssl_connection.reset(new TcpConnection(acceptor_.get_io_service(), context_));
acceptor_.async_accept( new_ssl_connection->socket(),
boost::bind(&TcpServer::handle_acceptSSL, this, asio::placeholders::error));
}
void handle_acceptSSL(const system::error_code& error){
if(!error)
new_ssl_connection->start();
start_accept();
}
void handle_stop(){
acceptor_.close();
printf("acceptor closed");
}
};
Now since I have 3 different server objects he should close all acceptors of them leaving the io_service
without work which again causes the io_service
to return from the run()
call which should make the program get to the cleanup code in the main function, right?
And besides all that the printf
call should be executed 3 times as well or?
Well first of all the io_service
doesn't return, the cleanup code is never reached.
Second of all there is only one printf call displayed in the console at most.
And third of all, by doing some debugging I found out that when I quit the program the handle_stop()
is called once but for some reason the program closes somewhere in the handle (which means sometimes he gets to printf and closes after that and sometimes he just gets to acceptor_.close()
)
Another thing maybe worth mentioning is that the program doesn't return 0 after close but instead this (the number of threads vary):
The thread 'Win32 Thread' (0x1758) has exited with code 2 (0x2).
The thread 'Win32 Thread' (0x17e8) has exited with code -1073741510 (0xc000013a).
The thread 'Win32 Thread' (0x1034) has exited with code -1073741510 (0xc000013a).
The program '[5924] Server.exe: Native' has exited with code -1073741510 (0xc000013a).
So I would like to know why this is happening, how I can fix it and how can I get to the cleanup code properly?
The io_service
is never returning because there is always work in the io_service
. When acceptor::close()
is invoked, the acceptor
's asynchronous accept operations will be cancelled immediately. The handlers for these cancelled operations will be passed the boost::asio::error::operation_aborted
error.
In the current code, the problem is the result of a new asynchronous accept operation always being initiated, even if the acceptor
has been closed. Thus, work is always being added to the io_service
.
void start_accept(){
// If the acceptor is closed, handle_accept will be ready to run with an
// error.
acceptor_.async_accept(..., handle_accept);
}
void handle_accept(const system::error_code& error){
if(!error)
{
connection->start();
}
// Always starts a new async accept operation, even if the acceptor
// has closed.
start_accept();
}
void handle_stop(){
acceptor_.close();
}
The Boost.Asio examples prevents this from occurring checking if acceptor_
has been closed in the handle_accept
call. If the acceptor_
is no longer open, then the handler returns early without adding more work to the io_service
.
Here is the relevant snippet from from the HTTP Server 1 example:
void server::handle_accept(const boost::system::error_code& e)
{
// Check whether the server was stopped by a signal before this completion
// handler had a chance to run.
if (!acceptor_.is_open())
{
return;
}
if (!e)
{
connection_manager_.start(new_connection_);
}
start_accept();
}
User contributions licensed under CC BY-SA 3.0