TCP(Transmission Control Protocol)
TCP(Transmission Control Protocol)提供一种面向连接的、可靠的字节流服务。TCP连接是全双工的,即数据在两个方向上能够同时传递。
TCP和UDP处在同一层(传输层),但是TCP和UDP最不同的地方是,TCP提供了一种可靠(reliable)的数据传输服务,TCP是面向连接的。
也就是说,利用TCP通信的两台主机首先要经历一个“拨打电话”的过程,等到通信准备结束才开始传输数据,最后结束通话。所以TCP要比UDP可靠的多,UDP是把数据直接发出去,而不管对方是不是在收信,就算是UDP无法送达,也不会产生ICMP差错报文。
IP包裹的TCP
TCP报文由首部和数据组成,数据部分是可选的。TCP报文被封装在IP数据报中,结构如下图所示:
TCP 头部(TCP Header)结构
TCP Header由固定部分(Basic)和选项部分(Options)组成,其中固定部分的大小为20 bytes。TCP Header最长为60 bytes。
因此,默认的TCP头部(TCP Header)长度为20个字节。
下面就将每个字段的信息都详细的说明一下。
- Source Port (源端口号)和Destination Port(目的端口号):分别占用16位;用于区别主机中的不同进程,而IP地址是用来区分不同的主机的,源端口号和目的端口号配合上IP首部中的源IP地址和目的IP地址就能唯一的确定一个TCP连接。
- Sequence Number(请求序号):占用32位,用来标识从TCP发端向TCP收端发送的数据字节流,它表示当前TCP报文段中的第一个数据字节在数据流中的序号;主要用来解决网络报乱序(reordering)的问题。
- 当建立一个新的连接时,请求序号等于含由这个主机选择的该连接的初始序号ISN(Initial Sequence Number)。
- Acknowledgment Number(确认序号):占用32位,确认序列号包含发送确认的一端所期望收到的下一个序号,因此,确认序号应当是上次已成功收到数据字节序号加1。不过,只有当标志位中的ACK标志(下面介绍)为1时该确认序列号的字段才有效。主要用来解决不丢包的问题。
- Header Length(TCP Header长度):占用4位,表示TCP Header的长度为多少个32bit(4字节),或者说,表示TCP 数据区的起始位置距离TCP Header的起始位置有多少个32bit。
- 需要这个字段的原因是因为TCP Header中Options(可选字段)区域的长度是可变的;
- 该字段的最大值为15(即0xf);这意味着TCP Header的最大长度为4字节*15=60字节;
- 当TCP Header中没有Options区域时,TCP Header的长度是20字节。因此,TCP Header的默认长度就是20字节;
- TCP Flags(TCP 标志位):TCP Header中有12个标志位,每个标志位占用一个bit。
- 它们中的多个可同时被设置为1;
- 前六位为保留位(Resserved),后六位分别为Urgent、Acknowledgment、Push、Reset、Syn和Fin;
- 有的资料也认为保留位只包括前四位(或者前三位),而(Nonce)、Congestion Window Reduced (CWR)、ECN-Echo(ECU)不是保留位。
- 用于操控TCP的状态机的标志位,包括URG,ACK,PSH,RST,SYN,FIN。每个标志位的意思如下:
- URG:此标志表示TCP包的紧急指针(Urgent Pointer)域有效,用来保证TCP连接不被中断,并且督促中间层设备要尽快处理这些数据;
- ACK:此标志表示应答域有效,就是说前面所说的TCP应答号将会包含在TCP数据包中;有两个取值:0和1,为1的时候表示应答域有效,反之为0;
- PSH:表示Push操作。所谓Push操作就是指在数据包到达接收端以后,立即传送给应用程序,而不是在缓冲区中排队;
- RST(重置连接报文):表示连接复位(Reset)请求。用来复位那些产生错误的连接,也被用来拒绝错误和非法的数据包;
- SYN:表示同步序号,用来建立连接。SYN标志位和ACK标志位搭配使用,当连接请求的时候,SYN=1,ACK=0;连接被响应的时候,SYN=1,ACK=1;这个标志的数据包经常被用来进行端口扫描。扫描者发送一个只有SYN的数据包,如果对方主机响应了一个数据包回来 ,就表明这台主机存在这个端口;但是由于这种扫描方式只是进行TCP三次握手的第一次握手,因此这种扫描的成功表示被扫描的机器不很安全,一台安全的主机将会强制要求一个连接严格的进行TCP的三次握手;
- FIN(断开连接报文): 表示发送端已经达到数据末尾,也就是说双方的数据传送完成,没有数据可以传送了,发送FIN标志位的TCP数据包后,连接将被断开。这个标志的数据包也经常被用于进行端口扫描。
- Window size(窗口大小):占用16位,单位为字节,也就是有名的滑动窗口(Sliding Window),用来进行流量控制;具体来说,用来控制对方发送的数据量,单位为字节。TCP连接中的一端根据自己的缓存空间大小来具体确定接收窗口大小,并通知对方。
- Window size value可能由Window size(窗口大小)和 Window size scaling factor(窗口大小换算系数)共同决定。
- Checksum(校验和):占用16位,用于校验Header和数据区这两部分的数据抵达接收方主机后的完整性。在计算检验和时,要在TCP报文段的前面加上12字节的伪首部。
- Urgent pointer(紧急指针):占用16位,紧急指针指出在本报文段中的紧急数据的最后一个字节的序号。
- Options(可选区域):占用0到20个字节,具体可能包括以下几个字段:maximum segment,windows scale,NOP,SACK permitted等。
Options(可选区域)
每一个Option项均为这样的结构:Kind + (Length) + (Data):
- Kind部分:必须,表示这个Option项的类型,占8 Bit:
- Kind=0表示选项结束(End of Option List,EOL),整个Option项占用一个1字节;
- Kind=1表示无操作(No-Operation,NOP),主要是用来占位从而达到字节对齐的目的,整个Option项占用一个1字节;
- Kind=2表示MSS(Maximum Segment Size,最大分段大小),表示对方通过TCP传输到本机的最大TCP segment数据的长度。当建立一个连接时,每方都可以通告对方,自己期望接收的 MSS选项(M S S选项只能出现在SYN报文段中),整个Option项占用一个4字节;
- 通过MSS,应用数据被分割成TCP认为最适合发送的数据块;
- 我们不难联想到,跟最大报文段长度最为相关的一个参数是网络设备接口的MTU,以太网的MTU是1500,基本IP首部长度为20,TCP首部是20,所以MSS的值可达1460(MSS不包括协议首部,只包含应用数据)
- Kind=3表示窗口扩大因子(window scale),整个Option项占用一个3字节;
- Kind=4表示SACK-Permitted,整个Option项占用一个2字节;
- Kind=5表示一个SACK包,整个Option项长度可变;
- Kind=8表示时间戳(Timestamps),整个Option项占用一个10字节。
- 时间戳选项使发送方在每个报文段中放置一个时间戳值。接收方在确认中返回这个数值,从而允许发送方为每一个收到的 ACK计算RTT。
- Length部分:可选,表示Kind、Lenght、Data三者的总长度,占8 Bit;
- 当Kind = 1时,没有Length部分。
- Data部分,可选,表示内容
举例:
- 类型2,表示MSS(Maximum Segment Size),长度length = 4 Bytes, Data(MSS Value = 1440)
TCP Option - Maximum segment size: 1460 bytes Kind: Maximum Segment Size (2) Length: 4 MSS Value: 1460
- 类型1,表示无操作(No-Operation)
TCP Option - No-Operation (NOP) Kind: No-Operation (1)
- 类型3,表示窗口扩大因子(window scale)为2,长度为3
TCP Option - Window scale: 6 (multiply by 64) Kind: Window Scale (3) Length: 3 Shift count: 6 [Multiplier: 64]
- 类型8,时间戳(Timestamps)
TCP Option - Timestamps: TSval 617957676, TSecr 0
Kind: Time Stamp Option (8)
Length: 10
Timestamp value: 617957676
Timestamp echo reply: 0
-
类型4,表示SACK-Permitted,长度为2
TCP Option - SACK permitted Kind: SACK Permitted (4) Length: 2
-
类型0,表示选项结束(End of Option List,EOL)
TCP Option - End of Option List (EOL) Kind: End of Option List (0)
TCP保证可靠性
把TCP保证可靠性的简单工作原理:
- 应用数据被分割成TCP认为最适合发送的数据块。这和UDP完全不同,应用程序产生的数据报长度将保持不变。由TCP传递给IP的信息单位称为报文段或段(Segment)。
- 当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
- 当TCP收到发自TCP连接另一端的数据,它将发送一个确认。这个确认不是立即发送,通常将推迟几分之一秒.
- TCP将计算它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错, TCP将丢弃这个报文段和不确认收到此报文段(希望发端超时并重发)。
- 既然TCP报文段作为IP数据报来传输,而IP数据报的到达可能会失序,因此,TCP报文段的到达也可能会失序。如果必要, TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层。
- TCP还能提供流量控制。TCP连接的每一方都有固定大小的缓冲空间。TCP的接收端只允许另一端发送接收端缓冲区所能接纳的数据。这将防止较快主机致使较慢主机的缓冲区溢出。
从这段话中可以看到,TCP中保持可靠性的方式就是超时重发,这是有道理的,虽然TCP也可以用各种各样的ICMP报文来处理这些,但是这也不是可靠的,最可靠的方式就是只要不得到确认,就重新发送数据报(datagram),直到得到对方的确认为止。
TCP的首部和UDP首部一样,都有发送端口号和接收端口号。但是显然,TCP的首部信息要比UDP的多,可以看到,TCP协议提供了发送和确认所需要的所有必要的信息。
TCP的状态机
TCP状态转换图,可以很好地概括整个TCP连接的建立和终止的过程及状态的变化。图来自 TCP/IP Illustrated, Volume 1 : The Protocols(2nd Edition) 。
TCP KeepAlive
TCP 的连接,实际上是一种纯软件层面的概念,在物理层面并没有“连接”这种概念。TCP 通信双方建立交互的连接,但是并不是一直存在数据交互,有些连接会在数据交互完毕后,主动释放连接,而有些不会。在长时间无数据交互的时间段内,交互双方都有可能出现掉电、死机、异常重启等各种意外,当这些意外发生之后,这些 TCP 连接并未来得及正常释放,在软件层面上,连接的另一方并不知道对端的情况,它会一直维护这个连接,长时间的积累会导致非常多的半打开连接,造成端系统资源的消耗和浪费,为了解决这个问题,在传输层可以利用 TCP 的 KeepAlive 机制实现来实现。主流的操作系统基本都在内核里支持了这个特性。
TCP KeepAlive 的基本原理是,隔一段时间给连接对端发送一个探测包,如果收到对方回应的 ACK,则认为连接还是存活的,在超过一定重试次数之后还是没有收到对方的回应,则丢弃该 TCP 连接。
TCP KeepAlive 的局限
首先 TCP KeepAlive 监测的方式是发送一个 probe 包,会给网络带来额外的流量,另外 TCP KeepAlive 只能在内核层级监测连接的存活与否,而连接的存活不一定代表服务的可用。例如当一个服务器 CPU 进程服务器占用达到 100%,已经卡死不能响应请求了,此时 TCP KeepAlive 依然会认为连接是存活的。因此 TCP KeepAlive 对于应用层程序的价值是相对较小的。需要做连接保活的应用层程序,例如 QQ,往往会在应用层实现自己的心跳功能。
TCP的异常终止
TCP的异常终止是相对于正常释放TCP连接的过程而言的。
我们都知道,TCP连接的建立是通过三次握手完成的,而TCP正常释放连接是通过四次挥手来完成,但是有些情况下,TCP在交互的过程中会出现一些意想不到的情况,导致TCP无法按照正常的四次挥手来释放连接,如果此时不通过其他的方式来释放TCP连接的话,这个TCP连接将会一直存在,占用系统的部分资源。
在这种情况下,我们就需要有一种能够释放TCP连接的机制,这种机制就是TCP的reset报文。reset报文是指TCP报头的标志字段中的reset位置为一的报文,如下图所示:
TCP异常终止的常见情形
我们在实际的工作环境中,导致某一方发送reset报文的情形主要有以下几种:
1.客户端尝试与服务器未对外提供服务的端口建立TCP连接,服务器将会直接向客户端发送reset报文
- 客户端和服务器的某一方在交互的过程中发生异常(如程序崩溃等),该方系统将向对端发送TCP reset报文,告之对方释放相关的TCP连接,如下图所示:
- 接收端收到TCP报文,但是发现该TCP的报文,并不在其已建立的TCP连接列表内,则其直接向对端发送reset报文,如下图所示:
- 在交互的双方中的某一方长期未收到来自对方的确认报文,则其在超出一定的重传次数或时间后,会主动向对端发送reset报文释放该TCP连接,如下图所示:
5,有些应用开发者在设计应用系统时,会利用reset报文快速释放已经完成数据交互的TCP连接,以提高业务交互的效率,如下图所示:
为何Reset报文不需要ACK确认?
因为发送Reset报文的一端,在发送完这个报文之后,和该TCP Session有关的内存结构体瞬间全部释放,无论接收方收到或没有收到,关系并不大。
如果接收方收到Reset报文,也会释放该TCP Session 的相关内存结构体。
如果接收方没有收到Reset 报文,可能会继续发送让接收方弹射出Reset报文的报文,到最后对方一样会收到Reset 报文,并最终释放内存。
TCP初始化序列号ISN
TCP初始化序列号不能设置为一个固定值,因为这样容易被攻击者猜出后续序列号,从而遭到攻击
RFC1948中提出了一个较好的初始化序列号ISN随机生成算法
- ISN = M + F(localhost, localport, remotehost, remoteport)
- M是一个计时器,这个计时器每隔4毫秒加1
- F是一个Hash算法,根据源IP、目的IP、源端口、目的端口生成一个随机数值。
Wireshark常见异常状态
- TCP Previous segment not captured TCP前分片未收到
- TCP Out-Of-Order TCP数据乱序
- TCP Dup ACK 47#1 重复应答
当收到一个出问题的分片,TCP协议规定接收方应立即产生一个应答。这个相同的ack不会延迟。这个相同应答的意图是让对端知道一个分片被收到的时候出现问题,并且告诉它希望得到的序列号。上图中TCP Dup ACK 47#1 的意思第47位数据出现问题,次数是1次。
最大报文长度(Maximum Segment Size)
最大报文段长度(Maximum Segment Size)表示TCP传往另一端的最大块数据的长度。当一个连接建立时,连接的双方都要通告各自的MSS。
一般这个SYN长度是MTU减去固定IP首部和TCP首部长度。
对于一个以太网,一般可以达到1460字节。当然如果对于非本地的IP,这个MSS可能就只有536字节,而且,如果中间的传输网络的MSS更加的小的话,这个值还会变得更小。
TCP分段与IP分片的区别
IP分片产生的原因是MTU网络层的;而TCP分段产生原因是MSS(Max Segment Size),它们工作在不同的层。
IP分片由网络层完成,也在网络层进行重组;TCP分段是在传输层完成,并在传输层进行重组。
对于以太网,MSS为1460字节(1500 – 20 - 20),而MTU为1480字节(1500 – 20 )其中20字节为IP的头部。因而,MTU往往会大于MSS。
因此,采用TCP协议进行数据传输,是不会造成IP分片的。因为当数据过大时,会在传输层进行数据分段,因而到了IP层就不用分片了。
我们常提到的IP分片是由于UDP传输协议造成的,因为UDP传输协议并未限定传输UDP Datagram(数据报)的大小。
TCP的半关闭(Half-closed)
TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。这就是所谓的半关闭。正如我们早些时候提到的只有很少的应用程序使用它。
为了使用这个特性,编程接口必须为应用程序提供一种方式来说明“我已经完成了数据传送,因此发送一个文件结束(FIN)给另一端,但我还想接收另一端发来的数据,直到它给我发来文件结束(FIN)”。
为何ACK报文不需要ACK确认?
这里的ACK报文,是指没有携带任何数据的裸ACK报文,对方收到这样的ACK报文,自然也不需要ACK。否则,对方为了确认已发送的ACK,还要确认已发送的ACK的ACK,这就是一个死循环,永无止息。
所以为了避免这个死循环,一律不允许对对方的裸ACK报文进行确认(通过发送一个ACK报文)。
Reference
- TCP/IP Illustrated, Volume 1 : The Protocols(2nd Edition), Kevin.R.Fall