Skip to content
Snippets Groups Projects
tcp_server_socket.cpp 5.36 KiB
Newer Older
/*
 * tcp_server_socket.cpp
 *
 *  Created on: 02.01.2021
 *      Author: doralitze
 */
#include "net/tcp_server_socket.hpp"

#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <arpa/inet.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <functional>

#include "macros.hpp"
#include "net/socketaddress.hpp"
#include "net/tcp_client.hpp"
namespace rmrf::net {

Benny Baumann's avatar
Benny Baumann committed
tcp_server_socket::tcp_server_socket(
    const socketaddr &socket_identifier,
    incoming_client_listener_type client_listener_
) :
    ss{nullptr},
    client_listener(client_listener_),
    overflow_client_listener(nullptr),
    number_of_connected_clients(0),
    max_number_of_simulataneusly_allowed_clients(0)
Benny Baumann's avatar
Benny Baumann committed
{
    auto_fd socket_fd{socket(socket_identifier.family(), SOCK_STREAM, 0)};

    if (!socket_fd.valid()) {
        // TODO implement propper error handling
        throw netio_exception("Failed to create socket fd.");
    }

    if (auto error = bind(socket_fd.get(), socket_identifier.ptr(), socket_identifier.size()); error != 0) {
        std::string msg = "Failed to bind to all addresses (FIXME). Errorcode: " + std::to_string(error);

        if (socket_identifier.family() == AF_INET6) {
            sockaddr_in* inptr = (sockaddr_in*) socket_identifier.ptr();
            const auto port = ntohs(inptr->sin_port);

            if (port < 1024) {
                msg += "\nYou tried to bind to a port smaller than 1024. Are you root?";
            }
        } else if (socket_identifier.family() == AF_INET) {
            sockaddr_in6* inptr = (sockaddr_in6*) socket_identifier.ptr();
            const auto port = ntohs(inptr->sin6_port);
Benny Baumann's avatar
Benny Baumann committed

            if (port < 1024) {
                msg += "\nYou tried to bind to a port smaller than 1024. Are you root?";
            }
        }

        throw netio_exception(msg);
    }

    // Append the non blocking flag to the file state of the socket fd.
    // TODO This might be linux only. We should check that
    fcntl(socket_fd.get(), F_SETFL, fcntl(socket_fd.get(), F_GETFL, 0) | O_NONBLOCK);

    if (listen(socket_fd.get(), 5) == -1) {
        throw netio_exception("Failed to enable listening mode for raw socket");
    }

    this->ss = std::make_shared<async_server_socket>(std::forward<auto_fd>(socket_fd));

    using namespace std::placeholders;
    this->ss->set_accept_handler(std::bind(&tcp_server_socket::await_raw_socket_incomming, this, _1, _2));
    this->set_low_latency_mode(false);
}

static inline socketaddr get_ipv6_socketaddr(const uint16_t port) {
Benny Baumann's avatar
Benny Baumann committed
    sockaddr_in6 addr;
    addr.sin6_family = AF_INET6;
    addr.sin6_port = htons(port);
    addr.sin6_addr = IN6ADDR_ANY_INIT;
    socketaddr sa{addr};
    return sa;
Benny Baumann's avatar
Benny Baumann committed
tcp_server_socket::tcp_server_socket(
    const uint16_t port,
    incoming_client_listener_type client_listener_
) :
    tcp_server_socket{get_ipv6_socketaddr(port), client_listener_}
{}
Benny Baumann's avatar
Benny Baumann committed
void tcp_server_socket::await_raw_socket_incomming(
    async_server_socket::self_ptr_type ass,
    const auto_fd &socket
) {
    MARK_UNUSED(ass);
    struct sockaddr_storage client_addr;
Benny Baumann's avatar
Benny Baumann committed
    socklen_t client_len = sizeof(client_addr);
    int client_fd_raw = accept(socket.get(), (struct sockaddr*)&client_addr, &client_len);
    socketaddr address{client_addr};
Benny Baumann's avatar
Benny Baumann committed
    if (client_fd_raw < 0) {
        throw netio_exception("Unable to bind incoming client");
    }
    auto flags = O_NONBLOCK;
    if (
        const auto existing_fd_flags = fcntl(client_fd_raw, F_GETFL, 0);
        existing_fd_flags == -1 || fcntl(client_fd_raw, F_SETFL, existing_fd_flags | flags) == -1
    ) {
        throw netio_exception("Failed to set socket mode. fcntl resulted in error:" + std::to_string(errno));
    }
Benny Baumann's avatar
Benny Baumann committed
    // Generate client object from fd and announce it
    this->number_of_connected_clients++;
    using namespace std::placeholders;
    if (this->is_low_latency_mode_enabled()) {
        int one = 1;
        setsockopt(socket.get(), IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
#ifdef __linux__
        setsockopt(socket.get(), IPPROTO_TCP, TCP_QUICKACK, &one, sizeof(one));
#endif
    }

    auto weak_this = this->weak_from_this();
    tcp_client::destructor_cb_type cb = [weak_this](exit_status_t status) {
        auto ref_this = weak_this.lock();

        if (!ref_this) {
            return;

        ref_this->client_destructed_cb(status);
    };
    auto client = std::make_shared<tcp_client>(cb, auto_fd(client_fd_raw), address);

    if (this->max_number_of_simulataneusly_allowed_clients == 0 || this->get_number_of_connected_clients() <= this->max_number_of_simulataneusly_allowed_clients) {
        this->client_listener(client);
    } else if (this->overflow_client_listener != nullptr) {
        this->overflow_client_listener(client);
unsigned int tcp_server_socket::get_number_of_connected_clients() const {
Benny Baumann's avatar
Benny Baumann committed
    return this->number_of_connected_clients;
void tcp_server_socket::client_destructed_cb(exit_status_t exit_status) {
Benny Baumann's avatar
Benny Baumann committed
    MARK_UNUSED(exit_status);
Benny Baumann's avatar
Benny Baumann committed
    this->number_of_connected_clients--;
void tcp_server_socket::set_client_overflow_handler(incoming_client_listener_type overflow_client_listener_) {
    this->overflow_client_listener = overflow_client_listener_;
}

void tcp_server_socket::set_maximum_concurrent_connections(unsigned int max_connections) {
    this->max_number_of_simulataneusly_allowed_clients = max_connections;
}