Принятие TCP-соединения с тайм-аутом - PullRequest
0 голосов
/ 06 сентября 2018

Есть ли стандартный способ вызова accept с тайм-аутом?

Я устанавливаю сокет как неблокирующий, но он сразу возвращается с errno, установленным на EAGAIN, я быхотел бы подождать точку, в случае успеха вернуть дескриптор, если нет, вернуть -1.Я делаю это, но мне это не нравится, и я чувствую, что должен быть лучший путь.

template <class Rep, class Period>
socket_handler_t wait_for_connection(const std::chrono::duration<Rep, Period> &timeout_duration)
{
        set_nonblocking();

        auto c_lambda = [](int fd) -> int {
            struct sockaddr_storage conn_addr_;
            int addrlen = sizeof(conn_addr_);
            return accept(fd, (struct sockaddr *)&conn_addr_, (socklen_t *)&addrlen);
        };

        auto wait_ms = std::chrono::duration_cast<std::chrono::milliseconds>(timeout_duration);
        wait_ms /= 10;

        socket_handler_t connfd = -1;
        auto count = 0U;

        while (count < 10)
        {
            count++;
            connfd = c_lambda(socket_handle);
            if (errno == EWOULDBLOCK || errno == EAGAIN)
            {
                std::puts(std::to_string(wait_ms.count()).c_str());
                std::this_thread::sleep_for(timeout_duration);
                continue;
            }
            else
            {
                break;
            }
        }

        set_blocking();

        return connfd;
}

1 Ответ

0 голосов
/ 07 сентября 2018

Существует ли стандартный способ вызова с тайм-аутом?

Зависит от того, что вы подразумеваете под «стандартным способом». accept сам по себе даже не указан стандартом C ++, поэтому, безусловно, нет никакого способа, указанного в C ++. В стандарте POSIX также нет функции accept, которая бы принимала аргумент времени ожидания, но время ожидания может быть реализовано с использованием стандартных функций POSIX.

Я бы хотел подождать некоторое время

Если вы хотите, чтобы accept ожидал, т.е. block , вам необходимо установить сокет в режим блокировки.

Чтобы реализовать тайм-аут для accept (или любого системного вызова блокировки), вы можете использовать таймер POSIX для отправки сигнала, который прервет вызов accept. После возврата accept необходимо проверить, успешно ли оно было прервано или прервано по другой причине.

Пример использования таймера POSIX. Принять моделируется с помощью sleep:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>

void die(const char*);

volatile sig_atomic_t timeout_reached;
const int timeout_signal = SIGRTMIN;

int main()
{
    int limit = 1;

    timer_t timer_id;
    sigevent sev{};
    sev.sigev_notify = SIGEV_SIGNAL;
    sev.sigev_signo = timeout_signal;
    if (timer_create(CLOCK_MONOTONIC, &sev, &timer_id))
        die("timer_create");

    timeout_reached = false;

    struct sigaction sa{};
    sa.sa_flags = SA_RESETHAND;
    sa.sa_handler = [](int) {
        timeout_reached = true;
    };
    if (sigaction(timeout_signal, &sa, nullptr))
        die("sigaction");

    itimerspec its {};
    its.it_value.tv_sec = limit;
    if (timer_settime(timer_id, 0, &its, nullptr))
        die("timer_settime");

    while(!timeout_reached) {
        std::cout << "start accepting" << std::endl;
        // blocking accept; we simulate it using sleep
        sleep(100000);
        // check here whether accept succeeded
    }
    std::cout << "timed out after " << limit << " seconds\n";
}

void die(const char* msg) {
    perror(msg);
    exit(EXIT_FAILURE);
}

Обратите внимание, что этот пример, вероятно, не будет работать без изменений, если вы используете потоки.


Альтернативный подход заключается в использовании таймера, который использует многопоточный обратный вызов, и в этом обратном вызове подключается к сокету, чтобы остановить блокировку. Полный пример, включающий использование TCP:

#include <iostream>
#include <atomic>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>

void die(const char*);

std::atomic<bool> timeout_reached;
constexpr int port = 50000;

int main()
{
    int limit = 1;

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1)
        die("socket");

    sockaddr_in listen_addr{};
    listen_addr.sin_family = AF_INET;
    listen_addr.sin_port = htons(port);
    if(bind(sock, (sockaddr*)&listen_addr, sizeof listen_addr))
        die("bind");

    if(listen(sock, SOMAXCONN))
        die("listen");

    timer_t timer_id;
    sigevent sev{};
    sev.sigev_notify = SIGEV_THREAD;
    sev.sigev_notify_function = [](sigval) {
        timeout_reached = true;
        int client_sock = socket(AF_INET, SOCK_STREAM, 0);
        sockaddr_in client_addr;
        client_addr.sin_family = AF_INET;
        client_addr.sin_port = htons(port);
        if(inet_pton(AF_INET, "127.0.0.1", &client_addr.sin_addr) != 1)
            die("inet_pton");
        if(connect(client_sock, (sockaddr*)&client_addr, sizeof client_addr))
            die("connect");
        if(close(client_sock))
            die("close");
    };
    if (timer_create(CLOCK_MONOTONIC, &sev, &timer_id))
        die("timer_create");

    timeout_reached = false;

    itimerspec its {};
    its.it_value.tv_sec = limit;
    if(timer_settime(timer_id, 0, &its, nullptr))
        die("timer_settime");

    for(;;) {
        std::cout << "start accepting" << std::endl;
        sockaddr_storage addr;
        socklen_t addrlen = sizeof addr;
        int fd = accept(sock, (sockaddr*)&addr, &addrlen);
        if(fd != -1) {
            if (timeout_reached) {
                close(fd);
                break;
            }
            // read or whatever
            close(fd);
        } else {
            // handle EAGAIN etc
        }
    }
    std::cout << "timed out after " << limit << " seconds\n";
    if(close(sock))
        die("close");
}

void die(const char* msg) {
    perror(msg);
    exit(EXIT_FAILURE);
}

Этот подход может быть проще портировать на определенные системы, которые не поддерживают прерывание accept.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...