首页 > 解决方案 > select() 在未连接的套接字上返回 1(不一致)

问题描述

我正在使用非阻塞套接字连接到服务器。
在一个特定的测试场景中,服务器宕机,这意味着一个 TCP SYN 出去了,但是没有响应,永远不可能建立连接。

在此设置中,通常会select在 2 秒后超时,返回 0。这是大多数情况下的行为,而且看起来是正确的。

然而,在大约 5% 的情况下,select立即返回 1(表示套接字在掩码中是可读的)。
但是当我read(2)从套接字-1中返回时,返回的是' Network is unreachable'

sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// sockfd checked and > 0
// set non-blocking

struct timeval tv{};
tv.tv_sec = 2;

int ret = connect(sockfd, addr, addrlen ); // addr set elsewhere
if (ret < 0 && errno == EINPROGRESS)
{
    fd_set cset;
    FD_ZERO(&cset);
    FD_SET(sockfd, &cset);
    
    ret = select(sockfd + 1, &cset, nullptr, nullptr, &tv);
    // returns 1 sometimes
}

在第一篇文章中,我错误地指出,在错误情况下,网络上只有一个 TCP SYN(没有重试)。
这不是真的; 在错误和非错误情况下,网络上都有一个 TCP SYN 在 1 秒后重新发送。

什么可能导致这种情况,有没有办法获得一致的行为select

标签: clinuxsocketsnonblocking

解决方案


确定非阻塞是否完成的正确connect()方法是要求select()写性而不是可读性connect()这在文档中明确说明:

EINPROGRESS
套接字是非阻塞的,连接不能立即完成。(UNIX 域套接字改为失败EAGAIN。) 可以通过选择用于写入的套接字来完成select(2)poll(2)完成。在select(2)指示可写性之后,用于getsockopt(2)读取SO_ERROR级别的选项SOL_SOCKET以确定是connect()成功完成(SO_ERROR为零)还是未成功完成(是SO_ERROR此处列出的常见错误代码之一,解释失败的原因)。

在您知道连接实际上已首先建立之前,使用/测试套接字的可读性是未定义的行为。select()poll()

试试这个:

sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// sockfd checked and > 0
// set non-blocking

int ret = connect(sockfd, addr, addrlen); // addr set elsewhere
if (ret < 0)
{
    if (errno != EINPROGRESS)
    {
        close(sockfd);
        sockfd = -1;
    }
    else
    {
        fd_set cset;
        FD_ZERO(&cset);
        FD_SET(sockfd, &cset);
    
        struct timeval tv{};
        tv.tv_sec = 2;

        ret = select(sockfd + 1, nullptr, &cset, nullptr, &tv);
        if (ret <= 0)
        {
            close(sockfd);
            sockfd = -1;
        }
        else
        {
            int errCode = 0;
            socklen_t len = sizeof(errCode);
            getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &errCode, &len);

            if (errCode != 0)
            {
                close(sockfd);
                sockfd = -1;
            }
        }
    }
}

if (sockfd != -1)
{
    // use sockfd as needed (read(), etc) ...
    close(sockfd);
}

推荐阅读