Socket连接超时设置

原文连接:https://blog.csdn.net/daiyudong2020/article/details/50675219

一.基本概念(摘自:《unix网络编程》卷1 14.2 套接字超时)
在涉及套接字的I/O操作上设置超时的方法有以下三种
(1)调用alarm,它在指定超时期满时产生SIGALARM。这个方法涉及信号处理,而信号处理在不同的实现上存在差异,而且可能干扰进程中现有的alarm调用。
(2)在select中阻塞等待I/O(select有内置的时间限制),以此替代直接阻塞在readwrite调用上。
(3)使用SO_RECVTIMEOSO_SNDTIMEO套接字选项
上述三个技术都适用于输入输出操作(例如readwrite及其诸如recvfromsendto之类的变体),不过我们依然期待可用于connect的技术,因为TCP内置的connect超时相当长(典型值为75秒钟)。select可用来在connect上设置超时的先决条件是相应的套接字是处于非阻塞模式,而那两个套接字选项对connect并不适用。我们还指出,前两个技术适用于任何描述符,而第三个技术仅仅适用于套接字描述符。


注:阻塞模式下,当服务器连接不上时,通过命令 “netstat -tlnap|grep SENT” 可以看到客户端会处于SYN_SENT状态,直到connect超时


二.非阻塞模式socket
1.connect不需要考虑超时问题,立即返回一个错误EINPROGRESS,可通过检测fd是否可用判断连接是否建立完成
2.read不需要考虑超时问题,立即返回
3.write不需要考虑超时问题,立即返回


三.阻塞模式socket
1.connect需要考虑超时问题,否则当连接IP不可达的情况下,需要等待很长一段时间(默认时长)
2.read需要考虑超时问题,可通过setsockopt设置SO_RECVTIMEO选项
3.write需要考虑超时问题,可通过setsockopt设置SO_SNDTIMEO选项

四.带超时的connect,适用于阻塞模式与非阻塞模式socket(本质上是非阻塞connect)


附上测试代码,可通过更改main中的IP和端口进行测试,读懂这段代码需要了解select与getsockopt函数

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

int connect_nonb(int sockfd, const struct sockaddr *addr, socklen_t addrlen, int nsec)
{
    int flags, n, error;
    socklen_t len;
    fd_set rset, wset;
    struct timeval tval;

    /* 调用fcntl把套接字设置为非阻塞 */
    flags = fcntl(sockfd, F_GETFL, 0);
    fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);

    /* 发起非阻塞connect。期望的错误是EINPROGRESS,表示连接建立已经启动但是尚未完成  */
    error = 0;
    if ( (n = connect(sockfd, addr, addrlen)) < 0)
        if (errno != EINPROGRESS)
            return(-1);

    /* 如果非阻塞connect返回0,那么连接已经建立。当服务器处于客户端所在主机时这种情况可能发生 */
    if (n == 0)
        goto done; /* connect completed immediately */

    /* 调用select等待套接字变为可读或可写,如果select返回0,那么表示超时 */
    FD_ZERO(&rset);
    FD_SET(sockfd, &rset);
    wset = rset;
    tval.tv_sec = nsec;
    tval.tv_usec = 0;

    if ( (n = select(sockfd+1, &rset, &wset, NULL, nsec ? &tval : NULL)) == 0) {
        close(sockfd); /* timeout */
        errno = ETIMEDOUT;
        return(-1);
    }

    /* 检查可读或可写条件,调用getsockopt取得套接字的待处理错误,如果建立成功,该值将为0 */
    if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
        len = sizeof(error);
        if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
            return(-1); /* Solaris pending error */
    } else {
        perror("select error: sockfd not set");
        exit(1);
    }

    /* 恢复套接字的文件状态标志并返回 */
done:
    fcntl(sockfd, F_SETFL, flags); /* restore file status flags */
    if (error) {
        close(sockfd); /* just in case */
        errno = error;
        return(-1);
    }
    return(0);
}

int main()
{
    // socket  
    struct sockaddr_in servaddr;    
    short port = 9999;    
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);    
    servaddr.sin_family = AF_INET;    
    servaddr.sin_addr.s_addr = inet_addr("113.107.231.211");    
    servaddr.sin_port = htons(port);
    int timeout = 3;

    if (connect_nonb(sockfd, (sockaddr *) &servaddr, sizeof(sockaddr_in), 2) < 0) {  
        perror("connect fail: ");
        return(-1);
    }
    printf("connect success!\n");

    return 0;
}

参考:《unix网络编程》卷1

猜你喜欢

转载自blog.csdn.net/u014253011/article/details/81706072