简体   繁体   English

超时接受TCP连接

[英]Accepting TCP connection with a time-out

Is there a standard way of calling accept with a timeout? 是否有标准的方法来调用带有超时的accept

I am setting the socket as a non-blocking, but it immediately returns with errno set to EAGAIN , I would like to wait for a period, if succeed return descriptor, if not, return -1. 我将套接字设置为非阻塞套接字,但它立即返回,并将errno设置为EAGAIN ,我想等待一段时间,如果成功返回描​​述符,否则返回-1。 I am doing this, but I don;t feel good about it and I feel like there has to be a better way. 我正在这样做,但是我对此并不满意,我觉得必须有更好的方法。

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;
}

Is there a standard way of calling accept with a timeout? 是否有标准的方法来调用带有超时的接受?

Depends on what you mean by "standard way". 取决于您所说的“标准方式”。 accept itself isn't even specified by the C++ standard, so there is certainly no way specified by C++. accept本身甚至不是C ++标准指定的,因此C ++当然没有指定任何方法。 There is no accept function that would take a timeout argument in POSIX standard either, but a timeout can be implemented using standard POSIX functionality. 在POSIX标准中也没有accept超时参数的accept函数,但是可以使用标准POSIX功能来实现超时。

I would like to wait for a period 我想等一下

If you want accept to wait ie block , you need to set the socket to blocking mode. 如果要accept等待,即阻塞 ,则需要将套接字设置为阻塞模式。

To implement a timeout for accept (or any blocking system call), you can use a POSIX timer to send a signal that will interrupt the accept call. 要实现accept (或任何阻塞的系统调用)超时,可以使用POSIX计时器发送一个信号,该信号将中断accept调用。 After accept returns, you need to check whether it succeeded, or was interrupted, or failed for other reason. accept返回后,您需要检查它是否成功,被中断或由于其他原因失败。

Example using a POSIX timer. 使用POSIX计时器的示例。 Accept is simulated using sleep : 接受是使用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);
}

Note that this example probably won't work without modification if you use threads. 请注意,如果您使用线程,则此示例在不进行修改的情况下可能无法工作。


An alternative approach, is to use a timer that uses a threaded callback, and within that callback connect to the socket to stop the blocking. 一种替代方法是使用计时器,该计时器使用线程回调,并在该回调内连接到套接字以停止阻塞。 A full example including the use of TCP: 包括使用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);
}

This approach may be easier to port to certain systems that don't support interruption of accept . 这种方法可能更容易移植到某些不支持中断accept

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM