TCP套接字连接是否具有“保持活动”功能?

115

我听说过HTTP keep-alive,但现在我想要和远程服务器建立一个套接字连接。
那么这个套接字连接会一直保持打开状态吗?还是类似于HTTP keep-alive有与之关联的超时限制?

注:HTTP keep-alive指的是在一次TCP连接中可以发送多个HTTP请求和响应,而不需要为每个请求和响应创建新的连接。

2
只是为了确认,“http keepalive”通常与socket keepalive没有关系,它涉及到HTTP/1.1的连接保持功能以便进行进一步的请求。它仅与TCP keepalive相关,因为它需要检测到断开的TCP连接(或通常仅将套接字保持打开一段有限时间)。 - eckes
8个回答

179
TCP套接字连接是否有“保持活动”?
简短回答是,通过TCP Keep-Alive会强制执行超时,所以套接字不会永远保持打开状态,但可能在几小时后超时。如果您想在机器上配置Keep-Alive超时时间,请参阅下面的“更改TCP超时”部分。否则,请阅读本答案的其余部分,了解TCP Keep-Alive的工作原理。
介绍
TCP连接由两个套接字组成,一个在连接的每一端。当一方希望终止连接时,它发送一个FIN数据包,另一方确认并关闭它们的套接字。然而,在此之前,双方将无限期地保持其套接字打开。这留下了一种可能性,即一方可能关闭其套接字(无论是故意还是由于某些错误),而没有通过FIN通知另一端。为了检测到这种情况并关闭陈旧的连接,使用TCP Keep Alive进程。
保持活动过程
有三个可配置属性决定Keep-Alives的工作方式。在Linux上,它们是:
tcp_keepalive_time 默认7200秒
tcp_keepalive_probes 默认9
tcp_keepalive_intvl 默认75秒
该过程的工作方式如下:
客户端打开TCP连接
如果连接在tcp_keepalive_time秒内保持静默,则发送单个空ACK数据包。
服务器是否响应了自己的相应ACK?
否:等待tcp_keepalive_intvl秒,然后发送另一个ACK。
重复此过程,直到已发送的ACK探针数量等于tcp_keepalive_probes。
如果在此时没有收到响应,则发送RST并终止连接。
是:返回到步骤2
大多数操作系统默认启用此过程,因此一旦其他端未响应超过2小时11分钟(7200秒+75*9秒),则定期删除死亡TCP连接。
注意事项
2小时默认值

默认情况下,进程要等待两个小时的空闲连接才会启动,因此过期的TCP连接可能会在被修剪之前持续存在很长一段时间。这对于诸如数据库连接之类的昂贵连接尤其有害。

Keep-Alive是可选的

根据RFC 1122 4.2.3.6,响应和/或中继TCP Keep-Alive数据包是可选的:

实现者可以在其TCP实现中包括“keep-alives”,尽管这种做法并不普遍接受。如果包括了保持活动状态,则应用程序必须能够打开或关闭每个TCP连接的保持活动状态,并且它们必须默认为关闭。

...

非常重要的一点是,不包含数据的ACK段不能由TCP可靠地传输。

原因是Keep-Alive数据包不包含数据,不是必需的,并且如果过度使用,可能会堵塞互联网管道。

然而,在实践中,我的经验是随着带宽变得更便宜,这种担忧随着时间的推移而减少;因此,Keep-Alive数据包通常不会被丢弃。例如,Amazon EC2文档间接支持Keep-Alive,因此如果您使用AWS托管,则可能安全地依赖Keep-Alive,但结果可能有所不同。

更改TCP超时

每个套接字

2022年更新: 显然,从Java 11开始,您可以在Java TCP Socket本身上设置这些内容。

不幸的是,由于TCP连接在操作系统级别上进行管理,旧版本的Java不支持在java.net.Socket等每个套接字级别上配置超时。我发现了一些尝试3使用Java Native Interface(JNI)创建调用本机代码以配置这些选项的Java套接字,但没有一个得到广泛的社区采用或支持。

相反,您可能需要将配置应用于整个操作系统。请注意,此配置将影响运行在整个系统上的所有TCP连接。

Linux

当前配置的TCP Keep-Alive设置可以在以下位置找到

  • /proc/sys/net/ipv4/tcp_keepalive_time
  • /proc/sys/net/ipv4/tcp_keepalive_probes
  • /proc/sys/net/ipv4/tcp_keepalive_intvl

您可以像这样更新其中任何一个:

# Send first Keep-Alive packet when a TCP socket has been idle for 3 minutes
$ echo 180 > /proc/sys/net/ipv4/tcp_keepalive_time
# Send three Keep-Alive probes...
$ echo 3 > /proc/sys/net/ipv4/tcp_keepalive_probes
# ... spaced 10 seconds apart.
$ echo 10 > /proc/sys/net/ipv4/tcp_keepalive_intvl

这些更改不会在重新启动后保持。要进行持久更改,请使用sysctl

sysctl -w net.ipv4.tcp_keepalive_time=180 net.ipv4.tcp_keepalive_probes=3 net.ipv4.tcp_keepalive_intvl=10

Mac OS X

您可以使用sysctl查看当前配置的设置:

$ sysctl net.inet.tcp | grep -E "keepidle|keepintvl|keepcnt"
net.inet.tcp.keepidle: 7200000
net.inet.tcp.keepintvl: 75000
net.inet.tcp.keepcnt: 8

值得注意的是,Mac OS X以毫秒为单位定义keepidlekeepintvl,而Linux则使用秒。
可以使用sysctl设置这些属性,这将使这些设置在重新启动后保持不变。
sysctl -w net.inet.tcp.keepidle=180000 net.inet.tcp.keepcnt=3 net.inet.tcp.keepintvl=10000

或者,您可以将它们添加到 /etc/sysctl.conf 中(如果该文件不存在,则创建该文件)。

$ cat /etc/sysctl.conf
net.inet.tcp.keepidle=180000
net.inet.tcp.keepintvl=10000
net.inet.tcp.keepcnt=3

Windows

我没有Windows机器进行确认,但你应该能在注册表的下面找到相应的TCP Keep-Alive设置:

\HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\TCPIP\Parameters

1. 要获取更多信息,请参见man tcp

2. 此数据包通常称为“Keep-Alive”数据包,但在TCP规范中,它只是一个普通的ACK数据包。像Wireshark这样的应用程序可以通过元分析套接字上的前期通信所包含的序列和确认号来标识它为“Keep-Alive”数据包。

3. 我通过基本的谷歌搜索找到了一些示例,如lucwilliams/JavaLinuxNetflonatel/libdontdie


2
非常有帮助,谢谢!还有一点:对于Windows系统,需要重新启动才能使新的KeepAliveTime值生效。 - geld0r
在AIX上,可以使用“$ no -a | grep tcp_keep”命令查询当前TCP Keep-Alive设置。 - Jarek Przygódzki
当创建TCP套接字时,可以使用除默认值以外的keepalive值进行创建。 有人知道如何查找正在运行的进程的keepalive值吗?我已经在/proc/PID/net/netstat中查找过了,但是那里找到的值并不实用,或者至少我找不到解释。 - Jared Still
在我看来,“alive”这个词是具有误导性的。毕竟:断言一个连接是“alive”只意味着您已经进行了某种形式的成功通信(过去时!),但这并不会对任何或所有后续通信都提供保证(现在/未来时)。因此,在TCP层面和应用层面都没有做这个的价值,您最好决定采取强大的_失败重试_或_崩溃我的应用程序_方法,而不是其他人可能正在寻找的任何虚假安全感。 - Gerard van Helden
TCP连接通过交换FIN而不是RST来终止。 RST是一种异常关闭,不被确认。 - user207421
看起来Java 11现在具有扩展的套接字选项:https://dev59.com/V33aa4cB1Zd3GeqPhLgZ - dbep

79

TCP套接字一直保持打开状态,直到它们被关闭。

话虽如此,如果不发送数据,很难检测到一个破损的连接(比如路由器出现故障等情况),因此大多数应用程序会定期执行某种ping/pong响应,以确保连接仍然处于活动状态。


5
这是个好主意。虽然不一定非要这么做,但如果你不这样做,可能直到有人真正想要使用时,才会发现链接已经失效了。这可能是好事也可能不重要,具体取决于你实际想要达到的目标。 - Matthew Scharley
1
@Pacerier: 这取决于协议,因为它完全取决于协议,但对于需要一个字面上的 "PING" 和 "PONG" 命令的基于文本的协议来说是相当典型的。 - Matthew Scharley
4
@MatthewScharley说:"Ping Pong"已经在标准TCP实现中为我们实现了,被称为“keep-alive”(请参见此问题的其他流行答案)。有没有实现应用级别的原因呢? - Tim Cooper
7
@TimCooper:实际上并非如此。正如我在其他答案的评论中所强调的那样,TCP实现对于大多数应用级需求并不实用。你不能按需发送它,对于大多数操作系统而言,TCP保持活动连接的超时时间只能在系统范围内进行配置,并且设置得太高,通常对应用程序没有太大用处。 - Matthew Scharley
21
@Tim,应用程序级别的保持连接机制存在的原因是TCP标准建议将保持连接定时器设置为两个小时以上。我从未见过没有流量的TCP连接能够存活超过这段时间。因此,默认情况下TCP的保持连接机制是无用的。 - Robert
显示剩余4条评论

57
你正在寻找SO_KEEPALIVE套接字选项。 Java Socket API通过setKeepAlivegetKeepAlive方法向应用程序公开“保持活动状态”。
编辑:SO_KEEPALIVE在操作系统网络协议栈中实现,而不发送任何“真实”数据。保持活动的间隔时间取决于操作系统,并且可以通过内核参数进行调整。
由于没有发送数据,因此SO_KEEPALIVE只能测试网络连接的活动性,而不能测试套接字连接的服务的活动性。要测试后者,您需要实现涉及向服务器发送消息并获得响应的内容。

4
如果我调用 setKeepAlive(true) 方法,间隔时间会是多少?同时,Java 是否会在默认间隔时间内持续发送 keep-alive 消息,还是需要在程序中手动实现? - Kevin Boyd
3
SO_KEEPALIVE有一个描述。虽然它是基于协议的选项,但它与OP所需的并不完全一样...尽管每两个小时进行一次操作对应用程序来说并没有太大帮助。 - Matthew Scharley
4
关于 "it must not default to no less than two hours" 的意思是它不能默认为不到两个小时,这是否意味着它可以少于两个小时呢? - Pacerier
1
@MatthewScharley - “你说得对,但那将是实现特定的……”一个保持连接间隔时间如果不少于两个小时,那将是如此无用,以至于很难想象有人会实现它。 - Stephen C
2
@Daniel - 在Java中的替代方法是执行手动保持连接,如上面和其他答案中所提到的。虽然不太美观,但它可能比更改默认值对整个操作系统造成故障的做法更好,这可能会影响系统服务或其他应用程序。 - Stephen C
显示剩余6条评论

36

TCP保活和HTTP保活是非常不同的概念。在TCP中,保活是发送的管理数据包,用于检测失效连接。而在HTTP中,保活意味着持久连接状态。

这来自TCP规范:

仅当连接在一个时间间隔内未收到数据或确认数据包时,才必须发送保活数据包。此时间间隔必须可配置且默认值不得少于2小时。

如你所见,默认的TCP保活间隔对于大多数应用程序来说太长了。您可能需要在应用程序协议中添加保活。


2
您可以修改TCP保持活动间隔以适应您的应用程序。例如:http://msdn.microsoft.com/en-us/library/dd877220%28VS.85%29.aspx - Dan Berindei
@ZZCoder 你能否详细解释一下当你说“在HTTP中,保持活动状态意味着持久连接状态”是什么意思? - Pacerier
1
@Pacerier:在HTTP/1.0中,每个请求/响应都需要重新连接到服务器。对于HTTP/1.1,他们引入了一个Keep-Alive头,可以用来触发服务器在处理完响应后不关闭连接,以便请求更多文件并允许“流水线化”;发送多个请求,然后等待所有数据返回。 - Matthew Scharley
基本上意味着许多HTTP请求将/应该重用相同的TCP连接(这些连接可能也具有keep-alive,但对于HTTP来说并不重要,因此它实质上是一个不同的概念)。 - Igor Čordaš

25
如果你在NAT后面(像现今大多数家庭用户一样),那么外部端口的池子是有限的,这些端口必须在TCP连接之间共享。因此,伪装的NAT会假设如果在某个时间段内没有发送数据,则该连接已被终止。

这和其他类似问题(在两个端点之间的任何地方)可能意味着如果你在一个合理的空闲期后尝试发送数据,连接将不再“工作”。然而,直到你尝试发送数据之前,你可能还没有发现这一点。

使用keepalives既可以减少连接在中途被中断的机会,也可以让你更早地发现连接出现问题。


啊!你在这里提出了一个很好的观点,那就是你还必须考虑到可能会阻碍连接操作的中间因素,例如NAT路由器等等... - Kevin Boyd
4
这是一个好观点,并提醒我们要牢记的不仅仅是我们直接实现的内容。另外,旋转木马! - Matthew Scharley
请注意,点对点文件共享既会使用大量端口,同时也会产生许多僵尸连接,这样会增加 NAT 删除空闲连接的可能性。 - Artelius
4
TCP连接的标识不一定需要使用不同的外部(源)端口,只要目标IP不同即可。TCP连接的标识有四个元素:源IP、源端口、目标IP和目标端口。 - Dan Berindei
1
没错,你说得对。我认为真正的原因是NAT有一个固定大小的打开连接表,这是由于内存限制和查找时间所致。 - Artelius
有趣的是,TCP保持活动间隔默认情况下比NAT大,至少在某些配置中是这样。因此,空闲连接可能会通过NAT丢失。 - Paul Stelian

4

以下是一些有关保持连接的补充文献,详细解释了此概念。

http://www.tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO

由于Java不能控制实际的保持连接时间,如果您正在使用Linux内核(或基于proc的操作系统),则可以使用示例来更改它们。


0
在JAVA Socket中,TCP连接是在操作系统级别上进行管理的,java.net.Socket没有提供任何内置函数来设置每个套接字级别的keepalive数据包超时时间。但是我们可以为Java Socket启用keepalive选项,但默认情况下需要2小时11分钟(7200秒)才能处理过期的TCP连接。这会导致连接在清除之前可用的时间非常长。因此,我们找到了一些解决方案,使用Java Native Interface(JNI)调用本地代码(c ++)来配置这些选项。 Windows操作系统 在Windows操作系统中,keepalive_time和keepalive_intvl是可配置的,但tcp_keepalive_probes无法更改。默认情况下,当初始化TCP套接字时,将keep-alive超时设置为2小时,keep-alive间隔设置为1秒。通过KeepAliveTime注册表设置可以控制全局保持活动状态超时的默认系统值,该设置以毫秒为单位。
在Windows Vista及更高版本中,保持活动状态探测(数据重传)的数量设置为10,无法更改。
在Windows Server 2003、Windows XP和Windows 2000上,保持连接探测的默认设置为5。保持连接探测的数量是可控的。对于Windows Winsock IOCTLs库用于配置tcp-keepalive参数。
int WSAIoctl(
    SocketFD, // descriptor identifying a socket
    SIO_KEEPALIVE_VALS, // dwIoControlCode
    (LPVOID) lpvInBuffer, // pointer to tcp_keepalive struct (DWORD)
    cbInBuffer, // length of input buffer
    NULL, // output buffer
    0, // size of output buffer
    (LPDWORD) lpcbBytesReturned, // number of bytes returned
    NULL, // OVERLAPPED structure
    NULL // completion routine
);

Linux操作系统

Linux内置支持保持连接,需要在启用TCP/IP网络才能使用。程序必须使用setsockopt接口请求其套接字的保持连接控制。

int setsockopt(int socket, int level, int optname, const void *optval, socklen_t optlen)

每个客户端套接字将使用java.net.Socket创建。每个套接字的文件描述符ID将使用Java反射来检索。


7200秒是两个小时,这是最小默认超时时间。我不知道你从哪里得到了11分钟。 - user207421

0

针对Windows,根据Microsoft文档进行设置

  • KeepAliveTime(REG_DWORD,毫秒,默认情况下未设置,即7,200,000,000 = 2小时)- 类似于tcp_keepalive_time
  • KeepAliveInterval(REG_DWORD,毫秒,默认情况下未设置,即1,000 = 1秒)- 类似于tcp_keepalive_intvl
  • 自Windows Vista以来,没有类似于tcp_keepalive_probes的模拟,该值固定为10且无法更改

7200000000毫秒是2000小时,而不是2。 - Ant
@Ant 不是这样的。你把毫秒和秒搞混了,而且你连这个都没搞对。 - user207421
7200000000毫秒/1000 = 7200000秒 7200000秒/60 = 120000分钟 120000分钟/60 = 2000小时 我错在哪里了? - Ant

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接