上一篇中介绍了TCP数据传输中涉及的一些基本知识点。本文让我们看看TCP中的4种定时器。

TCP定时器

对于每个TCP连接,TCP管理4个不同的定时器,下面看看对4种定时器的简单介绍。

  • 重传定时器使用于当希望收到另一端的确认。
    • 该定时器是用来决定超时和重传的。
    • 由于网络环境的易变性,该定时器时间长度肯定不是固定值;该定时器时间长度的设置依据是RTT(Round Trip Time),根据网络环境的变化,TCP会根据这些变化并相应地改变超时时间。
  • 坚持定时器(persist)使窗口大小信息保持不断流动,即使另一端关闭了其接收窗口。
  • 保活定时器(keepalive)可检测到一个空闲连接的另一端何时崩溃或重启。
  • 2MSL定时器测量一个连接处于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种定时器的更多相关文章

  1. 动手学习TCP:总结和索引

    TCP是一个十分复杂的协议,通过前面几篇文章只涉及了TCP协议中一些基本的概念. 虽然说都是一些TCP最基本的概念,但是试验过程中一直在踩坑,例如:TCP flag设置错误,seq.ack号没有计算正 ...

  2. 动手学习TCP:TCP特殊状态

    前面两篇文章介绍了TCP状态变迁,以及通过实验演示了客户端和服务端的正常状态变迁. 下面就来看看TCP状态变迁过程中的几个特殊状态. SYN_RCVD 在TCP连接建立的过程中,当服务端接收到[SYN ...

  3. 动手学习TCP:数据传输

    前面的文章介绍了TCP状态变迁,以及TCP状态变迁图中的一些特殊状态. 本文主要看看TCP数据传输过程中需要了解的一些重要点: MSS(Maximum Segment Size) Seq号和Ack号的 ...

  4. 动手学习TCP:数据传输(转)

    前面的文章介绍了TCP状态变迁,以及TCP状态变迁图中的一些特殊状态. 本文主要看看TCP数据传输过程中需要了解的一些重要点: MSS(Maximum Segment Size) Seq号和Ack号的 ...

  5. TCP四种定时器--学习笔记

    TCP使用四种定时器: 重传定时器(Retransmission Timer).坚持定时器(Persistent Timer).保活定时器(Keeplive Timer).时间等待定时器(Time_W ...

  6. 解读TCP 四种定时器

    TCP 是提供可靠的传输层,它使用的方法之一就是确认从另一端收到的数据.但是数据和确认都可能会丢失.TCP 通过在发送时设置一个定时器来解决这个问题.如果当定时器溢出时还没收到确认,它就会重传该数据. ...

  7. 动手学习TCP: 环境搭建

    前一段时间通过Wireshark抓包,定位了一个客户端和服务器之间数据传输的问题.最近就抽空看了看<TCP/IP详解 卷1>中关于TCP的部分,书中用了很多例子展示了TCP/IP协议中的一 ...

  8. 动手学习TCP:客户端状态变迁

    上一篇文章中介绍了TCP连接的建立和终止. 通过实际操作了解到,在TCP协议工作过程中,客户端和服务端都会接收或者发送特定标志的TCP数据包,然后进入不同的状态. 也就是说,TCP协议就是一个包含多种 ...

  9. 动手学习TCP:TCP连接建立与终止

    TCP是一个面向连接的协议,任何一方在发送数据之前,都必须先在双方之间建立一条连接.所以,本文就主要看看TCP连接的建立和终止. 在开始介绍TCP连接之前,先来看看TCP数据包的首部,首部里面有很多重 ...

随机推荐

  1. 【Unity】12.5 Navmesh Obstacle组件

    开发环境:Win10.Unity5.3.4.C#.VS2015 创建日期:2016-05-09 一.简介 在大多数游戏情景中,可行进的区域往往不是完全不变的.比如被破坏的路.桥等将不再允许通过.那么, ...

  2. csharp: Speech

    Speech SDK 5.1https://www.microsoft.com/en-us/download/details.aspx?id=10121 detects mobile devices ...

  3. 各个平台的mysql重启命令

    linux平台及windows平台mysql重启方法 Linux下重启MySQL的正确方法: 1.通过rpm包安装的MySQL service mysqld restart 2.从源码包安装的MySQ ...

  4. Introduction to ASP.NET 5

    ASP.NET 5 is a significant redesign of ASP.NET. This topic introduces the new concepts in ASP.NET 5 ...

  5. Javascript的封装

    js的封装分为以下几种模式: 工厂模式,代码如下: <!doctype html><html lang="en"><head><meta ...

  6. 同源策略 JSONP(实践篇)

    JSONP详解 json相信大家都用的多,jsonp我就一直没有机会用到,但也经常看到,只知道是“用来跨域的”,一直不知道具体是个什么东西.今天总算搞明白了.下面一步步来搞清楚jsonp是个什么玩意. ...

  7. Step by step configuration of Outgoing Emails from SharePoint to Microsoft Online

    First of all your SharePoint server should be added to Microsoft online safe sender list, so that Sh ...

  8. 利用在线工具自动化生成findviewById

    我们在编码的时候经常会用到findviewById,不厌其烦,我之前介绍过一个很取巧的方法,挺好用的,这里再贴一下: public class KaleBaseActivity extends Act ...

  9. JDK8 API文档(下载)

    DK API文档 java SE 8 API文档: http://www.oracle.com/technetwork/java/javase/documentation/jdk8-doc-downl ...

  10. mysql登录和连接 权限

    在一些配置中会要求登录mysql 授权的时候注意ip地址是ip地址,localhost是localhost,在grant授权时,如果用localhost,就必须在所登录的配置文件中使用localhos ...