原文连接:https://blog.csdn.net/daiyudong2020/article/details/50675219
一.基本概念(摘自:《unix网络编程》卷1 14.2 套接字超时)
在涉及套接字的I/O操作上设置超时的方法有以下三种
(1)调用alarm,它在指定超时期满时产生SIGALARM。这个方法涉及信号处理,而信号处理在不同的实现上存在差异,而且可能干扰进程中现有的alarm调用。
(2)在select中阻塞等待I/O(select有内置的时间限制),以此替代直接阻塞在read或write调用上。
(3)使用SO_RECVTIMEO和SO_SNDTIMEO套接字选项
上述三个技术都适用于输入输出操作(例如read、write及其诸如recvfrom、sendto之类的变体),不过我们依然期待可用于connect的技术,因为TCP内置的connect超时相当长(典型值为75秒钟)。select可用来在connect上设置超时的先决条件是相应的套接字是处于非阻塞模式,而那两个套接字选项对connect并不适用。我们还指出,前两个技术适用于任何描述符,而第三个技术仅仅适用于套接字描述符。
注:阻塞模式下,当服务器连接不上时,通过命令 “netstat -tlnap|grep SENT” 可以看到客户端会处于SYN_SENT状态,直到connect超时
二.非阻塞模式socket
1.connect不需要考虑超时问题,立即返回一个错误EINPROGRESS,可通过检测fd是否可用判断连接是否建立完成
2.read不需要考虑超时问题,立即返回
3.write不需要考虑超时问题,立即返回
三.阻塞模式socket
1.connect需要考虑超时问题,否则当连接IP不可达的情况下,需要等待很长一段时间(默认时长)
2.read需要考虑超时问题,可通过setsockopt设置SO_RECVTIMEO选项
3.write需要考虑超时问题,可通过setsockopt设置SO_SNDTIMEO选项
四.带超时的connect,适用于阻塞模式与非阻塞模式socket(本质上是非阻塞connect)
附上测试代码,可通过更改main中的IP和端口进行测试,读懂这段代码需要了解select与getsockopt函数
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int connect_nonb(int sockfd, const struct sockaddr *addr, socklen_t addrlen, int nsec)
{
int flags, n, error;
socklen_t len;
fd_set rset, wset;
struct timeval tval;
/* 调用fcntl把套接字设置为非阻塞 */
flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
/* 发起非阻塞connect。期望的错误是EINPROGRESS,表示连接建立已经启动但是尚未完成 */
error = 0;
if ( (n = connect(sockfd, addr, addrlen)) < 0)
if (errno != EINPROGRESS)
return(-1);
/* 如果非阻塞connect返回0,那么连接已经建立。当服务器处于客户端所在主机时这种情况可能发生 */
if (n == 0)
goto done; /* connect completed immediately */
/* 调用select等待套接字变为可读或可写,如果select返回0,那么表示超时 */
FD_ZERO(&rset);
FD_SET(sockfd, &rset);
wset = rset;
tval.tv_sec = nsec;
tval.tv_usec = 0;
if ( (n = select(sockfd+1, &rset, &wset, NULL, nsec ? &tval : NULL)) == 0) {
close(sockfd); /* timeout */
errno = ETIMEDOUT;
return(-1);
}
/* 检查可读或可写条件,调用getsockopt取得套接字的待处理错误,如果建立成功,该值将为0 */
if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
len = sizeof(error);
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
return(-1); /* Solaris pending error */
} else {
perror("select error: sockfd not set");
exit(1);
}
/* 恢复套接字的文件状态标志并返回 */
done:
fcntl(sockfd, F_SETFL, flags); /* restore file status flags */
if (error) {
close(sockfd); /* just in case */
errno = error;
return(-1);
}
return(0);
}
int main()
{
// socket
struct sockaddr_in servaddr;
short port = 9999;
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr("113.107.231.211");
servaddr.sin_port = htons(port);
int timeout = 3;
if (connect_nonb(sockfd, (sockaddr *) &servaddr, sizeof(sockaddr_in), 2) < 0) {
perror("connect fail: ");
return(-1);
}
printf("connect success!\n");
return 0;
}
参考:《unix网络编程》卷1