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,&tv);
    // returns 1 sometimes
}

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

可能导致这种情况的原因是什么?有没有办法通过 select 获得一致的行为?

解决方法

确定非阻塞 connect() 是否完成的正确方法是询问 select() 可写性而不是可读性。这在 connect() documentation 中有明确说明:

EINPROGRESS
套接字是非阻塞的,连接不能立即完成。 (UNIX 域套接字失败,而是使用 EAGAIN。)可以通过选择要写入的套接字来 select(2)poll(2) 来完成select(2)表示可写性后,使用getsockopt(2)读取SO_ERROR级别的SOL_SOCKET选项,判断connect()是否成功完成({ {1}} 为零)或不成功(SO_ERROR 是此处列出的常见错误代码之一,用于解释失败的原因)。

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

试试这个:

poll()