linux – TCP保持活动参数不被尊重
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了linux – TCP保持活动参数不被尊重,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含6076字,纯文字阅读大概需要9分钟。
内容图文
![linux – TCP保持活动参数不被尊重](/upload/InfoBanner/zyjiaocheng/956/11327310d28744a29fb6c03cde7cebc1.jpg)
我正在试验我的Linux机器上的TCP保持活动,并编写了以下小型服务器:
#include <iostream>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h> // inet_ntop
#include <netinet/tcp.h>
#include <netdb.h> // addrinfo stuff
using namespace std;
typedef int SOCKET;
int main(int argc, char *argv [])
{
struct sockaddr_in sockaddr_IPv4;
memset(&sockaddr_IPv4, 0, sizeof(struct sockaddr_in));
sockaddr_IPv4.sin_family = AF_INET;
sockaddr_IPv4.sin_port = htons(58080);
if (inet_pton(AF_INET, "10.6.186.24", &sockaddr_IPv4.sin_addr) != 1)
return -1;
SOCKET serverSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (bind(serverSock, (sockaddr*)&sockaddr_IPv4, sizeof(sockaddr_IPv4)) != 0 || listen(serverSock, SOMAXCONN) != 0)
{
cout << "Failed to setup listening socket!\n";
}
SOCKET clientSock = accept(serverSock, 0, 0);
if (clientSock == -1)
return -1;
// Enable keep-alive on the client socket
const int nVal = 1;
if (setsockopt(clientSock, SOL_SOCKET, SO_KEEPALIVE, &nVal, sizeof(nVal)) < 0)
{
cout << "Failed to set keep-alive!\n";
return -1;
}
// Get the keep-alive options that will be used on the client socket
int nProbes, nTime, nInterval;
socklen_t nOptLen = sizeof(int);
bool bError = false;
if (getsockopt(clientSock, IPPROTO_TCP, TCP_KEEPIDLE, &nTime, &nOptLen) < 0) { bError = true; }
nOptLen = sizeof(int);
if (getsockopt(clientSock, IPPROTO_TCP, TCP_KEEPCNT, &nProbes, &nOptLen) < 0) {bError = true; }
nOptLen = sizeof(int);
if (getsockopt(clientSock, IPPROTO_TCP, TCP_KEEPINTVL, &nInterval, &nOptLen) < 0) { bError = true; }
cout << "Keep alive settings are: time: " << nTime << ", interval: " << nInterval << ", number of probes: " << nProbes << "\n";
if (bError)
{
// Failed to retrieve values
cout << "Failed to get keep-alive options!\n";
return -1;
}
int nRead = 0;
char buf[128];
do
{
nRead = recv(clientSock, buf, 128, 0);
} while (nRead != 0);
return 0;
}
然后我调整了系统范围的TCP keep alive设置如下:
# cat /proc/sys/net/ipv4/tcp_keepalive_time
20
# cat /proc/sys/net/ipv4/tcp_keepalive_intvl
30
然后我从Windows连接到我的服务器,并运行Wireshark跟踪以查看保持活动的数据包.下图显示了结果.
这使我感到困惑,因为我现在明白保持活动间隔只有在没有收到响应原始保持活动包的ACK时才会起作用(参见我的other question here).所以我希望后续的数据包能够以20秒的间隔(不是30,这就是我们看到的)一致地发送,而不仅仅是第一个.
然后,我调整了系统范围设置如下:
# cat /proc/sys/net/ipv4/tcp_keepalive_time
30
# cat /proc/sys/net/ipv4/tcp_keepalive_intvl
20
这次当我连接时,我在Wireshark跟踪中看到以下内容:
现在我们看到第一个保活包在30秒后发送,但之后的每一个也在30秒发送,而不是前一次运行建议的20!
有人可以解释这种不一致的行为吗?
解决方法:
粗略地说,它应该如何工作是每隔tcp_keepalive_time秒发送一个keepalive消息.如果未收到ACK,则它将探测每个tcp_keepalive_intvl秒.如果在tcp_keepalive_probes之后未收到ACK,则连接将中止.因此,连接最多会中止
tcp_keepalive_time + tcp_keepalive_probes * tcp_keepalive_intvl
没有回应的秒.请参见this内核文档.
我们可以使用netcat keepalive轻松观看这项工作,这是一个允许我们设置tcp keepalive参数的netcat版本(sysctl keepalive参数是默认参数,但它们可以在tcp_sock结构中基于每个套接字覆盖).
首先启动一个侦听端口8888的服务器,其中keepalive_timer设置为5秒,keepalive_intval设置为1秒,keepalive_probes设置为4.
$./nckl-linux -K -O 5 -I 1 -P 4 -l 8888 >/dev/null &
接下来,让我们使用iptables为发送到服务器的ACK数据包引入丢失:
$sudo iptables -A OUTPUT -p tcp --dport 8888 \
> --tcp-flags SYN,ACK,RST,FIN ACK \
> -m statistic --mode random --probability 0.5 \
> -j DROP
这将导致发送到TCP端口8888的数据包仅将ACK标志设置为以0.5的概率丢弃.
现在让我们连接并观察vanilla netcat(它将使用sysctl keepalive值):
$nc localhost 8888
这是捕获:
如您所见,它在收到ACK之后等待5秒钟,然后再发送另一个keepalive消息.如果它在1秒内未收到ACK,则发送另一个探测,如果在4次探测后没有收到ACK,则中止连接.这正是keepalive应该如何工作的.
所以让我们尝试重现你所看到的.让我们删除iptables规则(没有丢失),启动一个新服务器,tcp_keepalive_time设置为1秒,tcp_keepalive_intvl设置为5秒,然后连接客户端.结果如下:
有趣的是,我们看到了你所做的相同行为:在第一次ACK之后,它等待1秒钟发送一个keepalive消息,然后每5秒钟.
让我们重新添加iptables规则来引入丢失以查看它在没有得到ACK的情况下实际等待发送另一个探测的时间(在服务器上使用-K -O 1 -I 5 -P 4):
同样,它从第一个ACK等待1秒钟以发送保持活动消息,但此后它等待5秒钟是否看到ACK,就像keepalive_time和keepalive_intvl都设置为5一样.
为了理解这种行为,我们需要看一下linux内核的TCP实现.我们先来看看tcp_finish_connect:
if (sock_flag(sk, SOCK_KEEPOPEN))
inet_csk_reset_keepalive_timer(sk, keepalive_time_when(tp));
建立TCP连接后,keepalive定时器有效地设置为tcp_keepalive_time,在我们的例子中为1秒.
接下来,让我们看一下tcp_keepalive_timer中如何处理计时器:
elapsed = keepalive_time_elapsed(tp);
if (elapsed >= keepalive_time_when(tp)) {
/* If the TCP_USER_TIMEOUT option is enabled, use that
* to determine when to timeout instead.
*/
if ((icsk->icsk_user_timeout != 0 &&
elapsed >= icsk->icsk_user_timeout &&
icsk->icsk_probes_out > 0) ||
(icsk->icsk_user_timeout == 0 &&
icsk->icsk_probes_out >= keepalive_probes(tp))) {
tcp_send_active_reset(sk, GFP_ATOMIC);
tcp_write_err(sk);
goto out;
}
if (tcp_write_wakeup(sk, LINUX_MIB_TCPKEEPALIVE) <= 0) {
icsk->icsk_probes_out++;
elapsed = keepalive_intvl_when(tp);
} else {
/* If keepalive was lost due to local congestion,
* try harder.
*/
elapsed = TCP_RESOURCE_PROBE_INTERVAL;
}
} else {
/* It is tp->rcv_tstamp + keepalive_time_when(tp) */
elapsed = keepalive_time_when(tp) - elapsed;
}
sk_mem_reclaim(sk);
resched:
inet_csk_reset_keepalive_timer (sk, elapsed);
goto out;
当keepalive_time_when大于keepalive_itvl_时,此代码按预期工作.但是,如果不是,您会看到您观察到的行为.
当初始定时器(建立TCP连接时设置)在1秒后到期时,我们将延长定时器直到经过的时间大于keepalive_time_when.此时我们将发送一个探测器,并将计时器设置为keepalive_intvl_when,即5秒.当此计时器到期时,如果最后1秒没有收到任何信息(keepalive_time_when),我们将发送一个探测器,然后再次将计时器设置为keepalive_intvl_when,并在另外5秒内唤醒,依此类推.
但是,如果我们在计时器到期时收到keepalive_time_内的某些内容,它将使用keepalive_time_when重新安排计时器,因为我们上次收到任何内容后1秒钟.
因此,为了回答你的问题,TCP keepalive的linux实现假设keepalive_intvl小于keepalive_time,但仍然“明智地”工作.
内容总结
以上是互联网集市为您收集整理的linux – TCP保持活动参数不被尊重全部内容,希望文章能够帮你解决linux – TCP保持活动参数不被尊重所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。