首先是在I/O操作上设置超时,三种方法;read和write这两个函数的三个变体:recv和send运行通过第四个参数从进程到内核传递标志:readvhe writev允许指定往其中输入数据或从其中输出数据的缓冲区向量:recvmsg和sendmsg结合了其他I/O函数的所有特性,并具备接受和发送辅助数据的新能力。
套接字设置超时
- 使用信号处理函数alarm,不过这样会涉及到信号处理函数的问题,同时还有可能会引起程序中其他alarm函数的处理
- 使用select函数,在这个函数的最后一个参数中可以设置时间超时
- 使用比较新颖的超时套接字选项SO_RCVTIMEO和SO_SENDTIMEO,属于套接字选项中的内容。并非所有实现都支持这两个套接字选项。
以上这三个技术都适用于输入和输出操作(read、write及其注入recvfrom、sendto之类的变体)。但是TCP内置的connect函数超时默认为75s。select可用来在connect函数上设置超时的先决条件是相应的套接字处于非阻塞模式,而上述的两个套接字选项对connect并不适用,前两种技术适用于任何技术,第三个技术适用于套接字描述符。
一:使用SIGALRM为connect设置超时——1.本技术总能减少connect中断超时期限,但是无法延长内核现有的超时,Berkeley内核connect通常值为75s,如果我们指定一个比75s小的值可以实现,但是如果指定比75s大的值,到75s就返回。
//自定义signal函数 typedef void Sigfunc(int); /* for signal handlers */ Sigfunc *signal1(int signo, Sigfunc *func) { struct sigaction act,oact; act.sa_handler = func; sigemptyset(&act.sa_mask); act.sa_flags = 0; /*标志清零*/ if (signo == SIGALRM)/*被SIGALRM信号中断的系统调用不重新启动*/ { #ifdef SA_INTERRUPT act.sa_flags|=SA_INTERRUPT;//SunOS 4.x #endif // SA_INTERRUPT } else { #ifdef SA_RESTART act.sa_flags |= SA_RESTART;//SVR,4.4BSD #endif // SA_RESTART } if (sigaction(signo, &act, &oact) < 0) return SIG_ERR; return oact.sa_handler; } static void connect_alarm(int signo); int connect_timeo(int sockfd, const SA *saptr, socklen_t salen, int nsec) { Sigfunc *sigfunc; int n; sigfunc = Signal1(SIGALRM, connect_alarm); /*如果此前进程已经设置过报警时钟,并且未还未超时,输出错误信息*/ if (alarm(nsec) != 0) err_msg("connect_timeo: alarm was already set"); if ((n = connect(sockfd, saptr, salen)) < 0) { /*如果调用被中断,就关闭套接字,防止三路握手继续进行*/ close(sockfd); if (errno == EINTR) errno = ETIMEDOUT; } /*关闭报警时钟*/ alarm(0); /*恢复之前的信号处理函数*/ Signal1(SIGALRM, sigfunc); return n; } static void connect_alarm(int signo) { return;//just interrupt connect() }
二.使用SIGALRM为recvfrom设置超时
#include "unp.h" static void sig_alrm(int); void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) { int n; char sendline[MAXLINE], recvline[MAXLINE + 1]; Signal(SIGALRM, sig_alrm);//返回sig_alarm函数指针 while (Fgets(sendline, MAXLINE, fp) != NULL) { Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen); alarm(5);//recvfrom之前设置5s超时 if ( (n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL)) < 0) { if (errno == EINTR)//调用被信号处理函数中断 fprintf(stderr, "socket timeout "); else err_sys("recvfrom error"); } else //读到来自服务器的文本 { alarm(0);//关掉报警器时钟 recvline[n] = 0; /* null terminate */ Fputs(recvline, stdout); } } } static void sig_alrm(int signo)//中断被阻塞的recvfrom() { return; /* just interrupt the recvfrom() */ }
三.使用select为recvfrom设置超时
//本函数不执行读操作,他只是等待给定的描述符变为可读,因此本函数适用于 //任何类型的套接字,TCP or UDP int readable_timeo(int fd,int sec) { fd_set rset; struct timeval tv; FD_ZERO(&rset); FD_SET(fd,&rset); tv.tv_sec =sec; tv.tv_usec =0; return (select(fd+1,&rset,NULL,NULL,&tv)); } void dg_cli(FILE*fp,int sockfd,const struct sockaddr*pservaddr,socklen_t servlen) { int n; char sendbuff[MAXLEN]; char recvbuff[MAXLEN+1]; while(fgets(sendbuff,MAXLEN,fp)!=NULL) { sendto(sockfd,sendbuff,strlen(sendbuff),0,pservaddr,servlen); if(readable_timeo(sockfd,5)==0)//设置超时等待5秒 { fprintf(stderr,"socket timeout "); } else//readable_timeo返回正值的时候 { if((n=recvfrom(sockfd,recvbuff,MAXLEN,0,NULL,NULL))<=0) { printf("recvfrom error "); return ; } recvbuff[n]='