Using sockets

The x::fd reference-counted object provides standard wrappers for the usual socket functions, socket(2), connect(2), listen(2), bind(2), and accept(2). Additionally, the x::netaddr reference provides a convenient high level interface for creating sockets:

#include <x/fd.H>
#include <x/netaddr.H>

x::fd sock=x::netaddr::create("www.example.com", "http", SOCK_STREAM)
        ->domain(AF_INET)->connect();

x::netaddr holds an unresolved network address (the hostname, port, socket type, and a few other attributes). Its connect() method uses getaddrinfo(3) to resolve the network address, then tries connecting to each resolved address. If the server's name resolves to multiple addresses, a connection attempt gets made to each address, until a connection gets established. An exception gets thrown if connection to all resolved addresses fails.

An optional parameter to connect() implements a general mechanism for timing out a connection attempt:

#include <x/fdtimeoutconfig.H>

class my_timeout_config : public x::fdtimeoutconfig {

public:
    x::fd termfd;

    x::fdbase operator()(const fd &fdArg) const
    {
        x::fdtimeout fd_with_timeout(x::fdtimeout::create(fd));

        fd_with_timeout->set_terminate_fd(termfd);

        return fd_with_timeout;
    }
};

// ...

my_timeout_config timeout;

// ...

fd sock=x::netaddr::create("www.example.com", "http", SOCK_STREAM)
        ->domain(AF_INET)
        ->connect(timeout);

The optional parameter to connect() is a functor subclass of x::fdtimeoutconfig. The functor receives the newly created socket object, and returns a reference to the x::fdbase which will be used for the actual connection attempt. The above example sets a file descriptor terminator. If the terminator descriptor becomes readable, the connection attempt gets aborted.

fd sock=x::netaddr::create("www.example.com", "http", SOCK_STREAM)
        ->domain(AF_INET)
        ->connect(x::fdtimeoutconfig::terminate_fd(termfd));

Use x::fdtimeoutconfig::terminate_fd as a convenient shortcut to implement the same functionality. The constructor takes a terminator file descriptor, and defines a functor that installs the terminator file descriptor into each received file descriptor.

LIBCXX_NAMESPACE::fdtimeoutptr timeout;

fd sock=x::netaddr::create("www.example.com", "http", SOCK_STREAM)
        ->domain(AF_INET)
        ->connect(x::make_fdtimeoutconfig(
            [&timeout]
            (const x::fdbase &fd)
            {
                auto new_timeout=x::fdtimeout::create(fd);

                new_timeout->set_write_timeout(60);
                timeout=new_timeout;
                return new_timeout;
            }));

timeout->set_read_timeout(60);

x::make_fdtimeoutconfig() takes a lambda as a parameter, and constructs a subclass of x::fdtimeoutconfig::terminate_fd that calls the lambda. Note that connect() uses the returned timeout handler for the duration of the socket connection only, and returns the actual socket file descriptor, so this lambda saves the constructed timeout handled, for use after connect() returns.

std::list<x::fd> fdList;

    x::netaddr::create("", "http", SOCK_STREAM)
        ->bind(fdList, true);

x::netaddr's bind() method creates a list of socket objects and binds them to the specified address. It's possible that the specified address may resolve to multiple addresses, for example an IPv4 and an IPv6 socket. This is why bind() takes a list of sockets as a parameter. bind() creates all sockets, binds each socket, and adds them to the list.

The second parameter may also be a string with a list of port names or numbers, separated by commas or spaces; or an explicit std::list<int> &. When used with bind(), this creates listening sockets on all specified ports. When used with connect(), this connects to the first port in the list that's listening for connections.

An alternative format provides for a unified way to create either a network or filesystem domain socket:

x::fd fd=x::netaddr::create(SOCK_STREAM, "inet:www.example.com", "http");

// ...

x::fd fd=x::netaddr::create(SOCK_STREAM, "file:/tmp/sock");

        ->bind(fdList, true);

Specify inet:host creates a network socket, and file:path connects to a filesystem socket. For network sockets, /port may also be appended to the host parameter, instead of given as a separate argument.

std::string d=x::fd::base::mktempdir(0700);
std::pair<x::fd, std::string> tmpsock=x::fd::tmpunixfilesock(d + "/sysmon.");

As explained at the beginning of this chapter, x::fd::mktempdir() creates a temporary subdirectory, with the right permissions, in /tmp. x::fd::tmpunixfilesock() creates a randomly named filename and attempts to bind a filesystem domain socket, returning the socket and the bound name. To prevent pollution, every attempt to manually unlink the socket should be made, before the process terminates.