c - select() 在 non_blocking 手动超时 connect() 调用的繁重条件下失败

标签 c linux sockets

我必须实现应用程序健康检查机制,虽然我取得了成功,但我使用非阻塞套接字和 select

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>

int connect_tout(char * hostname1, int port, int timeoutval)
{
    char *hostname = hostname1;         /* pointer to name of server */
    struct sockaddr_in saddr;               /* socket address */
    int s, i;

    fd_set fd_r, fd_w;
    struct timeval timeout;
    int flags;

    timeout.tv_sec = timeoutval;
    timeout.tv_usec =  0;

    memset(&saddr, 0, sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(port);
    saddr.sin_addr.s_addr = inet_addr(hostname1);
    s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    /* set the socket fd to non-blocking mode */
    fcntl(s, F_SETFL, (flags = fcntl(s, F_GETFL)) | O_NONBLOCK);

    connect(s, (struct sockaddr *)&saddr, sizeof(saddr));

    FD_ZERO(&fd_r);
    FD_ZERO(&fd_w);
    FD_SET(s, &fd_r);
    FD_SET(s, &fd_w);

    /*  timeout durring connect() ??  */
    select(s+1, &fd_r, &fd_w, NULL, &timeout);
    if(FD_ISSET(s, &fd_w))
    {
        printf("ALIVE\n");
    }
    else
    {
        printf("Conect TIMEOUT \n");
        close(s);

        return errno;
    }

    i = connect(s, (struct sockaddr *)&saddr, sizeof(saddr));
    if(i)
    {
        printf("Conect failed errno:%d\n",errno);
        perror("connect:");
        close(s);

        return errno;
    }
    else
    {
        printf("Connect  passed  and OK \n");
        close(s);

        return 1;
    }

    close(s);
    return 1;
}

int main (int argc, char *argv[])
{
    int ret;

    if (argc < 3)
    {
        printf("Usage: %s [host] [port] [timout]\n", argv[0]);
        exit(1);
    }
    char *hostname = argv[1];         /* pointer to name of server <IP address>*/

    connect_tout(hostname, atoi(argv[2]), atoi(argv[3])); 

    return 0;
}

但是当我的代码运行机器的 fd 使用率非常高时,我的问题就来了。注意:在我的系统中一次打开多个 fds 是常见的行为。那么这 block 每次都失败

if(FD_ISSET(s, &fd_w))
{
    printf("ALIVE\n");
} 
else
{
    close(s);

    return errno;

    printf("Conect TIMEOUT\n");
} 

正如我在这样的环境中所说的那样,它通过说 TIMEOUT 失败了,我想知道为什么 select 没有这么快确定就绪的描述符而失败,而且每次都是这样。 FD_ISSET() 是否也有疑问?
P S :当系统在正常数量的 fds 下时,这运行良好。对不起,我刚刚在这里粘贴了我的示例工作代码的错误程序。稍后我会检查错误

最佳答案

对于非阻塞的 connect() 用法,您在收到可写通知后不会再次调用它。相反,您应该使用带有 SO_ERROR 选项的 getsockopt() 检查套接字的错误状态。

您没有检查任何调用的返回值,这使得您的代码无法真正正确地确定任何失败。请注意,如果传入的超时本身为 0,则不检查大小写,这将导致 select() 立即返回套接字的瞬时状态。请注意,套接字 API 未记录检查连接套接字的可读通知。

int s = socket(PF_INET, SOCK_STREAM, 0);
assert(!(s < 0));
int r = fcntl(s, F_SETFL, fcntl(s, F_GETFL, 0)|O_NONBLOCK);
assert(r == 0);
r = connect(s, (struct sockaddr *)&saddr, sizeof(saddr));
if (r < 0) {
    if (errno == EINPROGRESS) {
        FD_ZERO(&fd_w);
        FD_SET(s, &fd_w);
        r = select(s+1, NULL, &fd_w, NULL, NULL);
        if (r < 0) {
            perror("select");
            abort();
        }
        assert(r == 1);
        assert(FD_ISSET(s, &fd_w));
        int erropt = -1;
        socklen_t errlen = sizeof(erropt);
        r = getsockopt(s, SOL_SOCKET, SO_ERROR, &erropt, &errlen);
        assert(r == 0);
        if (erropt != 0) {
            errno = erropt;
            perror("connect[after select]");
            abort();
        }
        /* connect succeeded asynchronously */
    } else {
        perror("connect[direct call]");
        abort();
    }
} else {
    /* connect succeeded synchronously */
}

关于c - select() 在 non_blocking 手动超时 connect() 调用的繁重条件下失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22967967/

相关文章:

c - 如何在 VNC 紧密查看器上运行我的可执行文件?

c++ - 向多个客户端发送相同的数据包

android - Android 的 C 编译器如何工作。

linux - Docker $(pwd) 和 bash 别名

linux - 学生 Shell - cd 不工作

linux - 用于在另一个目录中创建文件的 shell 重定向命令

java - 如果服务器没有客户端 TCP 使用 java 请求的文件,如何显示错误消息

ruby 套接字 : Error (EINVAL) while trying to bind a sending socket to a port

c - C 中的英特尔 AVX 乘法错误,

c - 缓冲区溢出的困境