在C语言中进行tcp套接字编程时,为connect()函数设置超时时间会导致recv()函数出现问题。

8

在我的程序中,如果服务器不可达,connect函数需要太长时间。因此,我尝试使用select()设置连接超时。现在的问题是,当我尝试使用recvfrom()从服务器接收数据时,会出现错误“EAGAIN”。下面是用于连接和从服务器接收数据的代码。

int sock;
struct sockaddr_in addr;
int connectWithServer
{

    int status;

    struct timeval  timeout;
    timeout.tv_sec = 10;
    timeout.tv_usec = 0;

    addr.sin_port = htons(port);
    sock = socket (AF_INET,SOCK_STREAM,0);
    inet_pton(AF_INET,serverIP,&addr.sin_addr);

    fd_set set;
    FD_ZERO(&set);
    FD_SET(sock, &set);

    fcntl(sock, F_SETFL, O_NONBLOCK);

    if ( (status = connect(sock, (struct sockaddr*)&addr, sizeof(addr))) == -1)
    {
        if ( errno != EINPROGRESS )
            return status;

    }
    status = select(sock+1, NULL, &set, NULL, &timeout);

    return status;
}


long int receiveResponse (void *response , unsigned int length)
{
    socklen_t sockLen = sizeof(struct sockaddr);
    long int received = recvfrom(sock, response, length, 0,(struct sockaddr *)&addr,  &sockLen);
    printf("Received %ld bytes...  err %d\n",received, errno);

    return received;
}

recvfromconnect不匹配。前者用于UDP套接字,而后者与TCP相关。 - Davide Berra
如果没有超时,它可以正常工作。 - Rajesh
1
是的,它可以工作,但“from”参数是无用的,因为您知道谁是对等方。不管怎样,这只是一个建议。 - Davide Berra
4个回答

6
设置C语言中tcp socket编程中connect()函数的超时时间是有效的。问题在于之后的recvfrom()函数,因为您将套接字留在非阻塞模式下,并且不知道如何处理产生的EAGAIN错误。因此,要么使用select()函数告诉您套接字何时准备好读取,要么在完成连接后将套接字重新设置为阻塞模式。

5
在调用recv()函数之前,套接字应该再次设置为阻塞模式。
fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) & ~O_NONBLOCK);

5
您之所以收到 EAGAIN 是因为套接字缓冲区中没有数据可读,并且您的套接字被设置为非阻塞模式。既然您没有与对等方连接,我对此并不感到惊讶。
man recvfrom 中可以看到以下内容:
如果在套接字上没有可用消息,则接收调用等待消息到达,除非该套接字是非阻塞的(请参见 fcntl(2)),在这种情况下返回值为 -1,并将外部变量 errno 设置为 EAGAIN。接收调用通常返回任何可用数据,最多请求的数量,而不是等待完全请求的接收。
另一种情况可能是:
您的套接字可能已连接,但是您检查是否接收到某些东西太快了。为了避免这种情况,在 recvfrom 之前加入另一个 select,以便仅在确保收到某些内容时才从套接字缓冲区中提取数据包(调用 readfrom 或只是 read)。

从 select 返回值可以判断连接是否成功。 - Rajesh
你确定吗?在你的描述中,你说对等方无法到达。不管怎样... select 的返回值是什么?你测试过你的套接字是否被 FD_ISSET 触发了吗? - Davide Berra
select() 返回了 "1"。 - Rajesh

5
第一个成功的选择意味着连接操作已经完成,但并不一定意味着它成功了,来自connect man页面,您应该检查SO_ERROR以确保它成功完成。

可以通过选择写入套接字来选择使用select(2)或poll(2)进行完成。 在select(2)指示可写性后,使用getsockopt(2)读取SOL_SOCKET级别下的SO_ERROR选项,以确定connect()是否已成功完成(SO_ERROR为零)还是未成功完成(SO_ERROR是此处列出的常见错误代码之一,解释失败的原因)。

因此,在您的代码中,您应该这样做:

int ret;
ret=select(sockfd+1, NULL, &wfds, NULL, NULL); //should use timeout
if(ret==1 && getSocketOpt(sockfd, SO_ERROR) ==0) {
    return 0; //successfully connected
}

接着,如其他答案所述,在写入或读取套接字之前,您应该再次调用select。


谢谢提供信息... 有没有其他设置超时的方法? - Rajesh
@RAJESH 是的,我认为可以使用信号,但我相信选择是惯用的方法,只要在选择连接后检查 SO_ERROR 即可。 - iabdalkader
很棒的答案!请问这里的超时时间推荐使用什么值,比如连接超时? - pa1
如果select()返回0,则表示套接字超时。如果返回-1,则表示发生了错误。对于大于0的值,套接字上发生了一些有趣的事情。难道不值得根据这个来做决策吗?为什么我们还需要检查SO_ERROR(我们是否在检查套接字在超时后刚连接的一个边缘案例)? - iammilind

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接