网络编程中,创建好套接字后以阻塞的方式读写,如果没有数据可读的话,程序会一直等待。事实上,网络状况一直不断变化,很有可能在通讯过程中出现网络连接断开。我们在程序中有必要对这种情况进行检测,从而及时做出响应。下面介绍几种常用的超时检测方法(假设我们要求通过套接字等待数据的最大时间为8秒):

一、 设置套接字接收超时

setsockopt可以设置套接字的属性,其中包括接收超时时间。参考代码如下
        struct timeval tv; // 描述时间的结构体变量
        ……
        tv.tv_sec = 8;
        tv.tv_usec = 0;
        setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
        ……

二、 多路IO复用select

select 函数通常被用来实现多路IO复用,同样可以利用它来实现超时处理。参考代码如下:
        struct timeval tv; // 描述时间的结构体变量
        fdset rdfds; // 定义读描述符集合
        ……
        tv.tv_sec = 8;
        tv.tv_usec = 0;
        FD_ZERO(&rdfds);
        FD_SET(sockfd, &rdfds);
        if (select(sockfd+1, &rdfds, NULL, NULL, &tv) == 0)
        {
                超时处理
        }
        ……

三、 设置定时器

这种方法的原理是在从套接字接收数据之前先设置8秒钟的定时器。如果8秒钟内没有数据到来,内核产生的SIGALRM信号会中断当前的读操作。我们知道设置信号捕捉函数可以用signal函数或是sigaction函数。但这里只能使用sigaction函数,因为signal设置的信号处理函数执行完后会重新执行被中断的操作。参考代码如下:

void handler(int signo) // 自定义SIGALRM信号处理函数
        {
                return;
        }
        struct sigaction act; // 描述信号行为的变量
        ……
        sigaction(SIGALRM, NULL, &act); // 获取SIGALRM信号的属性
        act.sa_handler = handler; // 设置SIGALRM信号的处理函数
        act.sa_flags &= ~SA_RESTART; // 关闭重启被中断操作的选项
        sigaction(SIGALRM, &act, NULL); // 设置SIGALRM信号的属性
        alarm(8); // 设置8秒钟的定时器
        ……

四、通过设置定时器(timer),捕捉SIGALRM信号实现超时:

在介绍编程技巧之前需要先介绍两个函数:
alarm()和signal()

alarm:
 函数原型:
unsigned int alarm(unsigned int seconds);

此函数作用是为信号设置闹钟,即设置一个等待信号到来的时间。
signal:
  函数原型:
sighandler_t signal(int signum,sighandler_t handler);

此函数用于信号的注册,只有信号被注册后,才能够被捕捉。

返回值:先前的信号处理配置(成功)SIG_ERR(失败)
参数signum:信号类型:例如SIGINT SIGUSR1 SIGURT SIGALRM等
参数handler:此参数是一个函数指针,指向信号处理函数。

 编程举例如下:
#include “sock.h”//自定义头文件,里面封装了socket()bind()                                    等函数的出错处理

#define MAXSIZE 80

static int connfd;

void usage(const char *info)
{
 printf(“Usage: %s \n”, info);
 exit(0);
}

void catch_sig(int sig)//信号处理函数
{
 if(sig == SIGALRM){
  printf(“time out.\n”);
  exit(0);
 }
}

void *count(void *arg)//为方便观察,此函数在程序执行期间用于                         一直读秒
{
 int second = 0;
 while(1){
  sleep(1);
  printf(“second: %u\n”, ++second);
 }
}

int main(int argc, char **argv)
{
 if(argc != 2)
  usage(argv[0]);

 pthread_t tid;
 pthread_create(&tid, NULL, count, NULL);//创建一个线                                                  程来读秒
/定义一个网络套接字,并绑定,与普通网络编程一样///
 int sockfd;
 sockfd = Socket(AF_INET, SOCK_STREAM, 0);

 int on = 1;
 Setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on);

struct sockaddr_in srvaddr, cliaddr;
 bzero(&srvaddr, sizeof srvaddr);
 srvaddr.sin_family = AF_INET;
 srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);
 srvaddr.sin_port = htons(atoi(argv[1]));

 Bind(sockfd, (struct sockaddr *)&srvaddr, sizeof srvaddr);
//
 Listen(sockfd, 5);

 socklen_t len;
 char recv[MAXSIZE];

 len = sizeof cliaddr;
 bzero(&cliaddr, len);

 connfd = Accept(sockfd, (struct sockaddr *)&cliaddr, &len);

 printf(“new connection: %s:%hu\n”,
  inet_ntoa(cliaddr.sin_addr),
  ntohs(cliaddr.sin_port));

 
 signal(SIGALRM, catch_sig);

 while(1){
  bzero(recv, MAXSIZE);
  alarm(5);           //等待五秒,五秒过后数据未到,发送SIGALRM
  Read(connfd, recv, MAXSIZE);
  printf(“%s\n”, recv);
 }

 return 0;
}

此函数功能是,等待客户端数据到来,如果五秒后未收到数据,程序退出。

虽然我们是以套接字的读操作为例,实际上在很多类似的情况中,大家都可以酌情采取上面介绍的方法。巧妙的运用所学知识会让你的程序更加灵活和人性化。
 


版权声明:本文为hellomxj1原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/hellomxj1/article/details/18605863