A Naive C++ Eventloop (Based Upon poll())#

Eventloop#

#pragma once

#include <map>

enum class EventAction 
{
    Continue,
    Quit,
};

class InputHandler
{
public:
    virtual ~InputHandler() = default;
    [[nodiscard]] virtual EventAction ready(int fd) = 0;
};

class Eventloop
{
public:
    void register_input(int fd, InputHandler*);
    void run();

private:
    std::map<int, InputHandler*> _inputs;    
};
#include "eventloop.h"

#include <vector>
#include <format>
#include <cassert>

#include <string.h>
#include <poll.h>


void Eventloop::register_input(int fd, InputHandler* h)
{
    const auto& [_, inserted] = _inputs.insert(std::make_pair(fd, h));
    assert(inserted);
}

void Eventloop::run()
{
    bool quit = false;
    while (!quit) {
        std::vector<struct pollfd> watches(_inputs.size());
        for (auto [fd, _]: _inputs)
            watches.push_back({
                    .fd = fd,
                    .events = POLLIN,
                });

        int nready = poll(&watches[0], watches.size(), -1);
        if (nready == -1) {
            auto msg = std::format("poll error ({}, {})", errno, strerror(errno));
            throw std::runtime_error(msg);
        }
        if (nready == 0)
            throw std::runtime_error("poll returns 0 though no timeout requested");

        for (const auto& watch: watches)
            if (watch.revents & POLLIN)
                switch (_inputs[watch.fd]->ready(watch.fd)) {
                    case EventAction::Quit: 
                        quit = true;
                        break;
                    case EventAction::Continue:
                        break;
                }
    }
}

Solution#

See underengineered C solution

#include "database.h"

#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <poll.h>
#include <assert.h>
#include <regex>

class ReceiveLineAndInsertRecord : public InputHandler
{
public:
    ReceiveLineAndInsertRecord(int fd, Database& db) : _fd(fd), _db(db) {}
    
    EventAction ready(int fd)
    {
        
    }
};

int main()
{
    Database db;

    int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sock == -1) {
        perror("socket");
        return 1;
    }
    struct sockaddr_in addr = {
        .sin_family = AF_INET,
        .sin_port = htons(1234),
        .sin_addr = INADDR_ANY,
    };
    int error = bind(sock, (struct sockaddr *)&addr, sizeof(addr));
    if (error == -1) {
        perror("bind");
        return 1;
    }

    bool quit = false;
    while (!quit) {
        struct pollfd watches[] = {
            {
                .fd = STDIN_FILENO,                    // <-- watch STDIN_FILENO
                .events = POLLIN,                      //     for input
            },
            {
                .fd = sock,                            // <-- watch UDP socket
                .events = POLLIN,                      //     for input
            },
        };

        int nready = poll(watches, 2, -1);             // <-- wait for either to become ready
        if (nready == -1) {                            //     (-1 ... no timeout)
            perror("poll");
            return 1;
        }
        assert(nready != 0);                           // <-- no timeout requested
        
        std::vector<std::string> lines;                // <-- possibly reading multiple lines in one swoop

        if (watches[0].revents & POLLIN) {             // <-- only if STDIN_FILENO has something
            char line[64];
            ssize_t nread = read(STDIN_FILENO, line, sizeof(line)-1);
            if (nread == -1) {
                perror("read(stdin)");
                return 1;
            }
            else if (nread == 0)                       // <-- EOF on stdin quits us
                quit = true;
            else 
                lines.push_back(std::string(line, nread));
        }
        if (watches[1].revents & POLLIN) {             // <-- only if USP socket has something
            char line[64];
            ssize_t nread = read(sock, line, sizeof(line)-1);
            if (nread == -1) {
                perror("read(udp)");
                return 1;
            }
            assert(nread > 0);                         // <-- no EOF over UDP
            lines.push_back(std::string(line, nread));
        }

        for (const auto& line: lines)
            if (Record r = split_line(line))
                db.insert(r);
            else
                std::println(stderr, "invalid line: \"{}\"", line);
    }

    db.commit();
    return 0;
};