超时接收

网络接收超时

  一般情况下网络接收数据都采用阻塞属性,就等于一直等待对方数据到达。有的场合中我们可能不需要一直等待,因为可能会没有结果,这是可以使用超时接收,在规定的时间内如果没有数据到达。,则超时退出。
  实现的主要方法有三种:
    1.使用多路复用的select函数设置超时时间;
    2.设置闹钟,当设置时间到达时将会产生一个SIGALRM信号,可以设置该信号的信号捕捉处理函数;
    3.设置套接字的属性为超时属性。

多路复用实现网络接收超时

  以下为服务器代码,多路复用介绍见随笔<socket的四种IO模型>

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

int main(int argc, char const *argv[])
{
    int ret_val;
    char *rev_data = (char *)calloc(100, sizeof(char));
    if (rev_data == NULL)
    {
        printf("calloc error");
        return -1;
    }
   
    int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sock_fd)
    {
        perror("socket error");
        return -1;
    }

    struct sockaddr_in server_addr;
    bzero(&server_addr , sizeof(server_addr)); 
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(65110);
    server_addr.sin_addr.s_addr = inet_addr("172.31.47.119");

    ret_val = bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (-1 == ret_val)
    {
        perror("bind error");
        return -1;
    }
    printf("bind successfully\n");

    ret_val = listen(sock_fd, 2);
    if (-1 == ret_val)
    {
        perror("listen error");
        return -1;
    }
    
    printf("listen successfully\n");

    struct sockaddr_in other_addr;
    socklen_t addrlen = sizeof(other_addr);
    int connect_fd;

    connect_fd = accept(sock_fd, (struct sockaddr *)&other_addr, &addrlen);
    if (-1 == connect_fd)
    {
        perror("accept error");
    }
    printf("accept successfully\n");

    char buf [100];
    fd_set set;
    struct timeval time_val;
    while (1)
    {
        time_val.tv_sec = 2;
        time_val.tv_usec = 0;

        FD_ZERO(&set);
        FD_SET(connect_fd, &set);
        FD_SET(STDIN_FILENO, &set);
        int max_fd = connect_fd > STDIN_FILENO ? connect_fd : STDIN_FILENO;
        int ret_val = select(max_fd+1, &set, NULL,NULL, &time_val);
        if (0 == ret_val)
        {
            printf("timeout\n");
        }
        
        bzero(rev_data , 100);
        if (FD_ISSET(connect_fd, &set))
        {
            int ret_val = recv(connect_fd, rev_data, 100, 0);
            if (ret_val > 0)
            {
                printf("recv client msg: %s", rev_data); 
            }
            continue;
        }
        if (FD_ISSET(STDIN_FILENO, &set))
        {
            fgets(buf , 100 , stdin);
            ret_val = send(connect_fd , buf , strlen(buf) , 0);
            continue;
        }
    }
    
    close(connect_fd);
    close(sock_fd);
    return 0;
}

输出结果:

注意:
  1.每次循环必须重新设置set和time_val;
  2.客户端代码使用随笔<网络套接字socket>的TCP客户端代码。

设置闹钟实现超时接收

  闹钟这种方式类似于信号驱动,信号驱动当收到信号时,证明有数据发送过来。闹钟使用setitimer函数来提前设定一个时间,当时间到达时,回自动产生一个信号SIGVTALRM。
  思路:
   1.使用signal函数设置好捕获闹钟信号,并设置好响应处理函数。
   2.使用setitimer()函数设置一个闹钟时间。
   3.当闹钟时间结束时产生一个SIGVTALRM信号。
   4.当捕获到闹钟信号则运行对应的响应函数。
   5.重新设置闹钟;

实验程序

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

int sockfd = 0;

void sig_recv(int sig)
{
    printf("timeout\n");
    struct sockaddr_in from_addr;
    int addrlen = sizeof(from_addr);
    char *recv_msg = calloc(1, 100);
    bzero(recv_msg, 100);
    int ret_val = recvfrom(sockfd, recv_msg, 100, 0, (struct sockaddr *)&from_addr, &addrlen);
    if (ret_val > 0)
    {
        printf("ip:%s port:%d receive a message:%s", 
            inet_ntoa(from_addr.sin_addr),
            ntohs(from_addr.sin_port), 
            recv_msg);
    }
}

int main(int argc, char const *argv[])
{
    struct sigaction *act = calloc(1, sizeof(struct sigaction));
    act->sa_handler = sig_recv;
    act->sa_flags =  SA_NODEFER;
    sigaction(SIGVTALRM, act, NULL);

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1)
    {
        perror("socker error");
        return -1;
    }
    printf("Socket created successfully\n");

    struct sockaddr_in server_addr;
    int addrlen = sizeof(server_addr);
    bzero(&server_addr, addrlen);

    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(65000);
    server_addr.sin_addr.s_addr = inet_addr("172.31.47.119");

    int ret_val = bind(sockfd, (struct sockaddr *)&server_addr, addrlen);
    if (-1 == ret_val)
    {
        perror("bind error");
        return -1;
    }
    printf("bind successfully\n");

    
    fcntl(sockfd ,F_SETOWN, getpid());// 设置套接字的拥有者
    int fl ;
    fl = fcntl(sockfd , F_GETFL);
    fl |=  O_NONBLOCK; // 不可以添加信号触发 选项到描述符中
    fcntl(sockfd , F_SETFL , fl);

    struct itimerval new_value;
    new_value.it_value.tv_sec = 2;  
    new_value.it_value.tv_usec = 0; //微秒
    new_value.it_interval.tv_sec = 2;
    new_value.it_interval.tv_usec = 0; //微秒
    setitimer(ITIMER_VIRTUAL, &new_value, NULL); //设置定时两秒钟,周期间隔也为两秒钟

    while (1);

    close(sockfd);
    return 0;
}

输出结果:

注意:
  1设置socket文件属性时不可以设置为信号触发.O_ASYNC,否则当客户端发送消息过来的时候将包I/O possible的错误;
  2.设置结构体itimerva时,不应该需要微秒而不设置,不需要用到微秒应该将其设置为0;
  3.设置时间类型为ITIMER_VIRTUAL,产生SIGVTALRM信号,设置时间类型为ITIMER_REAL产生SIGALRM信号;
  3.客户端程序客户端代码使用随笔<网络套接字socket>的UDP客户端代码。

设置套接字超时属性实现超时接收

  在Linux下,建立套接字都是阻塞属性,但是我们设置一个超时属性给套接字,这样读取套接字数据时,在规定的时间会阻塞,但是在规定的时间之外,将读取失败返回-1。
  思路:
  1.获得一个连接套接字;
  2.使用函数设置套接字的超时属性setsockopt();
  3.正常玩耍,当接收函数返回-1时就是超时的时候了。

相关API

getsockopt()
setsockopt()
    函数功能:
            获得/设置套接字属性
    头 文 件:
            #include <sys/types.h>         
            #include <sys/socket.h>
    定义函数:
            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 --> 优先级
                        SOL_SOCKET:套接字
                        IPPROTO_IP:IP优先级
                        IPPRO_TCP:TCP优先级
            optname --> 属性名
            optval --> 属性的值 使能为1,失能为0
            optlen --> 属性的值类型的长度
    返 回 值:
            成功  0 
            失败 -1
    备    注:
            补充optname:
            =============================SOL_SOCKET====================================:
            optname选项名字                                           optlen的大小
            SO_BROADCAST        允许发送广播数据            int 
            SO_DEBUG          允许调试                int 
            SO_DONTROUTE       不查找路由               int 
            SO_ERROR         获得套接字错误             int 
            SO_RCVTIMEO        接收超时                 struct timeval 
            SO_SNDTIMEO        发送超时                  struct timeval
            SO_TYPE          获得套接字类型             int 

            =========================IPPROTO_IP=======================================
            IP_HDRINCL       在数据包中包含IP首部          int 
            IP_OPTINOS       IP首部选项               int 
            IP_TOS          服务类型 
            IP_TTL          生存时间                int 
            IP_ADD_MEMBERSHIP      加入组播                             struct ip_mreq

            =========================IPPRO_TCP======================================
            TCP_MAXSEG       TCP最大数据段的大小           int 

实验程序

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


int main(int argc, char const *argv[])
{
    int ret_val;
    char *rev_data = (char *)calloc(100, sizeof(char));
    if (rev_data == NULL)
    {
        printf("calloc error");
    }
    
    int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sock_fd)
    {
        perror("socket error");
        return -1;
    }
     
    struct sockaddr_in server_addr;
    bzero(&server_addr , sizeof(server_addr)); 
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(65000);
    server_addr.sin_addr.s_addr = inet_addr("172.31.47.119");

    ret_val = bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (-1 == ret_val)
    {
        perror("bind error");
        return -1;
    }
    printf("bind successfully\n");

    ret_val = listen(sock_fd, 2);
    if (-1 == ret_val)
    {
        perror("listen error");
        return -1;
    }
    
    printf("listen successfully\n");

    struct sockaddr_in other_addr;
    socklen_t addrlen = sizeof(other_addr);
    int connect_fd = accept(sock_fd, (struct sockaddr *)&other_addr, &addrlen);
    if (-1 == connect_fd)
    {
        perror("accept error");
        return -1;
    }
    printf("accept successfully\n");

    // 设置超时时间
    struct timeval time_val ;
    time_val.tv_sec = 3 ;
    time_val.tv_usec = 0 ;
    // 设置已链接套接字为读取超时状态
    ret_val = setsockopt(connect_fd , SOL_SOCKET , SO_RCVTIMEO ,
                      &time_val, sizeof(time_val));
    if (ret_val == -1 )
    {
        perror("setsockopt error");
        return -1 ;
    }
   
    while (1)
    {
        bzero(rev_data , 100);
        ret_val = recv(connect_fd , rev_data , 100 , 0 );
        if (ret_val > 0)
        {
            printf("receive data is:%s", rev_data);
        }else if (ret_val == -1)
        {
            printf("timeout\n");
        }
    }
    
    close(connect_fd);
    close(sock_fd);
    return 0;
}

输出结果:

注意:
  1.此程序只可以由客户端向服务端发送数据,客户端程序参考以前的。

总结

  1.从实验效果来看使用多路复用和设置套接字超时属性的实验效果是最好的,设置闹钟实验效果有延迟;
  2.从程序来看设置套接字超时属性是最容易实现的,多路复用是最麻烦的。

posted @ 2021-01-19 16:38  ding-ding-light  阅读(440)  评论(0编辑  收藏  举报