I'm trying to create a server that receives connections via domain sockets. I can start the server and I can see the socket being created on the filesystem. But whenever I try to connect to it via socat I get the following error:
2015/03/02 14:00:10 socat[62720] E connect(3, LEN=19 AF=1 "/var/tmp/rpc.sock", 19): Connection refused
This is my Asio code (only the .cpp files). Despite the post title I'm using the Boost-free version of Asio but I don't think that would be a problem.
namespace myapp {
DomainListener::DomainListener(const string& addr) : socket{this->service}, Listener{addr} {
remove(this->address.c_str());
stream_protocol::endpoint ep(this->address);
stream_protocol::acceptor acceptor(this->service, ep);
acceptor.async_accept(this->socket, ep, bind(&DomainListener::accept_callback, this, _1));
}
DomainListener::~DomainListener() {
this->service.stop();
remove(this->address.c_str());
}
void DomainListener::accept_callback(const error_code& ec) noexcept {
this->socket.async_read_some(asio::buffer(this->data), bind(&DomainListener::read_data, this, _1, _2));
}
void DomainListener::read_data(const error_code& ec, size_t length) noexcept {
//std::cerr << "AAA" << std::endl;
//std::cerr << this->data[0] << std::endl;
//std::cerr << "BBB" << std::endl;
}
}
Listener::Listener(const string& addr) : work{asio::io_service::work(this->service)} {
this->address = addr;
}
void Listener::listen() {
this->service.run();
}
Listener::~Listener() {
}
In the code that uses these classes I call listen()
whenever I want to start listening to the socket for connections.
I've managed to get this to work with libuv and changed to Asio because I thought it would make for more readable code but I'm finding the documentation to be very ambiguous.
The issue is most likely the lifetime of the acceptor
.
The acceptor
is an automatic variable in the DomainListener
constructor. When the DomainListener
constructor completes, the acceptor
is destroyed, causing the acceptor to close and cancel outstanding operations, such as the async_accept
operations. Cancelled operations will be provided an error code of asio::error::operation_aborted
and scheduled for deferred invocation within the io_service
. Hence, there may not be an active listener when attempting to connect to the domain socket. For more details on the affects of IO object destruction, see this answer.
DomainListener::DomainListener(const string&) : /* ... */
{
// ...
stream_protocol::acceptor acceptor(...);
acceptor.async_accept(..., bind(accept_callback, ...));
} // acceptor destroyed, and accept_callback likely cancelled
To resolve this, consider extending the lifetime of the acceptor
by making it a data member for DomainListener
. Additionally, checking the error_code
provided to asynchronous operations can provide more insight into the asynchronous call chains.
Here is a complete minimal example demonstrating using domain sockets with Asio.
#include <cstdio>
#include <iostream>
#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
/// @brief server demonstrates using domain sockets to accept
/// and read from a connection.
class server
{
public:
server(
boost::asio::io_service& io_service,
const std::string& file)
: io_service_(io_service),
acceptor_(io_service_,
boost::asio::local::stream_protocol::endpoint(file)),
client_(io_service_)
{
std::cout << "start accepting connection" << std::endl;
acceptor_.async_accept(client_,
boost::bind(&server::handle_accept, this,
boost::asio::placeholders::error));
}
private:
void handle_accept(const boost::system::error_code& error)
{
std::cout << "handle_accept: " << error.message() << std::endl;
if (error) return;
std::cout << "start reading" << std::endl;
client_.async_read_some(boost::asio::buffer(buffer_),
boost::bind(&server::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_read(
const boost::system::error_code& error,
std::size_t bytes_transferred)
{
std::cout << "handle_read: " << error.message() << std::endl;
if (error) return;
std::cout << "read: ";
std::cout.write(buffer_.begin(), bytes_transferred);
std::cout.flush();
}
private:
boost::asio::io_service& io_service_;
boost::asio::local::stream_protocol::acceptor acceptor_;
boost::asio::local::stream_protocol::socket client_;
std::array<char, 1024> buffer_;
};
int main(int argc, char* argv[])
{
if (argc != 2)
{
std::cerr << "Usage: <file>\n";
return 1;
}
// Remove file on startup and exit.
std::string file(argv[1]);
struct file_remover
{
file_remover(std::string file): file_(file) { std::remove(file.c_str()); }
~file_remover() { std::remove(file_.c_str()); }
std::string file_;
} remover(file);
// Create and run the server.
boost::asio::io_service io_service;
server s(io_service, file);
io_service.run();
}
Coliru does not have socat installed, so the following commands use OpenBSD netcat to write "asio domain socket example" to the domain socket:
export SOCKFILE=$PWD/example.sock
./a.out $SOCKFILE &
sleep 1
echo "asio domain socket example" | nc -U $SOCKFILE
Which outputs:
start accepting connection
handle_accept: Success
start reading
handle_read: Success
read: asio domain socket example