动手学习TCP:4种定时器
上一篇中介绍了TCP数据传输中涉及的一些基本知识点。本文让我们看看TCP中的4种定时器。
TCP定时器
对于每个TCP连接,TCP管理4个不同的定时器,下面看看对4种定时器的简单介绍。
- 重传定时器使用于当希望收到另一端的确认。
- 该定时器是用来决定超时和重传的。
- 由于网络环境的易变性,该定时器时间长度肯定不是固定值;该定时器时间长度的设置依据是RTT(Round Trip Time),根据网络环境的变化,TCP会根据这些变化并相应地改变超时时间。
- 坚持定时器(persist)使窗口大小信息保持不断流动,即使另一端关闭了其接收窗口。
- 保活定时器(keepalive)可检测到一个空闲连接的另一端何时崩溃或重启。
- 2MSL定时器测量一个连接处于TIME_WAIT状态的时间。
- 参见"动手学习TCP:TCP特殊状态"中对TIME_WAIT的介绍
下面就介绍一下坚持定时器和保活定时器。
坚持定时器
TCP通过让接收方指明希望从发送方接收的数据字节数(即窗口大小)来进行流量控制。
如果窗口大小为 0会发生什么情况呢?这将有效地阻止发送方传送数据,直到窗口变为非0为止。

但是,由于TCP不对ACK报文段进行确认(TCP只确认那些包含有数据的ACK报文段),如果上图中通知发送方窗口大于0的[ACK]丢失了,则双方就有可能因为等待对方而使连接死锁。接收方等待接收数据(因为它已经向发送方通告了一个非0的窗口),而发送方在等待允许它继续发送数据的窗口更新。
为防止这种死锁情况的发生,发送方使用一个坚持定时器 (persist timer)来周期性地向接收方查询,以便发现窗口是否已增大。这些从发送方发出的报文段称为窗口探查(window probe)。
实验代码
下面通过Python socket实现一个快的发送端和慢的接收端,然后通过Wireshark抓包来看看窗口更新通知和窗口探查。
客户端代码如下,用户输入字符,客户端将用户输入重复1000次然后发送给服务端,通过这种简单的重复来模拟一个快的发送端:
from socket import *
import time HOST = "192.168.56.102"
PORT =
ADDR = (HOST, PORT) client = socket(AF_INET, SOCK_STREAM)
client.connect(ADDR) while True:
input = raw_input() if input:
client.send(input*)
else:
client.close()
break
对于服务端,通过制定一个小的接收BUFFER,以及一个延时来模拟一个慢的接收端:
import sys
from socket import *
import time HOST = "192.168.56.102"
PORT =
BUFSIZ =
ADDR = (HOST, PORT) server = socket(AF_INET, SOCK_STREAM)
print "Socket created"
try:
server.bind(ADDR)
except error, msg:
print 'Bind failed. Error Code : ' + str(msg[]) + ' Message ' + msg[]
sys.exit() server.listen()
print 'Socket now listening'
conn, addr = server.accept() while True:
time.sleep()
try:
data = conn.recv(BUFSIZ)
if data:
print data
else:
conn.close()
break
except Exception, e:
print e
break
在开始运行代码之前还需要进行一些设置,默认情况下接收端的window size很大,实验中很难耗尽。
所以,为了看到实验效果,需要对系统进行一些设置。打开虚拟机中的注册表设置"regedit",然后找到选项"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters",设置"TcpWindowSize"为4096Bytes。
注意,实验结束后,一定要恢复"TcpWindowSize"的原始设置,不然可能会影响正常的网络访问。
关于更多TCP相关的注册表设置,可以参考这个链接。

运行效果
下面运行代码,分别输入两个字符"a"和"b",通过Wireshark可以看到,在进行连接确认的时候,接收端已经给出了我们跟新后的可用窗口4096Bytes。
经过第一轮发送后,接收方的window size减少了1000;当两个数据包都处理完成后,window size又恢复到了4096。

第二轮测试中,发送端发送"1234567890"十个字符,从接收端的最后一个[ACK]包可以看到,最后接收端window size为1393,此次传输到此结束。
过了一段时间,当慢接收端处理完数据之后,接收端会发送窗口更新,通知发送端可以窗口为4096Bytes。

第三轮测试中,发送端发送更多的字符"1234567890987654321",这次接收端的可用窗口就被耗尽了,然后接收端发送一个[TCP ZeroWindow]的通知;这时,发送端停止发送,然后通过发送窗口探查。
当接收端有可用窗口的时候,接收端会发送窗口更新,数据传输继续。
注意,[TCP ZeroWindowProbe]和[TCP ZeroWindowProbeAck]的Seq和Ack号。

糊涂窗口综合症
基于窗口的流量控制方案,会导致一种"糊涂窗口综合症SWS(Silly Window Syndrome)"的状况。
当发送端应用进程产生数据很慢、或接收端应用进程处理接收缓冲区数据很慢,或二者兼而有之;就会使应用进程间传送的报文段很小,特别是有效载荷很小。 极端情况下,有效载荷可能只有1个字节;而传输开销有40字节(20字节的IP头+20字节的TCP头),加上物理帧头后,有效的数据传输比例就更小了,这就浪费了网络带宽,表现为糊涂窗口综合症。
糊涂窗口综合症可能由接收端或者发送端引起,不同的起因需要不同的解决方案,更多内容可以参考此处。
保活定时器
跟据TCP协议,当发送端和接收端都不主动释放一个TCP连接的时候,该连接将一直保持。即使一端出现了故障,由于另一端没有收到任何通知,TCP连接也会一直保持,这样就会造成TCP连接资源的浪费。
TCP keepalive
为了解决这个问题,大多数的实现中都是使服务器设置保活计时器。
保活计时器通常设置为2小时。若服务器过了2小时还没有收到客户的信息,它就发送探测报文段。若发送了10个探测报文段(每一个相隔75秒)还没有响应,就假定客户出了故障,因而就终止该连接。
在Linux系统中,有三个跟TCP keepalive相关的参数:
tcp_keepalive_intvl (integer; default: ; since Linux 2.4)
The number of seconds between TCP keep-alive probes. tcp_keepalive_probes (integer; default: ; since Linux 2.2)
The maximum number of TCP keep-alive probes to send before giving up and killing the connection if no
response is obtained from the other end. tcp_keepalive_time (integer; default: ; since Linux 2.2)
The number of seconds a connection needs to be idle before TCP begins sending out keep-alive probes. Keep-
alives are sent only when the SO_KEEPALIVE socket option is enabled. The default value is seconds (
hours). An idle connection is terminated after approximately an additional minutes ( probes an interval
of seconds apart) when keep-alive is enabled.
在Socket编程中,可以通过设置"TCP_KEEPCNT","TCP_KEEPIDLE"和"TCP_KEEPINTVL"选项来更改上述的三个系统参数:
from socket import *
import time HOST = "192.168.56.102"
PORT = 8081
ADDR = (HOST, PORT) client = socket(AF_INET, SOCK_STREAM) #TCP_KEEPCNT overwrite tcp_keepalive_probes,默认9(次)
#TCP_KEEPIDLE overwrite tcp_keepalive_time,默认7200(秒)
#TCP_KEEPINTVL overwrite tcp_keepalive_intvl,默认75(秒)
client.setsockopt(SOL_SOCKET, SO_KEEPALIVE, 1)
client.setsockopt(SOL_TCP, TCP_KEEPCNT, 5)
client.setsockopt(SOL_TCP, TCP_KEEPINTVL, 5)
client.setsockopt(SOL_TCP, TCP_KEEPIDLE, 10)
client.connect(ADDR) while True:
input = raw_input() if input:
client.send(input*1000)
else:
client.close()
break
TCP keepalive 包
下面是一段网络上抓取的TCP keepalive包,接下来看看TCP keepalive包的内容。

- 根据规范,TCP keepalive保活包不应该包含数据,但也可以包含1个无意义的字节,比如0x0。
- TCP保活探测包Seq号是将前一个TCP包的Seq号减去1。
当然,也有人认为保活定时器不合理,给出了不使用保活定时器的理由:
- 在出现短暂差错的情况下,这可能会使一个非常好的连接释放掉
- 耗费了不必要的带宽
- 在按分组计费的情况下会在互联网上花掉更多的钱
HTTP Keep-Alive
在HTTP早期 ,每个HTTP请求都要求打开一个TCP连接,并且使用一次之后就断开这个TCP连接。
这种方式会带来一些问题,尤其是包含图片,JS,CSS的复杂网页,一个完整的页面需要很多个请求才能完成,如果每一个HTTP请求都需要新建并断开一个TCP,这样就会消耗很多服务器的TCP连接资源。
为了缓解这个问题,HTTP 1.1中出现了Keep-Alive这个特性,开启HTTP Keep-Alive之后,能复用已有的TCP链接,当前一个请求已经响应完毕,服务器端没有立即关闭TCP链接,而是等待一段时间接收浏览器端可能发送过来的第二个请求,开启Keep-Alive能节省的TCP建立和关闭的消耗。

下面看看我访问一个网页后,通过Wireshark抓取的数据包。
HTTP/1.1之后默认开启Keep-Alive, 在HTTP的头域中增加Connection选项。当设置为"Connection:keep-alive"表示开启,设置为"Connection:close"表示关闭。
在上图中,服务器经过了大概2分钟的时间,然后发出关闭TCP连接的请求。
现在,基本所有的应用服务器都支持设置打开Keep-Alive,以及Keep-Alive timeout的设置。
总结
本文介绍了TCP中的4种定时器,并详细的介绍了坚持定时器和保活定时器。
在保活定时器的介绍中,对比介绍了HTTP的Keep-Alive特性。HTTP协议的Keep-Alive意图在于连接复用;TCP的keepalive机制在于保活、心跳,检测连接错误,两者的作用完全不同。
因为TCP keepalive不能满足实时性的要求,很多应用程序会在应用层实现heart beat(心跳包)来确认TCP连接的可用性。
动手学习TCP:4种定时器的更多相关文章
- 动手学习TCP:总结和索引
TCP是一个十分复杂的协议,通过前面几篇文章只涉及了TCP协议中一些基本的概念. 虽然说都是一些TCP最基本的概念,但是试验过程中一直在踩坑,例如:TCP flag设置错误,seq.ack号没有计算正 ...
- 动手学习TCP:TCP特殊状态
前面两篇文章介绍了TCP状态变迁,以及通过实验演示了客户端和服务端的正常状态变迁. 下面就来看看TCP状态变迁过程中的几个特殊状态. SYN_RCVD 在TCP连接建立的过程中,当服务端接收到[SYN ...
- 动手学习TCP:数据传输
前面的文章介绍了TCP状态变迁,以及TCP状态变迁图中的一些特殊状态. 本文主要看看TCP数据传输过程中需要了解的一些重要点: MSS(Maximum Segment Size) Seq号和Ack号的 ...
- 动手学习TCP:数据传输(转)
前面的文章介绍了TCP状态变迁,以及TCP状态变迁图中的一些特殊状态. 本文主要看看TCP数据传输过程中需要了解的一些重要点: MSS(Maximum Segment Size) Seq号和Ack号的 ...
- TCP四种定时器--学习笔记
TCP使用四种定时器: 重传定时器(Retransmission Timer).坚持定时器(Persistent Timer).保活定时器(Keeplive Timer).时间等待定时器(Time_W ...
- 解读TCP 四种定时器
TCP 是提供可靠的传输层,它使用的方法之一就是确认从另一端收到的数据.但是数据和确认都可能会丢失.TCP 通过在发送时设置一个定时器来解决这个问题.如果当定时器溢出时还没收到确认,它就会重传该数据. ...
- 动手学习TCP: 环境搭建
前一段时间通过Wireshark抓包,定位了一个客户端和服务器之间数据传输的问题.最近就抽空看了看<TCP/IP详解 卷1>中关于TCP的部分,书中用了很多例子展示了TCP/IP协议中的一 ...
- 动手学习TCP:客户端状态变迁
上一篇文章中介绍了TCP连接的建立和终止. 通过实际操作了解到,在TCP协议工作过程中,客户端和服务端都会接收或者发送特定标志的TCP数据包,然后进入不同的状态. 也就是说,TCP协议就是一个包含多种 ...
- 动手学习TCP:TCP连接建立与终止
TCP是一个面向连接的协议,任何一方在发送数据之前,都必须先在双方之间建立一条连接.所以,本文就主要看看TCP连接的建立和终止. 在开始介绍TCP连接之前,先来看看TCP数据包的首部,首部里面有很多重 ...
随机推荐
- 如何在ASP.Net中实现RSA加密
在我们实际运用中,加密是保证数据安全的重要手段.以前使用ASP时,对数据加密可以使用MD5和SHA1算法,这两种算法虽然快捷有效,但是无法对通过它们加密的密文进行反运算,即是解密.因此需要解密数据的场 ...
- c#重点[集合类型]异常,数组,集合ArrayList,List<>,hashTable,hashtable泛型(Dictionary)
1.foreach[对一些数组或集合进行遍历] foreach(类型 变量名 in 集合对象){语句体} //定义一个数组 ,,,,, }; foreach(var i in sNum1) { Con ...
- Wijmo金融图表系列之平均K线图&砖形图
2015年7月16日将会发布有史以来最令人兴奋的控件-Wijmo 金融图表,它的一体化设计为单个自定义集合提供了所有主要的金融图表,这是市场上的其他控件都不具备的独一无二的好处.它像Wijmo其他任意 ...
- 自己动手搞定支付宝手机网站支付接口 FOR ECShop
支付宝WAP网站版本的支付接口网上整合的比较少,看到很多网站在卖,顿觉无语. 主要是得自己查看支付宝官方提供的SDK中的开发文档. 支付宝sdk下载地址:https://doc.open.alipay ...
- 回文串---Palindrome
POJ 3974 Description Andy the smart computer science student was attending an algorithms class whe ...
- 泛函编程(10)-异常处理-Either
上节我们介绍了新的数据类型Option:一个专门对付异常情况出现时可以有一致反应所使用的数据类型.Option可以使编程人员不必理会出现异常后应该如何处理结果,他只是获得了一个None值,但这个Non ...
- 常用 Git 命令清单(摘录)
来源:阮一峰的网络日志 网址:http://www.ruanyifeng.com/blog/2015/12/git-cheat-sheet.html 我每天使用 Git ,但是很多命令记不住. 一般来 ...
- MyEclipse8.6安装SVN 教程 与遇到的问题
按网上的多种方式都不好用 最后这种好用 了! 写此文做记录. MyEclipse版本:8.6 SVN版本:1.6.9 MyEclipse版本要对应SVN版本.否则会出错. 教程: 1.下载最新 ...
- 缓存技术比拼:Redis与Memcached的同与不同
转至:http://developer.51cto.com/art/201603/507980.htm 在今天的文章中,我们将探讨Redis(REmote DIctionary Server).Red ...
- Dynamics AX for Retail POS Development blogs
Dynamics AX for Retail POS Development Dynamics AX for Retail POS Development - Code Samples AX for ...