• 网络编程(七)网络超时检测


    一、概念

    介于阻塞和非阻塞之间,可以自己设置一个时间,在设置的时间内,如果没数据就阻塞等待,如果设置的时间到了还没数据,则立即切换为非阻塞。

    二、实现方式

    (一) 使用select实现超时检测

    1. select函数补充说明:

    #include 
    int select(int nfds, fd_set *readfds, fd_set *writefds,
              fd_set *exceptfds, struct timeval *timeout);
    	其中 最后一个参数 timeout 就是超时时间
        NULL 永久阻塞 直到就绪为止
        也可以使用下面的结构体指定超时时间
        struct timeval {
            long    tv_sec;         /* 秒 */
            long    tv_usec;        /* 微秒 */
        };
        如果结构体的两个成员都为 0  则表示非阻塞
    
    返回值:
        成功 就绪的文件描述符个数
        失败 -1 重置错误码
        超时  0
    
    • 注:
    • select采用倒计时方式计时,如果在循环中使用,需要每次重置时间
    • 超时时,select会返回0

    2. 使用示例

    #include 
    
    int main(int argc, char const *argv[])
    {
        if(3 != argc){
            printf("Usage:%s Ipv4 port\n",argv[0]);
            exit(-1);
        }
        //创建套接字
        int sockfd=0;
        if(-1 ==(sockfd = socket(AF_INET,SOCK_STREAM,0))){
            ERR_LOG("socket error");
        }
        //填充结构体
        struct sockaddr_in serveraddr;
        serveraddr.sin_family=AF_INET;
        serveraddr.sin_addr.s_addr=inet_addr(argv[1]);
        serveraddr.sin_port=htons(atoi(argv[2]));
        socklen_t serverlen = sizeof(serveraddr);
        //绑定
        if(-1 == bind(sockfd,(struct sockaddr *)&serveraddr,serverlen)){
            ERR_LOG("bind error");
        }
        //开启监听
        if(-1 ==listen(sockfd,5)){
            ERR_LOG("listen error");
        }
        //创建集合
        fd_set readfds;
        FD_ZERO(&readfds);
        fd_set tempfds;
        FD_ZERO(&tempfds);
        int acceptfd=0;
        int max_fd=0;
        int ret=0;
        int i=0;
        int nbytes=0;
        char buff[128]={0};
        //将套接字加入集合
        FD_SET(sockfd,&readfds);
        max_fd=max_fd>sockfd?max_fd:sockfd;
        struct timeval tm;
        while(1){
            tm.tv_sec=5;
            tm.tv_usec=0;
            tempfds = readfds;
            if(-1 == (ret = select(max_fd+1,&tempfds,NULL,NULL,&tm))){
                ERR_LOG("select error");
            }else if(0 == ret){
                printf("已经五秒没有fd就绪了\n");
                continue;
            }
            printf("有fd就绪\n");
            //说明有fd就绪了,判断是不是sockfd
            for(i=3;i<max_fd+1 && 0<ret;i++){
                //先判断是不是这个fd就绪了
                if(FD_ISSET(i,&tempfds)){
                    ret--;
                    //判断就绪的fd是不是sockfd
                    if(sockfd == i){//说明有新的客户端接入
                        if(-1 == (acceptfd = accept(i,NULL,NULL))){
                            ERR_LOG("accept error");
                        }
                        //将新的acceptfd加入集合
                        FD_SET(acceptfd,&readfds);
                        max_fd=max_fd>acceptfd?max_fd:acceptfd;
                        printf("用户[%d]连接\n",i);
                    }else{//说明是已连接的客户端要通信
                        printf("接收到用户[%d]的消息\n",i);
                        if(-1 == (nbytes = recv(i,buff,sizeof(buff),0))){
                            ERR_LOG("recv error");
                        }else if(0 == nbytes){
                            printf("用户[%d]断开连接\n",i);
                            //从集合中删除
                            FD_CLR(i,&readfds);
                            close(i);
                            continue;
                        }
                        if(!strcmp(buff,"quit")){//用户退出
                            printf("用户[%d]退出\n",i);
                            //从集合中删除
                            FD_CLR(i,&readfds);
                            close(i);
                            continue;
                        }
                        //正常数据处理
                        printf("用户[%d]发送消息[%s]\n",i,buff);
                        strcat(buff,"T^T");
                        if(-1 == send(i,buff,sizeof(buff),0)){
                            ERR_LOG("send error");
                        }
                    }
                }
            }
        } 
        close(sockfd);
        return 0;
    }
    
    

    3. 输出结果

    在这里插入图片描述

    (二) 使用setsockopt函数

    1. 函数定义

    #include 
    #include 
    
    int getsockopt(int sockfd, int level, int optname,
              void *optval, socklen_t *optlen);
    int setsockopt(int sockfd, int level, int optname,
              const void *optval, socklen_t optlen);
    
    功能:设置或者获取套接字属性
    参数:
        sockfd:要操作的套接字
        level:选项的级别
            套接字API级别  SOL_SOCKET
            TCP级别       IPPROTO_TCP
            IP级别        IPPROTO_IP
        optname:选项的名字
            套接字API级别
                SO_BROADCAST  允许发送广播
                SO_RCVBUF     接收缓冲区的大小
                SO_SNDBUF     发送缓冲区的大小
                SO_RCVTIMEO   接收超时时间
                    参数是一个 struct timeval 结构体
                    如果超时了 调用会返回-1 错误码 为 EAGAIN
                SO_SNDTIMEO   发送超时时间
                SO_REUSEADDR  允许端口复用
            TCP级别
                TCP_NODELAY   关闭Nagle算法
            IP级别
                IP_ADD_MEMBERSHIP  设置加入多播组
        optval:选项的值,没有特殊说明时,一般是int型指针;1打开;0关闭
        optlen:optval的长度
    返回值:
        成功 0
        失败  -1 重置错误码
    
    • 注:如果参数没有特殊说明,一般是int型

    2. 获取发送缓冲区和接收缓冲区的大小

    #include 
    
    int main(int argc, char const *argv[])
    {
        int sockfd=0;
        if(-1 == (sockfd = socket(AF_INET,SOCK_STREAM,0))){
            ERR_LOG("socket error");
        }
    
        int snd_buff_size = 0;
        int snd_buff_size_len = sizeof(snd_buff_size);
        if(-1 == getsockopt(sockfd,SOL_SOCKET,SO_SNDBUF,&snd_buff_size,&snd_buff_size_len)){
            ERR_LOG("getsockopt error");
        }
        printf("Sendbuff:%dKB\n",snd_buff_size/1024);
        
        int recv_buff_size = 0;
        int recv_buff_size_len = sizeof(recv_buff_size);
        if(-1 == getsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&recv_buff_size,&recv_buff_size_len)){
            ERR_LOG("getsockopt error");
        }
        printf("Recvbuff:%dKB\n",recv_buff_size/1024);
        return 0;
    }
    

    输出结果:
    在这里插入图片描述

    • 注:默认 发送缓冲区16k,接收缓冲区128k。

    3. 端口复用

    int flag = 1;
    if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)))
        ERR_LOG("setsockopt error");
    //把上述代码加在  创建套接字之后 和 bind 之前即可
    

    4. 设置超时时间

    struct timeval tm;
    tm.tv_sec = 5;
    tm.tv_usec = 0;
    if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tm, sizeof(tm)))
            ERR_LOG("setsockopt error");
    

    当超时时,会立刻返回一个错误(EAGAIN),可以检测错误码来决定如何处理。

    注:由已经设置过超时时间的 sockfd 产生的 acceptfd 会继承超时属性,
    如果想用一样的超时时间,就无需对 acceptfd 设置了;
    如果不想用一样的时间,可以使用setsockopt函数对每个acceptfd单独设置

    (三) alarm

    1. 相关概念

    2. sigaction

  • 相关阅读:
    防火墙基础实验配置
    Python爬虫入门基础学习(二)
    go语言 反向代理
    Cytoscape 安装教程 | Network Data Integration, Analysis, and Visualization in a Box
    quick3-hydra
    RHCSA认证考试---12.查找字符串
    Go中的工作池:并发任务的优雅管理
    电源硬件设计----升压(Boost)变换器基础
    二、一起学习Eclipse 安装(Neon 版本)
    霍尔电流传感器如何进行可靠性测试?主要应用在哪些领域?
  • 原文地址:https://blog.csdn.net/weixin_44254079/article/details/139746614