TCP协议2

在上文(求求你不要在问我三次握手了(一))中我们对TCP的流程进行了泛泛的讲解,本文将会深入的继续讨论。

1,TCP是什么?
先看定义

TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。

从定义我们知道,TCP是一个协议,而且是一个很复杂的协议,但是它又是互联网的基础。

那么什么又是协议呢?

在两个人或者是两台机器之间进行通信就要遵守一些规定。协议就是管理通信的一种规则。

比如说两个人交流的时候,什么时候开始交流,怎么交流,是用汉语还是用英语,什么时候结束交流等等。这就是一种规则。同理,两个不同网络的主机想要使用TCP进行通信就必须遵守TCP的规定,This is my home!

协议的三个关键词是:语法,语义和时序。

语法:数据的格式或者是结构,指数据呈现的顺序。例如:可以规定前8位是目的地址,紧跟的8位是源地址,剩下的是内容。

语义:每一段比特流代表的含义。

时序:时序设计到两个方面。数据应该是什么时候发送出去以及数据应该以什么样的速度发送。

我们需要知道TCP工作在网络OSI的七层模型中的第四层——Transport层,IP在第三层——Network层,ARP在第二层——Data Link层;在第二层上的数据,我们把它叫Frame,在第三层上的数据叫Packet,第四层的数据叫Segment。 同时,我们需要简单的知道,数据从应用层发下来,会在每一层都会加上头部信息,进行封装,然后再发送到数据接收端。这个基本的流程你需要知道,就是每个数据都会经过数据的封装和解封装的过程。 在OSI七层模型中,每一层的作用和对应的协议如下:

TCP是一个协议,那这个协议是如何定义的,它的数据格式是什么样子的呢?要进行更深层次的剖析,就需要了解,甚至是熟记TCP协议中每个字段的含义。come on baby!

2,TCP的报文结构
TCP 是面向字节流的,但传送的数据单元却是报文段

那么什么是报文段呢?

例如一个 100kb 的 HTML 文档需要传送到另外一台计算机,并不会整个文档直接传送过去,可能会切割成几个部分,比如四个分别为 25kb 的数据段。然后在每个数据段再加上一个 TCP 首部,就组成了 TCP 报文段。一共四个 TCP 报文段,发送到另外一个端。另外一端收到数据段,然后再剔除 TCP首部,组装起来。等到四个数据段都收到了,就能还原出来一个完整的 HTML 文档了。

TCP的报文段包含两部分:首部和数据部分。

而 TCP 的全部功能都体现在它首部中各字段的作用,只有弄清 TCP 首部各字段的作用才能掌握 TCP 的工作原理。TCP 报文段首部的前20个字节是固定的,后面有 4N 字节是根据需要而增加的。下图是把 TCP 报文中的首部放大来看

上面就是TCP协议头部的格式,由于它太重要了,是理解其它内容的基础,下面就将每个字段的信息都详细的说明一下。

Source Port和Destination Port:分别占用2个字节,共四个字节。表示源端口号和目的端口号;用于区别主机中的不同进程,而IP地址是用来区分不同的主机的,源端口号和目的端口号配合上IP首部中的源IP地址和目的IP地址就能唯一的确定一个TCP连接;

Sequence Number(序号): 占四个字节。 TCP 是面向字节流的,在一个 TCP 连接中传输的字节流中的每个字节都按照顺序编号。用来标识从TCP发端向TCP收端发送的数据字节流,它表示在这个报文段中的的第一个数据字节在数据流中的序号;主要用来解决网络报乱序的问题。

例如 100 kb 的 HTML 文档数据,一共 102400 (100 * 1024) 个字节,那么每一个字节就都有了编号,整个文档的编号的范围是 0 ~ 102399。 序号字段值指的是本报文段所发送的数据的第一个字节的序号。

那么 100 的 HTML 文档分割成四个等分之后,第一个 TCP 报文段包含的是第一个 25kb 的数据,0 ~ 25599 字节, 该报文的序号的值就是:0第二个 TCP 报文段包含的是第二个 25kb 的数据,25600 ~ 51199 字节,该报文的序号的值就是:25600……根据 8 位 = 1 字节,那么 4 个字节可以表示的数值范围:[0, 2^32],一共 2^32 (4294967296) 个序号。序号增加到最大值的时候,下一个序号又回到了 0.也就是说 TCP 协议可对 4GB 的数据进行编号,在一般情况下可保证当序号重复使用时,旧序号的数据早已经通过网络到达终点或者丢失了。

Acknowledgemt Number(确认号): 占用四个字节。表示期望收到对方下一个报文段的序号值, TCP 的可靠性,是建立在「每一个数据报文都需要确认收到」的基础之上的 。就是说,通讯的任何一方在收到对方的一个报文之后,都要发送一个相对应的「确认报文」,来表达确认收到。那么,确认报文,就会包含确认号 。不过,只有当标志位中的ACK标志(下面介绍)为1时该确认序列号的字段才有效。主要用来解决不丢包的问题。譬如上面的例子,当接收方接收到第一个25K的报文段后,该报文的长度是 0 ~ 25599 字节。那么接收方就需要回复一个确认报文,该确认报文中的确认号就应该是25600。

Offset (数据偏移):占 0.5 个字节 (4 位)。需要这个值是因为任选字段的长度是可变的。这个字段实际上是指出了 TCP 报文段的首部长度 ,它指出了 TCP报文段的数据起始处 距离 TCP报文的起始处 有多远。(注意 数据起始处 和 报文起始处 的意思)一个数据偏移量 = 4 byte,由于 4 位二进制数能表示的最大十进制数字是 15,因此数据偏移的最大值是 60 byte,这也侧面限制了 TCP 首部的最大长度。

Reserved(保留字):占 0.75 个字节 (6 位)。保留为今后使用,但目前应置为 0。

TCP Flags(标志位):一共有 6 个,分别占 1 位,共 6 位 。每一位的值只有 0 和 1,它们中的多个可同时被设置为1,主要是用于操控TCP的状态机的 。依次为URG,ACK,PSH,RST,SYN,FIN。每个标志位的意思如下:

紧急 URG (Urgent):当 URG = 1 的时候,表示紧急指针(Urgent Pointer)有效。用来保证TCP连接不被中断 ,它告诉系统此报文段中有紧急数据,应尽快传送,而不要按原来的排队顺序来传送。URG 要与首部中的 紧急指针 字段配合使用。

确认 ACK (Acknowlegemt):当 ACK = 1 的时候,上面提到的确认号(Acknowledgemt Number)有效。一般称携带 ACK 标志的 TCP 报文段为「确认报文段」。TCP 规定,在连接建立后所有传送的报文段都必须把 ACK 设置为 1。

推送 PSH (Push):当 PSH = 1 的时候,表示该报文段高优先级,接收方 TCP 应该尽快推送给接收应用程序,而不用等到整个 TCP 缓存都填满了后再交付

复位 RST (Reset):当 RST = 1 的时候,表示 TCP 连接中出现严重错误,需要释放并重新建立连接。一般称携带 RST 标志的 TCP 报文段为「复位报文段」。

同步 SYN (SYNchronization):当 SYN = 1 的时候,表明这是一个请求连接报文段。一般称携带 SYN 标志的 TCP 报文段为「同步报文段」。在 TCP 三次握手中的第一个报文就是同步报文段,在连接建立时用来同步序号。对方若同意建立连接,则应在响应的报文段中使 SYN = 1 和 ACK = 1。

终止 FIN (Finis):当 FIN = 1 时,表示此报文段的发送方的数据已经发送完毕,并要求释放 TCP 连接。一般称携带 FIN 的报文段为「结束报文段」。在 TCP 四次挥手释放连接的时候,就会用到该标志。

窗口大小 Window Size:占 2 字节。也就是有名的滑动窗口,用来进行流量控制 。该字段明确指出了现在允许对方发送的数据量,它告诉对方本端的 TCP 接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。窗口大小的值是指,从本报文段首部中的确认号算起,接收方目前允许对方发送的数据量。例如,假如确认号是 701 ,窗口字段是 1000。这就表明,从 701 号算起,发送此报文段的一方还有接收 1000 (字节序号是 701 ~ 1700) 个字节的数据的接收缓存空间。

校验和 TCP Checksum:占 2 个字节。由发送端填充,接收端对 TCP 报文段执行 CRC 算法,以检验 TCP 报文段在传输过程中是否损坏,如果损坏这丢弃。检验范围包括首部和数据两部分,这也是 TCP 可靠传输的一个重要保障。

紧急指针 Urgent Pointer:占 2 个字节。仅在 URG = 1 时才有意义,它指出本报文段中的紧急数据的字节数。当 URG = 1 时,发送方 TCP 就把紧急数据插入到本报文段数据的最前面,而在紧急数据后面的数据仍是普通数据。因此,紧急指针指出了紧急数据的末尾在报文段中的位置。

到这里我们就介绍完了TCP报文段结构,那么他们到底是怎么连接的呢?我们继续看

3,TCP的连接
TCP 的整个交流过程可以总结为:先建立连接,然后传输数据,最后释放链接。下面看一张图

这张图的上部分是建立连接的过程,以及连接中客户端和服务端的状态变化。也就是我们之前提到的三次握手机制。但是实际上网络上的传输是没有连接的,TCP 也是一样的。而 TCP 所谓的「连接」,其实只不过是在通信的双方维护一个「连接状态」,让它看上去好像有连接一样。

连接过程

最初的时候,两端都处于 CLOSED 的状态,然后服务器打开了 TCP 服务,进入 LISTEN 状态,监听特定端口,等待客户端的 TCP 请求。

第一次握手: 客户端主动打开连接,发送 TCP 报文,进行第一次握手,然后进入 SYN_SEND 状态,等待服务器发回确认报文。

这时首部的同步位 SYN = 1,同时初始化一个序号 Sequence Number = J。注意,这个报文段中不包含确认号,也没有定义窗口大小。只有当一个报文段中包含了确认号时,定义窗口大小才有意义。

TCP 规定:SYN 报文段不能携带数据,但会消耗一个序号。

第二次握手: 服务器收到了 SYN 报文,如果同意建立连接,则向客户端发送一个确认报文,然后服务器进入 SYN_RCVD 状态。

这时首部的 SYN = 1,ACK = 1,而确认号 Acknowledgemt Number = J + 1(我希望你从这个地方给我传数据),同时也为自己初始化一个序号 Sequence Number = K。

这个报文主要有两个作用:首先,他是另一个通信方向上的SYN报文段,服务器使用这个报文段来初始化它的初始序号,以便从服务器向客户端发送字节。其次,服务器还通过ACK标志确认已经收到了来自客户端的SYN报文段,同时给出期望从客户端接受的下一个序号。因为这个报文段包含了确认号,所以需要定义窗口大小,供客户端使用。(主要用于流量控制,后面介绍)

SYN+ACK报文段不携带数据,但是会消耗一个序号

第三次握手:

客户端收到了服务器发过来的确认报文,还要向服务器给出确认,然后进入 ESTABLISHED 状态。

这时首部的 SYN 不再置为 1,而 ACK = 1,确认号 Acknowledgemt Number = K + 1(我希望你从这个地方给我传数据),序号 Sequence Number = J + 1。

第三次握手,一般会携带真正需要传输的数据,当服务器收到该数据报文的时候,就会同样进入 ESTABLISHED 状态。 此时,TCP 连接已经建立。

对于建立连接的三次握手,主要目的是初始化序号 Sequence Number,并且通信的双方都需要告知对方自己的初始化序号,所以这个过程也叫 SYN。

这个序号要作为以后的数据通信的序号,以保证应用层接收到的数据不会因为网络上的传输问题而乱序,因为TCP 会用这个序号来拼接数据。

4,释放连接,也就是我们经常说的四次挥手。
TCP 有一个特别的概念叫做半关闭,意思是说连接的一方可以停止发送数据,但是可以接受数据。TCP 的连接是全双工(可以同时发送和接收)的连接,因此在关闭连接的时候,必须关闭传送和接收两个方向上的连接。

客户端给服务器发送一个携带 FIN 的 TCP 结束报文段,然后服务器返回给客户端一个 确认报文段,同时发送一个 结束报文段,当客户端回复一个 确认报文段 之后,连接就结束了。

释放连接过程

在结束之前,通信双方都是处于 ESTABLISHED 状态,然后其中一方主动断开连接。

下面假如客户端先主动断开连接。为什么是假如呢?因为这个地方不同于我们经常认知的客户端和服务端的概念,主动断开连接的一方就是客户端,被动的是服务端。

第一次挥手:

客户端向服务器发送结束报文段,然后进入 FIN_WAIT_1 状态。

此报文段 FIN = 1, Sequence Number = M。

如果FIN报文段不携带数据,只消耗一个序列号

第二次挥手:

服务端收到客户端的结束报文段,然后发送确认报文段,进入 CLOSE_WAIT 状态。

此报文段 ACK = 1, Sequence Number = M + 1。

客户端收到该报文,会进入 FIN_WAIT_2 状态。

第三次挥手:

同时服务端向客户端发送结束报文段,宣布另一个方向的连接正在关闭连接。然后进入 LAST_ACK 状态。

此报文段 FIN = 1,Sequence Number = N。

第四次挥手:

客户端收到服务端的结束报文段,然后发送确认报文段,进入 TIME_WAIT 状态,经过 之后,自动进入 CLOSED 状态。

此报文段 ACK = 1, Sequence Number = N + 1。

服务端收到该报文之后,进入 CLOSED 状态。

关于 TIME_WAIT 过渡到 CLOSED 状态说明:

从 TIME_WAIT 进入 CLOSED 需要经过 2MSL,其中 MSL 就叫做 最长报文段寿命(Maxinum Segment Lifetime),根据 RFC 793 建议该值这是为 2 分钟,也就是说需要经过 4 分钟,才进入 CLOSED 状态。为什么需要这样一个过渡呢?

解释如下:

这里为什么客户端还不能直接close呢?主要是为了防止发送出去的 ACK 服务端没有收到,服务端重发 FIN 再次来询问,如果客户端发完就跑路了,那么服务端重发的时候就没人理他了。

简单的描述就是

Client: 服务端大哥,我事情都干完了,准备撤了,这里对应的就是客户端发了一个FIN

Server:知道了,但是你等等我,我还要收收尾,这里对应的就是服务端收到 FIN 后回应的 ACK

经过上面两步之后,服务端就会处于 CLOSE_WAIT 状态。过了一段时间 Server 收尾完了

Server:小弟,哥哥我做完了,撤吧,服务端发送了FIN

Client:大哥,再见啊,这里是客户端对服务端的一个 ACK

到此,关于TCP常见的理论部门我们都已经介绍完了,但是对于TCP的学习还远远没有结束,它是一个超级复杂的协议,但又是互联网的基石,需要吾辈继续去探索。

路漫漫其修远兮,吾将上下而求索

下篇文章我们会介绍一些TCP在工作中的应用,敬请期待《求求你不要在问我三次握手了(三)》