在前面的文章中我们已经学习了可靠数据传输的基本原理,我们就可以转而学习 TCP
了,它 是因特网传输成的面向链接的可靠的传输协议。
TCP 连接
TCP
被称为是面向连接的,这是因为在一个应用进程可以开始向另一个应用进程发送数据之前,这两个进程必须先相互 握手
,即它们必须相互发送某些预备报文段,以建立确保数据传输的参数。
TCP
连接提供的是全双工服务,如果一台主机上的进程 A
与另一台主机上的进程 B
存在一条 TCP
连接,那么应用层数据就可以从进程 B
流向进程 A
的同时,也从进程 A
流向进程 B
,TCP
的链接也中是点对点的,即在单个发送方与单个接收方之间的连接。
TCP
会在数据通信之前,通过 TCP
首部发送一个 SYN
包作为建立连接的请求等待确认应答,如果对端发来确认应答,则认为可以进行数据通信,如果对端的确认应答未能到达,就不会进行数据通信。此外,在通信结束时会进行断开连接的处理,即服务端向客户端发送一个 FIN
包。
TCP以端为单位发送数据
在建立 TCP
连接的同时,也可以确定发送数据报的单位,我们也可以称其为 最大消息长度(MSS)
最理想的情况是,最大消息长度正好是 IP
中不会被分片处理的最大数据长度。
TCP
在传送大量数据时,是以 MSS
的大小将数据进行分割发送,进行重发也是以 MSS
为单位。
MSS
是在三次握手的时候,在两端主机之间被计算得出。两端的主机在发出简历连接的请求时,会在 TCP
首部中写入 MSS
选项,告诉对方自动的接口能够适应的 MSS
的大小,然后会在两者之间选择一个较小的值投入使用。
为附加 MSS
选项,TCP
首部将不再是 20 字节,而是 4 字节的整数倍。
TCP报文段结构
在上面的内容中讲到 MSS
限制了报文段数据字段的最大长度,当 TCP
发送一个大文件,例如某 Web
页面上的一个图像时,TCP
通常是将该文件划分为长度为 MSS
的若干块。
上图展示了 TCP
首部的格式,TCP
首部相比 UDP
首部要复杂得多。另外TCP
中没有表示包长度和数据长度的字段,可由 IP
层获知 TCP
的包长由 TCP
的包长可知数据的长度。
我们接下来看这些首部字段都包含了什么意义:
源端口号
: 表示发送端的端口号,字段长 16 位;目标端口号
: 表示接收端端口号,字段长 16 位;序列号
: 字段长32
位,序列号有时也叫序号,是指发送数据的位置,每发送一次数据,就累加一次该数据字节数的大小。序列号不会从0
或者1
开始,而是在建立连接时由计算机生成的随机数作为其初始值,通过SYN
包传给接收端主机,然后将每转发过去的字节数累加到初始值上表示数据的位置。此外,在建立连接和断开连接时发送的SYN
包和FIN
包虽然不携带数据,但是也会作为一个字节增加对应的序列号;确认应答号
: 确认应答号字段长度32
位,是指下一次应该接收到的数据的序列号,实际上是指已收到确认应答号前一位位置的数据。发送端收到这个确认应答以后可以认为在这个序号一起的数据都被已经被正常接收;数据偏移
: 该最短表示TCP
所传输的数据部分应该从TCP
包在哪个位置开始计算,当然也可以把它看作TCP
首部的长度。该字段长4
位,单位为4
字节,不包含选项字段的话,如上图所示,TCP
的首部为20
字长,因此数据偏移字段可以设置为5
,如果该字段的值为5
,那说明从TCP
包的最一开始到20
字节位置都是TCP
首部,余下的部分为TCP
数据;保留
: 该字段主要是为了以后扩展时使用,其长度为4
位,一般设置为0
,但即使收到的包在该字段不为0
,此包也不会被丢弃;控制位
: 字段长为8
位,每一位从左至右分别为CWR
、ECE
、URG
、ACK
、PSH
、RST
、SYN
、FIN
。这些控制标志也叫做控制位,如下图所示:
窗口大小
:该字段长为16
位,用于通知从相用TCP
首部的确认应答号所指位置开始能够接收的数据大小为8
字节,TCP
不允许发送超过此处所示大小的数据,如果窗口为0
,则表示可以发送窗口探测,以了解最新的窗口大小,但这个数据必须是1
字节;校验和
:TCP
的校验和与UDP
相似,区别在于TCP
的校验和无法关闭。TCP
和UDP
一样在计算校验和的时候使用TCP
伪首部,这个伪首部如下图所示,为了让其全长为16
为的整数倍,需要在数据部分里的最后填充0
。首先将TCP
校验和字段设置为0
,然后以16
位为单位进行1
的补码和计算,再将它们总和的1
的补码和放入校验和字段。接收端在收到TCP
数据段以后,从IP
首部获取IP
地址信息构造TCP
伪首部,再进行校验和计算。由于校验和字段里保存着除本字段意外其他部分的和的补码值,因此如果计算校验和字段在内的所有数据的16
位和以后,得出的结果是16
位全部为1
,说明所收到的数据是正确的;
可靠数据传输
在前面的文章中讲过,因特网的网络层服务即 IP
服务是不可靠的,IP
不保证数据报的交付,不保证数据报的按序交付,也不保证数据报中的完整性,对于 IP
服务,数据报能够溢出路由器缓存而永远不能到达目的地,数据报也可能乱序到达,而且数据报中的比特可能损坏,从 0
变为 1
或者相反。由于传输层的报文段是被 IP
数据报携带着在网络中传输的,所以传输层的报文段也会遇到这些问题。
TCP
在 IP
不可靠的尽力而为服务智商创建了一种可靠数据传输服务。TCP
的可靠数据传输服务确保一个进程从其接收缓存中的读出的数据流是无损坏、无间隙、非冗余和按序的数据流,即该字节流和连接的另一方端系统发送出的字节流是完全相同。
超时间隔加倍
TCO
重传具有最小序号的还未被确认的报文段,知识每次 TCP
重传时都会将一下次的超时间隔设为先前的两倍。
因此,超时间隔在每次重传后会呈指数型增长。
快速重传
超时触发重传存在的问题之一是超时周期可能相对较长,当一个报文段丢失时,这种长超时周期迫使发送发延迟重传丢失的分组,因而增加了端到端时延。
幸运的是,发送发通常可在超时事件发生之前通过注意所谓 ACK
冗余来较好地检测到丢包情况。冗余ACK
就是再次确认某个报文段的 ACK
,而发送方先前已经收到对该报文段的确认。
因为发送方经常一个接一个地发送大量的报文段,如果一个报文段丢失,就很可能引起许多一个接一个的冗余 ACK
,如果 TCP
发送方接收到对相同数据的 3
个冗余 ACK
,它把这当做一种指示,说明跟在这个已确认过三次的报文段之后的报文段已经丢失。
是回退 N 步 还是选择重传
TCP
确认是累积式的,正确接收但时序的报文段是不会被接收方确认的,因此 TCP
发送方仅需维持已发送过但未被去人的字节的最小序号和下一个要发送的字节的序号。
在这种意义下,TCP
看起来更像一个 GBN
风格的协议,但是 TCP
和 GBN
协议之间有着一些显著的区别,许多 TCP
实现会将正确接收但失序的报文段缓存起来。
对 TCP
提出的一种修改意见是所谓的 选择确认
,它运行 TCP
接收方 有选择地确认失序报文段,而不是累积地确认最后一个正确接收的有序报文段。
当将该机制与选择重传机制结合起来使用时,那些被被收方选择性确认过的报文段会被跳过,TCP
看起来就很像我们通常的 SR
协议。因此,TCP
的差错恢复机制也许最好被分类为 GBN
协议与 SR
协议的混合体。
流量控制
一条 TCP
连接的每一册主机都为该连接设置了接收缓存,当该 TCP
连接正确、按序的字节后,它就将数据放入接收缓存,相关联的应用程序会从缓存中读取数据,但不必是数据刚一到达就立即读取。
事实上,接收方应用也许正忙于其他任务,甚至要过很长时间后才读取该数据,如果某应用程序读取数据时相对缓慢,而发送方发送得太多、太快,发送的数据就会很容易地使该连接的接收缓存溢出。
TCP
为它的应用程序提供了 流量控制服务
以消除发送方使接收方缓存溢出的可能性,流量控制因此是一个速度匹配服务,即发送方的发送速率与接收方应用程序的读取速率相匹配。
TCP
通过让发送方维护一个称为接收窗口的变量来提供流量控制,通俗地说,接收窗口用于发送发一个指示---该接收方还有多少可用的缓存空间。因为 TCP
是全双工通信,在连接两端的发送方都各自维护一个接收窗口。
TCP
首部中,专门有一个字段用来通知窗口大小,接收主机将自己可以接收的缓冲区大小放入这个字段中通知给发送端,这个字段的值越大,说明网络的吞吐量越高。发送端主机会根据接收端主机的指示,对发送数据的量进行控制,这也就形成了一个完整的流量控制,如下图所示:
在上图中,当接收端收到从 3001
号开始的数据段后其缓存区即满,不得不暂时停止接收数据,之后,在收到发送窗口更新通知后通信才得以继续进行。如果这个窗口的更新通知在传送途中丢失,可能会导致无法继续通信,为避免此类问题的发生,发送端主机会时不时发送一个叫作窗口探测的数据段,此数据段仅含一个字节以获取最新的窗口大小信息。
TCP 连接管理
TCP
提供面向有连接的通信传输,面向有连接是指在数据通信开始之前先做好通信两端之间的准备工作。
在上面的内容中我们已经讲到了 TCP
连接三次握手和四次挥手了,这篇我们就来详细讲讲这个内容,假设运行在一台客户机上的进程想与服务器上的一个进程简历一条连接,客户应用进程首先通知客户 TCP
,它像建立一个与服务器上某个进程之间的连接,客户端中的 TCP
会用以下方式与中服务器的 TCP
建立一条 TCP
连接:
第一步
: 客户端的TCP
首先向服务器端的TCP
发送一个特殊的TCP
报文段。该报文段中不包含应用层数据。但是在报文段的首部中的一个标准位即SYN
被设置为1
。因此这个特殊报文段被称为SYN
报文段。另外客户端随机地选择一个初始序号client_isn
,并将此编号放置于该起始的TCP SYN
报文段的序号字段中,该字段会被封装在一个IP
数据报中并发送给服务器中;第二步
: 一旦包含TCP SYN
报文段中的IP
数据报到达服务器主机,服务器会从该数据中提取出SYN
报文段,为该TCP
连接分配TCP
缓存和变量,并向该客户TCP
发送运行连接的报文段。这个连接允许的报文段不包含应用层数据,但是在报文段的首部却包含了三个重要的信息。首先SYN
被设置为1
,其次TCP
包围的那段首部的确认号字段被设置为client_isn + 1
。最后服务器选择自己的初始序号并将其放置到TCP
报文段首部的序号字段中,该报文段被称之为SYNACK
报文段;第三步
: 在收到SYNACK
报文段后,客户也要给该连接分配缓存和变量。客户主机则向该服务器发送另外一个报文段段。这最后一个报文段对服务器的运行连接的报文段进行了确认。因为连接已经简历了,所以该SYN
比特被设置为0
,该三次握手的第三个阶段可以在报文段负载中写道客户到服务器的数据;
第三次握手的目的是为了确保双方都能够正常地发送和接收数据,并使双方的初始序列号和确认号能够正确匹配,以建立一个可靠的通信连接。通过三次握手,客户端和服务端都确认了彼此的能力和意愿,确保后续的数据传输能够可靠地进行。
一旦完成这三个步骤,客户和服务器主机就可以相互发送包括数据的报文段了,在以后的每一个报文段中,SYN
比特都将被设置为 0
:
天下没有不散的筵席,对于 TCP
连接也是如此,参与 TCP
连接的两个进程中的任何一个都能终止该连接,当连接结束后,主机中的缓存和变量都将被释放。
当客户端和服务器通过 TCP
连接进行通信时,断开连接需要进行四次挥手,,以下是 TCP
四次挥手的详细步骤:
- 第一次挥手: 客户端发送一个
FIN(Finish)
标志给服务器,表示客户端不再发送数据。此时客户端进入FIN_WAIT_1
状态; - 第二次挥手: 服务器接收到
FIN
标志之后,向客户端发送一个ACK
确认标志,表示已经收到了客户端发送的FIN
。同时,服务器自己也发送一个FIN
给客户端,此时服务器进入COLSE_WAIT
状态,客户端进入FIN_WAIT_2
状态; - 第三次挥手: 客户端收到服务器发送的
FIN
后,向服务器发送一个ACK
确认标志,表示已经收到了服务器发送的FIN
,此时客户端进入TIME_WAIT
状态,等待可能迷路的ACK
消息; - 第四次挥手: 服务器收到客户端发送的
ACK
后,确认客户端已经收到了服务器发送的FIN
,并且完成了连接的关闭。服务器进入CLOSED
状态,连接完全关闭;
四次挥手的过程保证了双方都能安全地关闭连接,这是因为 TCP
是全双工通信协议,每个方向上的关闭都需要单独进行处理。客户端和服务器都必须发送自己的 FIN
来关闭连接,并接收对方的 ACK
确认。最后,等待一段时间后才能彻底关闭连接,以确保最后一个ACK的可靠性。
总结
TCP 之所以需要 3
次握手,是因为 TCP
通讯双方都是全双工的,所以要经过 3
次交互才能确认双方的发送能力和接收能力,并且 TCP
握手必须是 3
次,如果是 2
次握手,不能证明服务器端的发送能力和客户端的接收能力;也不能是 4
次握手;因为 3
次已经能证明的事情,再交互握手 1
次完全没有必要。
参考文献
- 书籍:
计算机网络自顶向下方法原书第七版
; - 书籍:
图解 TCP
;