为什么客户端的close()套接字不会导致服务器的select()返回

Ste*_*son 6 c sockets select tcp

[之前我曾问过类似的事情.这是一个更有针对性的版本.]

什么可以导致服务器的TCP套接字上的select()调用始终超时而不是"看到"客户端的套接字close()?在客户端,套接字是一个常规的socket() - 创建阻塞套接字,成功连接到服务器并成功传输往返事务.在服务器端,套接字通过accept()调用创建,阻塞,通过fork()传递给子服务器进程,由顶级服务器关闭,并由子服务器进程成功使用最初的交易.当客户端随后关闭套接字时,子服务器的select()调用始终超时(1分钟后),而不是在套接字上指示读取就绪状态.select()调用仅查找read-ready条件:write-ready和exception参数为NULL.

这是简化但逻辑上等效的select() - 在子服务器进程中使用代码:

int one_svc_run(
    const int           sock,
    const unsigned      timeout) 
{
    struct timeval      timeo;
    fd_set              fds;

    timeo.tv_sec = timeout;
    timeo.tv_usec = 0;

    FD_ZERO(&fds);
    FD_SET(sock, &fds);

    for (;;) {
        fd_set      readFds = fds;
        int         status = select(sock+1, &readFds, 0, 0, &timeo);

        if (status < 0)
            return errno;

        if (status == 0)
            return ETIMEDOUT;

        /* This code not reached when client closes socket */
        /* The time-out structure, "timeo", is appropriately reset here */
        ...            
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

这是客户端事件序列的逻辑等价物(错误处理未显示):

struct sockaddr_in *raddr = ...;

int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
(void)bindresvport(sock, (struct sockaddr_in *)0);
connect(sock, (struct sockaddr *)raddr, sizeof(*raddr));
/* Send a message to the server and receive a reply */
(void)close(sock);
Run Code Online (Sandbox Code Playgroud)

从不调用fork(),exec()和system().代码比这复杂得多,但这是相关调用的顺序.

Nagel的算法会导致FIN数据包在close()时不被发送吗?

SKi*_*SKi 1

select()Linux 的调用将修改timeout参数的值。从手册页:

在 Linux 上,select() 修改超时以反映未睡眠的时间量

所以你的timeo意志会归零。当它为零时select会立即返回(大多数情况下返回值为零)。

以下更改可能会有所帮助:

for (;;) {
    struct timeval timo = timeo;
    fd_set      readFds = fds;
    int         status = select(sock+1, &readFds, 0, 0, &timo);
Run Code Online (Sandbox Code Playgroud)