从linux内核角度看怎么设置connect超时
Posted linux大本营
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从linux内核角度看怎么设置connect超时相关的知识,希望对你有一定的参考价值。
linux服务器开发相关视频解析:
linux多线程之epoll原理剖析与reactor原理及应用
10道经典面试题的剖析, 技术方向如何决定职业方向
c/c++ linux服务器开发免费学习地址:c/c++ linux后台服务器高级架构师
我们在编写网络程序时,通常需要连接其他服务端(如微服务之间的通信),这时就需要通过调用 connect 函数来连接服务端。但我们发现 connect 函数并没有提供超时的设置,而在 Linux 系统中,connect 的默认超时时间为75秒。所以,在连接不上服务端的情况下,我们需要等待75秒,这对我们不能接受的。
通过 SO_SNDTIMEO 设置 connect 超时时间
虽然 connect 系统调用没有提供超时的设置,但我们通过查阅 Linux 内核代码可以发现,connect 系统调用的超时时间可以通过 SO_SNDTIMEO 参数来设定的,而 SO_SNDTIMEO 参数可以通过 setsockopt 系统调用来设置,如下代码:
struct timeval tv;
tv.tv_sec = 1; /* 设置1秒超时 */
tv.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
一般来说,SO_SNDTIMEO 参数是用来设置 socket 的发送超时时间,为什么在 Linux 中还能设置 connect 的超时时间呢?我们来查看一下 connect 系统调用的实现:
// 调用链: connect() -> sys_connect() -> inet_stream_connect()
int inet_stream_connect(struct socket *sock, struct sockaddr * uaddr,
int addr_len, int flags)
{
struct sock *sk = sock->sk;
int err;
long timeo;
lock_sock(sk);
...
switch (sock->state) {
...
case SS_UNCONNECTED:
...
err = -EINPROGRESS;
break;
}
timeo = sock_sndtimeo(sk, flags & O_NONBLOCK); // 获取 connect 超时时间,如果是非阻塞会返回0
if ((1<<sk->state)&(TCPF_SYN_SENT|TCPF_SYN_RECV)) {
// 如果 socket 设置了非阻塞或者 connect 超时了
// 跳到 out 处执行, 并且返回 EINPROGRESS 错误
if (!timeo || !inet_wait_for_connect(sk, timeo))
goto out;
err = sock_intr_errno(timeo);
if (signal_pending(current))
goto out;
}
if (sk->state == TCP_CLOSE)
goto sock_error;
sock->state = SS_CONNECTED;
err = 0;
out:
release_sock(sk);
return err;
...
}
在 inet_stream_connect 函数中,首先调用了 sock_sndtimeo 获取 socket 的 SO_SNDTIMEO 的值,我们来看看 sock_sndtimeo 函数的实现:
static inline long sock_sndtimeo(struct sock *sk, int noblock)
{
return noblock ? 0 : sk->sndtimeo; // 获取socket的SO_SNDTIMEO的值,如果socket被设置了非阻塞,那么返回0
}
sock_sndtimeo 函数只是简单的从 socket 对象中获取 sndtimeo 字段的值,如果 socket 被设置了非阻塞,那么就返回0。
我们接着分析 inet_stream_connect 函数,在获取到 SO_SNDTIMEO 的值后,就调用 inet_wait_for_connect 函数等待 socket 连接返回。返回三种情况:
- 连接成功了。
- 连接超时了。
- 连接被中断了。
如果连接成功,connect 会返回0;如果连接超时,connect 会返回 EINPROGRESS 错误;如果连接被中断,connect 会返回 EINTR 错误。
通过非阻塞与多路复用IO设置 connect 超时时间
从上面的分析可以看到,当把 socket 设置为非阻塞时,connect 系统调用会立刻返回 EINPROGRESS 错误,这时我们可以把 socket 添加到多路复用 IO 中进行监听,并且设置多路复用 IO 的超时时间即可达到设置 connect 超时时间的目的,如下代码:
int connect_timeout(int sockfd, struct sockaddr *serv_addr, int addrlen, int timeout)
{
int flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags|O_NONBLOCK); // 设置为非阻塞
int n = connect(sockfd, serv_addr, sizeof(*serv_addr)); // 连接服务端
if (n < 0) {
if (errno != EINPROGRESS && errno != EWOULDBLOCK)
return -1;
struct timeval tv;
fd_set wset;
tv.tv_sec = timeout/1000;
tv.tv_usec = (timeout - tv.tv_sec*1000)*1000;
FD_ZERO(&wset);
FD_SET(sockfd, &wset); // 把socket添加到select中进行监听
n = select(sockfd + 1, NULL, &wset, NULL, &tv);
if (n < 0) {
return -1; // 出错
} else if (0 == n) {
return 0; // 超时
}
}
fcntl(fd,F_SETFL,flags & ~O_NONBLOCK); // 恢复为阻塞模式
return 1;
}
connect_timeout 函数实现了有超时机制的 connect,其主要步骤有:
- 通过调用 fcntl 函数把 socket 设置为非阻塞。
- 调用 connect 函数进行连接服务端。
- 如果 connect 函数返回 EINPROGRESS 或者 EWOULDBLOCK 错误,表示连接还没有建立,所以此时把 socket添加到 select 中进行监听,并且设置 select 的超时时间。
- 判断 select 的返回值,如果返回值大于0,表示连接成功;如果返回值小于0,表示连接出错;如果反正等于0,表示连接超时。
- 最后把 socket 恢复到阻塞模式。
这种设置 connect 的超时时间的方式比前面设置 SO_SNDTIMEO 值的方式更为通用,因为在非 Linux 系统中,设置 SO_SNDTIMEO 值的方式不一定有效。
以上是关于从linux内核角度看怎么设置connect超时的主要内容,如果未能解决你的问题,请参考以下文章