您的位置:首页 > 理论基础 > 计算机网络

socket超时设置详解(connect/read/write)

2016-02-16 23:44 846 查看
一.基本概念(摘自:《unix网络编程》卷1 14.2 套接字超时)

在涉及套接字的I/O操作上设置超时的方法有以下三种

(1)调用alarm,它在指定超时期满时产生SIGALARM。这个方法涉及信号处理,而信号处理在不同的实现上存在差异,而且可能干扰进程中现有的alarm调用。

(2)在select中阻塞等待I/O(select有内置的时间限制),以此替代直接阻塞在readwrite调用上。

(3)使用SO_RECVTIMEOSO_SNDTIMEO套接字选项

上述三个技术都适用于输入输出操作(例如readwrite及其诸如recvfromsendto之类的变体),不过我们依然期待可用于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函数

-----  g++ connect_nonb.cpp -----

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

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

End;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux socket 网络编程