Qt socket and systemd

How to create a systemd-friendly QWebSocket server?

Surprisingly, there does not seem to be any official Qt documentation for this use-case. Enjoy.

Introduction to systemd socket-based services

The keyword here is socket-based activation. [1]

systemd will create the socket first and listen to incoming connections. On first incoming connection, the corresponding service is started and the socket is handed over to the process for processing.

Example quote for a daemon implementation from [2]:

/* Source Code Example #2: UPDATED, SOCKET-ACTIVABLE SERVICE */
...
#include "sd-daemon.h"
...
int fd;

if (sd_listen_fds(0) != 1) {
        fprintf(stderr, "No or too many file descriptors received.\n");
        exit(1);
}

fd = SD_LISTEN_FDS_START + 0;

sd-daemon.h is a header file from systemd. The actually include is <systemd/sd-daemon.h> [3].

By the way, this requires to add the libsystemd-dev package on Debian/Ubuntu and the pkg-config libsystemd to the qmake project file:

# Link to libsystemd shared library using pkg-config:
CONFIG += link_pkgconfig
PKGCONFIG += libsystemd

From Qt point of view

In Qt world, the daemon will be a QApplication with QWebSocket. A QWebSocket actually contains itself a QTcpServer. When the QTcpServer is created, the member function setSocketDescriptor(...) can be used to set the socket from systemd, and process the first connection. According to the documentation [4], the socket is assumed to be in listening state, which will be the case since systemd was already listining to it.

The problem is to access QTcpServer from QWebSocket).

This is a hack

From the project cutelyst [5]:

bool LocalServer::setSocketDescriptor(qintptr socketDescriptor)
{
    bool ret = false;
    if (listen(socketDescriptor)) {
        // THIS IS A HACK
        // QLocalServer does not expose the socket
        // descriptor, so we get it from it's QSocketNotifier child
        // if this breaks it we fail with an error.
        const auto childrenL = children();
        for (auto child : childrenL) {
            auto notifier = qobject_cast<QSocketNotifier*>(child);
            if (notifier) {
                m_notifier = notifier;
                ret = true;
                break;
            }
        }
    }
    return ret;
}

This hack can be easily adapted to QWebSocket as well.

bool MyServer::setSocketDescriptor(qintptr socketDescriptor)
{
    // THIS IS A HACK
    // QWebSocket does not expose the socket
    // descriptor, so we get it from it's QTcpServer child
    // if this breaks it we fail with an error.
    const auto childrenL = m_webSocketServer.children();
    for (auto child : childrenL) {
        auto tcpServer = qobject_cast<QTcpServer*>(child);
        if(tcpServer) {
            return tcpServer->setSocketDescriptor(socketDescriptor);
        }
    }
    return false;
}
#include <systemd/sd-daemon.h>

if(isSocketActivationEnabled)
{
    int fd;
    if(sd_listen_fds(0) != 1) {
        fprintf(stderr, "No or too many file descriptors received.\n");
        exit(1);
    }

    fd = SD_LISTEN_FDS_START + 0;
    if(setSocketDescriptor(fd)) {
        fprintf(stderr, "Unable to set systemd descriptor.\n");
        exit(1);
    }
}

Finally, systemd service files

Socket options are described in [6].

/etc/systemd/system/foobar.socket

[Socket]
#ListenStream=/run/foobar.sk    # for file-system socket (AF_UNIX)
ListenStream=4000               # for TCP socket (AF_INET)
#ListenDatagram=4000            # for UDP socket (AF_INET)


[Install]
WantedBy=multi-user.target      # Always start on start-up
#WantedBy=sockets.target        # Only start on demand

/etc/systemd/system/foobar.service

[Service]
ExecStart=/usr/bin/foobard

Enable the service:

# systemctl enable foobar.service
# systemctl start foobar.service

References

[1]FreeDesktop, systemd official man page "daemon", https://www.freedesktop.org/software/systemd/man/daemon.html#Socket-Based%20Activation
[2]0pointer blog, "systemd for Developers I", http://0pointer.de/blog/projects/socket-activation.html
[3]FreeDesktop, systemd official man page "sd-daemon", https://www.freedesktop.org/software/systemd/man/sd-daemon.html
[4]Qt official documentation, QTcpServer::setSocketDescriptor, http://doc.qt.io/qt-5/qtcpserver.html#setSocketDescriptor
[5]Cutelyst project, 1.3.0 release notes, http://cutelyst.org/2017/01/24/cutelyst-1-3-0-released
[6]FreeDesktop, systemd official man page "socket", https://www.freedesktop.org/software/systemd/man/systemd.socket.html#Options