摘要:
本文介绍在套接字的I/O操作上设置超时的三种方法。
图片可能有点宽,看不到的童鞋可以点击图片查看完整图片。。
1 调用alarm
使用SIGALRM为connect设置超时
设置方法:
- 监听SIGALRM信号,
- 设置sig_alrm处理函数,
- 在阻塞函数前调用alarm函数设置超时时间,
- 正常返回后,重置超时事件为0
void handle_msg(int sockfd) { char sendbuf[BUFSIZE]; char recvbuf[BUFSIZE]; signal(SIGALRM, sig_alrm); //监听SIGALRM信号 while(1) { memset( sendbuf, ‘\0‘, BUFSIZE ); memset( recvbuf, ‘\0‘, BUFSIZE ); printf("%s", "send msg:"); gets(sendbuf); if (strlen(sendbuf) > 0) send(sockfd,sendbuf,strlen(sendbuf),0); if ( !strcmp(sendbuf, "exit")) break; alarm(5); //设置超时事件为5s,同时设置服务器回射前sleep 10秒,以让recv函数超时 if (recv(sockfd,recvbuf,BUFSIZE,0) > 0) { alarm(0); printf("recv back:%s\n\n", recvbuf); } else { if (errno == EINTR) fprintf(stderr, "socket timeout\n"); else fprintf(stderr, "receive error\n"); } } close( sockfd ); return; } static void sig_alrm(int signo) { fprintf(stderr, "recv SIGALRM, return.\n"); return; }
运行截图:
虽然设置了SIGALRM信号处理函数,但是如图所示,本例依然可以等待读取回射信息,因为信号处理函数里只是return。
2 使用select阻塞等待I/O
设置方法:
使用select的内置时间限制,阻塞在select代替recv函数的阻塞。
void handle_msg(int sockfd) { char sendbuf[BUFSIZE]; char recvbuf[BUFSIZE]; while(1) { memset( sendbuf, ‘\0‘, BUFSIZE ); memset( recvbuf, ‘\0‘, BUFSIZE ); printf("%s", "send msg:"); gets(sendbuf); if (strlen(sendbuf) > 0) send(sockfd,sendbuf,strlen(sendbuf),0); if ( !strcmp(sendbuf, "exit")) break; if (readable_timeo(sockfd, 5) == 0) { fprintf(stderr, "socket timeout\n"); } else{ recv(sockfd,recvbuf,BUFSIZE,0); printf("recv back:%s\n\n", recvbuf); } } close( sockfd ); return; } 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); }
运行截图:
由运行截图可以看到,超时警告运行正常。
需要注意一点的是,虽然客户端在超时之后继续发送消息,但是服务器回射的消息(hello world)依然被接收,这导致我们再次发送消息时,从缓冲区中读出了延迟收到的hello world。
这里只是演示超时技术,因此对此并不做进一步处理。
3 使用SO_RCVTIMEO套接字选项
使用SO_RCVTIMEO套接字选项为recv设置超时
设置方法:
- 使用setsockopt函数对套接字进行设置
- 一旦设置了某个描述符,其超时设置将应用于该描述符上的所有读操作
- SO_RCVTIMEO仅用于读操作,SO_SNDTIMEO仅用于写操作,两者都不能用于为connect设置超时
- 如果套接字超时,被阻塞的函数将返回一个EWOULDBLOCK错误
void handle_msg(int sockfd) { char sendbuf[BUFSIZE]; char recvbuf[BUFSIZE + 1]; int n; struct timeval tv; tv.tv_sec = 5; tv.tv_usec = 0; setsockopt( sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv) ); while(1) { memset( sendbuf, ‘\0‘, BUFSIZE ); memset( recvbuf, ‘\0‘, BUFSIZE ); printf("%s", "send msg:"); gets(sendbuf); if (strlen(sendbuf) > 0) send(sockfd,sendbuf,strlen(sendbuf),0); if ( !strcmp(sendbuf, "exit")) break; if ( (n=recv(sockfd,recvbuf,BUFSIZE,0)) < 0 ) { if (errno == EWOULDBLOCK) { fprintf(stderr, "socket timeout\n"); continue; } else fprintf(stderr, "recv error"); } else{ printf("recv back:%s\n\n", recvbuf); } } close( sockfd ); return; }
运行截图:
可以看到,超时警报成功运行,但依然有上一例中的延迟接收的情况发生,不作处理。
示例源码上传到了github上,地址:https://github.com/zs634134578/UNP/tree/tryTimeout
参考资料:
《UNIX网络编程 卷1:套接字联网API(第3版)》