为connect、send、recv设置超时时间

目录
  1. 非阻塞connect
  2. 为send/recv设置超时时长

非阻塞connect

  1. 调用fcntl设置套接字为非阻塞
  2. 发起非阻塞connect。期望的错误是EINPROCESS,表示连接已经启动但是尚未建立。
  3. 如果非阻塞connect返回0,表示连接已经建立,当客户端和服务端位于同一主机时连接很快,有可能出现这种情况
  4. 调用select设置超时时间,然后等待套接字变为可读或可写。当select返回0时,表示connect超时;
  5. 如果描述符变为可读或可写,由于错误情况下套接字也是可读可写的,所以要区分这种情况下是否真的连接成功,这时我们调用getsockopt取得套接字的待处理错误
  6. 还原套接字的阻塞状态并返回

注意:
使用 getsockopt 函数检查错误
getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len)
在 sockfd 都是可读和可写的情况下,我们使用 getsockopt 来检查连接
是否出错。但这里有一个可移植性的问题。
如果发生错误,getsockopt 源自 Berkeley 的实现将在变量 error 中
返回错误,getsockopt 本身返回0;然而 Solaris 却让 getsockopt 返回 -1,
并把错误保存在 errno 变量中。所以在判断是否有错误的时候,要处理
这两种情况。

具体源程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
int conn_nonb(int sockfd, const struct sockaddr_in *saptr, socklen_t salen, int nsec)
{
int flags, n, error, code;
socklen_t len;
fd_set wset;
struct timeval tval;
flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
error = 0;
if ((n == connect(sockfd, saptr, salen)) == 0) {
goto done;
} else if (n < 0 && errno != EINPROGRESS){
return (-1);
}
/* Do whatever we want while the connect is taking place */
FD_ZERO(&wset);
FD_SET(sockfd, &wset);
tval.tv_sec = nsec;
tval.tv_usec = 0;
if ((n = select(sockfd+1, NULL, &wset,
NULL, nsec ? &tval : NULL)) == 0) {
close(sockfd); /* timeout */
errno = ETIMEDOUT;
return (-1);
}
if (FD_ISSET(sockfd, &wset)) {
len = sizeof(error);
code = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len);
/* 如果发生错误,Solaris实现的getsockopt返回-1,
* 把pending error设置给errno. Berkeley实现的
* getsockopt返回0, pending error返回给error.
* 我们需要处理这两种情况 */
if (code < 0 || error) {
close(sockfd);
if (error)
errno = error;
return (-1);
}
} else {
fprintf(stderr, "select error: sockfd not set");
exit(0);
}
done:
fcntl(sockfd, F_SETFL, flags); /* restore file status flags */
return (0);
}

为send/recv设置超时时长

1
2
3
struct timeval timeout={3,0};//3s
int ret=setsockopt(sock_fd,SOL_SOCKET,SO_SNDTIMEO,(const char*)&timeout,sizeof(timeout));
int ret=setsockopt(sock_fd,SOL_SOCKET,SO_RCVTIMEO,(const char*)&timeout,sizeof(timeout));

如果recv或者send返回-1,而且errno为EAGIN表示超时。具体可以使用man recv查看说明如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
RECV(2) BSD System Calls Manual RECV(2)
NAME
recv, recvfrom, recvmsg -- receive a message from a socket
...
ERRORS
The calls fail if:
[EAGAIN] The socket is marked non-blocking, and the receive
operation would block, or a receive timeout had been
set, and the timeout expired before data were
received.

参考链接:
非阻塞connect的实现

本站总访问量