zoukankan      html  css  js  c++  java
  • Linux C套接字选项SO_RCVTIMEO, SO_SNDTIMEO

    SO_RCVTIMEO, SO_SNDTIMEO介绍

    套接字选项SO_RCVTIMEO: 用来设置socket接收数据的超时时间;
    套接字选项SO_SNDTIMEO: 用来设置socket发送数据的超时时间;

    比如,一般情况下,调用accept/connect/send/recv, 进程会阻塞,但是如果对端异常,进行可能无法正常退出等待。如何让这些调用自动定时退出?
    可以使用诸如alarm定时器、I/O复用设置定时器,还可以使用socket编程里函数级别的socket套接字选项SO_RCVTIMEO和SO_SNDTIMEO,仅针对与数据接收和发送相关,而无需设置专门的信号捕获函数。

    能够作用的系统调用包括:send、sendmsg、recv、recvmsg、accept、connect。

    系统调用 有效选项 系统调用超时后的行为
    send SO_SNDTIMEO 返回-1,设置errno为EAGAIN或EWOULDBLOCK
    sendmsg SO_SNDTIMEO 返回-1,设置errno为EAGAIN或EWOULDBLOCK
    recv SO_RCVTIMEO 返回-1,设置errno为EAGAIN或EWOULDBLOCK
    recvmsg SO_RCVTIMEO 返回-1,设置errno为EAGAIN或EWOULDBLOCK
    accept SO_RCVTIMEO 返回-1,设置errno为EAGAIN或EWOULDBLOCK
    connect SO_SNDTIMEO 返回-1,设置errno为EINPROGRESS

    注意:

    1. EAGAIN通常和EWOULDBLOCK是同一个值;
    2. SO_RCVTIMEO, SO_SNDTIMEO不要求系统调用对应fd是非阻塞(nonblocking)的,但是使用了该套接字选项的sock fd,会成为nonblocking(即使之前是blocking)的。参见man手册ERRORS EAGAIN/EWOULDBLOCK的描述;

    man send关于EAGAIN / EWOULDBLOCK描述:

    EAGAIN or EWOULDBLOCK
    The socket is marked nonblocking and the requested operation would block. POSIX.1-2001 allows either error to be returned for this case, and does not require these constants to have the same value, so a portable application should check for both possibilities.

    示例1:设置connect超时时间

    根据系统调用accept的返回值,以及errno判断超时时间是否已到,从而决定是否开始处理超时定时任务。

    客户端程序:超时连接服务器

    /**
     * 客户端程序
     * 连接服务器,超时报错、返回
     * build:
     * $ gcc timeout_connect.c
     */
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <string.h>
    #include <errno.h>
    #include <assert.h>
    
    /* 超时连接 */
    int timeout_connect (const char *ip, int port, int time)
    {
        int ret = 0;
        struct sockaddr_in servaddr;
    	
        printf("client start...
    ");
    
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        inet_pton(AF_INET, ip, &servaddr.sin_addr);
        servaddr.sin_port = htons(port);
    
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        assert(sockfd >= 0);
    
        /* 通过选项SO_RCVTIMEO和SO_SNDTIMEO设置的超时时间的类型时timeval, 和select系统调用的超时参数类型相同 */
        struct timeval timeout;
        timeout.tv_sec = time;
        timeout.tv_usec = 0;
    
        socklen_t len = sizeof(timeout);
        ret = setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, len);
        if (ret == -1) {
            perror("setsockopt error");
    
            return -1;
        }
    
        if ((ret = connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr))) < 0) {
    	/* 超时对于errno 为EINPROGRESS. 下面条件如果成立,就可以处理定时任务了 */
            if (errno == EINPROGRESS) {
                perror("connecting timeout, process timeout logic");
                return -1;
            }
    
            perror("error occur when connecting to server
    ");
        }
    
        return sockfd;
    }
    
    int main(int argc, char *argv[])
    {
        if (argc <= 2) {
            printf("usage: %s ip_address port_number
    ", argv[0]);
            return 1;
        }
    
        const char *ip = argv[1];
        int port = atoi(argv[2]);
        printf("connect %s:%d...
    ", ip, port);
    
        int sockfd = timeout_connect(ip, port, 10);
        if (sockfd < 0) {
            perror("timeout_connect error");
            return 1;
        }
    
        return 0;
    }
    

    运行结果(随意输入一个服务器IP、端口):

    $ ./timeout_connect 192.168.0.105 8000
    connect 192.168.0.105:8000...
    client start...
    connecting timeout, process timeout logic: Operation now in progress
    timeout_connect error: Operation now in progress
    

    可以看到,本来阻塞的connect调用,10秒后返回-1,并且errno设置为EINPROGRESS。

    示例2:超时接收(服务器数据)

    服务器端

    监听本地任意IP地址,端口8001
    从键盘输入一行数据,就发送给用户;如果没有数据,就阻塞。

    /**
     * 服务器程序
     * 示例:超时接收服务器数据,超时时间例程中设置为10秒
     * 编译: $ gcc timeout_recv_server.c -o server
     * 运行方式:
     * $ ./server
     * 默认监听端口8001(根据实际情况修改)
     * 服务器功能:从键盘接收用户输入,每接收一行就向客户输出一行。如果没有用户输入,
     * 则阻塞。
     * 客户端需要跟服务器安装在同一网段上,为了测试方便,就直接都安装到同一机器上
     */
    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>
    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <signal.h>
    
    
    int sockfd = -1;
    
    void sig_func(int sig_no)
    {
        if (sig_no == SIGINT || sig_no == SIGTERM) {
            if (sockfd >= 0) {
                close(sockfd);
            }
    
            exit(1);
        }
    }
    
    int main()
    {
        struct sockaddr_in servaddr, cliaddr;
        int listenfd;
    
        signal(SIGINT, sig_func);
    
        printf("server start...
    ");
    
        if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
            perror("socket error");
            exit(1);
        }
        sockfd = listenfd;
    
        int on = 1;
        if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
            perror("setsocketopt error");
            exit(1);
        }
    
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        // servaddr.sin_addr.s_addr = INADDR_ANY;
        inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
        servaddr.sin_port = htons(8001);
    
        if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
            perror("bind error");
            exit(1);
        }
    
        if (listen(listenfd, 5) < 0) {
            perror("listen error");
            exit(1);
        }
    
        char buf[1024];
        socklen_t clilen = sizeof(cliaddr);
        int connfd;
    
        if ((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen)) < 0) {
            perror("accept error");
            exit(1);
        }
    
        printf("input a line string: 
    ");
        int nbytes;
        while (fgets(buf, sizeof(buf), stdin)) {
            nbytes = send(connfd, buf, strlen(buf), 0);
            if (nbytes < 0) {
                perror("send error");
                break;
            }
            else if (nbytes == 0) {
            
            }
            printf("send: %s
    ", buf);
        }
        
        close(connfd);
        close(listenfd);
    
        return 0;
    }
    

    客户端

    设置10秒超时,接收服务器数据。

    客户端10秒以内,接收到服务器数据,则直接打印;超过10秒,就报错退出。

    /**
     * 客户端程序
     * 示例:超时接收服务器数据,超时时间例程中设置为10秒
     * 编译: $ gcc timeout_recv_client.c -o client
     * 运行方式:
     * 如本地运行(对应服务器实际监听的IP地址和端口号) $ ./client 127.0.0.1 8001
     */
    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>
    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    
    int timeout_recv(int fd, char *buf, int len,  int nsec)
    {
        struct timeval timeout;
        timeout.tv_sec = nsec;
        timeout.tv_usec = 0;
    
        printf("timeout_recv called, timeout %d seconds
    ", nsec);
    
        if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) {
            perror("setsockopt error");
            exit(1);
        }
    
        int n = recv(fd, buf, len, 0);
    
        return n;
    }
    
    int main(int argc, char *argv[])
    {
        if (argc != 3) {
            printf("usage: %s <ip address> <port>
    ", argv[0]);
        }
    
        char *ip = argv[1];
        uint16_t port = atoi(argv[2]);
        
        printf("client start..
    ");
        printf("connect to %s:%d
    ", ip, port);
    
        int sockfd;
        if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
            perror("socket error");
            exit(1);
        }
        
        struct sockaddr_in servaddr;
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        inet_pton(AF_INET, ip, &servaddr.sin_addr);
        servaddr.sin_port = htons(port);
        
        int connfd;
        if ((connfd = connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) < 0) {
            perror("connect error");
            exit(1);
        }
    
        printf("success to connect server %s:%d
    ", ip, port);
        printf("wait for server's response
    ");
        char buf[100];
        while (1) {
            int nread;
            
            nread = timeout_recv(sockfd, buf, sizeof(buf), 10);
            if (nread < 0) {
                perror("timeout_recv error");
                exit(1);
            }
            else if (nread == 0) {
                shutdown(sockfd, SHUT_RDWR);
                break;
            }
    
            write(STDOUT_FILENO, buf, nread);
        }
    
        return 0;
    }
    

    客户端运行结果:
    可以看到,超过10秒后,客户端自动退出程序,而不再阻塞在recv。

    $ ./client 127.0.0.1 8001
    client start..
    connect to 127.0.0.1:8001
    success to connect server 127.0.0.1:8001
    wait for server's response
    timeout_recv called, timeout 10 seconds
    hello # 服务器端用户输入数据
    timeout_recv called, timeout 10 seconds
    nihao # 服务器端用户输入数据
    timeout_recv called, timeout 10 seconds
    timeout_recv error: Resource temporarily unavailable # 服务器端超时未输入数据,客户端程序运行结束
    

    参考

    [1]游双. Linux高性能服务器编程[M]. 机械工业出版社, 2013.

  • 相关阅读:
    布局重用 include merge ViewStub
    AS 常用插件 MD
    AS 2.0新功能 Instant Run
    AS .ignore插件 忽略文件
    AS Gradle构建工具与Android plugin插件【大全】
    如何开通www国际域名个人网站
    倒计时实现方案总结 Timer Handler
    AS 进行单元测试
    RxJava 设计理念 观察者模式 Observable lambdas MD
    retrofit okhttp RxJava bk Gson Lambda 综合示例【配置】
  • 原文地址:https://www.cnblogs.com/fortunely/p/15055618.html
Copyright © 2011-2022 走看看