1、定义
TCP,Transmission Control Protocol
,即传输控制协议。作为传输层中最重要的协议,因为其可靠传输的特点,在互联网中的作用可谓是方方面面。使用的TCP的应用层协议包括HTTP、HTTPS、FTP、SMTP、DNS
协议等,而使用了TCP的应用场景包括:浏览器、文件传输、邮件......但凡需要可靠传输的场景,都离不开TCP协议。
2、特点
-
TCP是面向连接的。应用程序在使用TCP之前,必须先建立链接。在传送数据完毕后,必须释放已经建立的连接。就跟我们平时打电话一样,通话前需要先拨号建立连接,通话结束后需要挂机释放连接。
-
TCP提供可靠交付的服务。通过TCP传送的数据,保证不丢失、不重复、无差错。
-
TCP的连接是点对点的。每一条TCP连接只能有两个端点,这个端点叫做套接字(socket)。套接字的表示方式是点分十进制的IP地址后面拼接上端口号,中间用冒号或者逗号分隔,例如:
192.168.4.5:80
,就表示一个套接字。 -
TCP是面向字节流的。流,指的是流入到进程或从进程流出的字节序列。面向字节流是指:虽然应用程序和TCP的交互是一次一个数据块,但是TCP把应用层传递过的数据仅仅是看成一连串无结构的字节流,放置在发送缓冲区中,如果数据太大,则需要分片后再进行发送。发送的数据大小跟应用层过来的报文大小没有任何关系,所以说TCP是面向字节流的。而作为对比的UDP,它没有缓冲区,应用层过来的报文数据会直接添加头部交给网络层,由网络层负责分片,所以UDP是面向报文的。
3、TCP的首部
TCP的首部占用20~60
个字节,其中有20
个字节是首部固定的长度。
源端口、目的端口
和UDP一样,各占两个字节。
序号
占4
字节。在传输的过程中,每一个字节都会有一个编号。在建立连接后,序号代表:这一次传递给对方的TCP数据部分的第一个字节的编号。
确认号
占4
字节。在建立连接后,确认号代表:期望对方下一次传过来的TCP数据部分的第一个字节的编号。
数据偏移
占用4
位,取值范围是0x0101~0x1111
。数值乘以4之后就是首部的长度了,可得首部的长度就是20~60
字节。将首部长度叫做数据偏移很好理解,首部是排列在数据部分的前面的,数据部分偏移的量就是首部的长度。
保留位
占用6
位,目前全为0。
窗口
占2字节。用于流量控制,告知对方下一次允许发送的数据大小(字节为单位)。
检验和
和UDP一样,检验和的计算方式:伪首部 + 首部 + 数据部分。伪首部只在计算检验和时起作用,不会传递给网络层。
紧急指针
占2
字节。指报文段中的紧急数据的字节数。因为紧急数据排列在普通数据的前面,所以紧急指针也指出了紧急数据末尾在报文段中的位置。需要注意的是,紧急指针尽在URG=1
时才起作用。
选项
长度可变,最长为40
个字节。用来存储其他辅助数据,例如选择确认之类的数据。
标志位
6个标志位可以说是TCP首部最核心的数据,用来说明本报文段的性质。
URG:Urgent
, 紧急位。当URG=1
时,紧急指针部分的数据才生效,说明报文数据中有些部分需要紧急处理。
ACK: Acknowledgment
,确认位。当ACK=1
时,确认号字段才有效。
PSH:Push
,推送位。当两个进程进行交互式通信时,有时候希望在键入一个命令后能够立刻得到对方的响应,这时候可以设置PSH=1
,这样接收方收到PSH=1
的报文时,就会尽快将其推送给应用进程,及时作出相应,而不是等接收缓存填满了再向上交付。
RST:Reset
,复位。当RST=1
时,表明连接中出现严重差错,必须释放连接,然后再重新建立连接。
SYN:Synchronization
,同步位。在连接建立时用来同步序号。当SYN=1、ACK=0
时,表明这是一个建立连接的请求。若对方同意建立连接,则回复SYN=1、ACK=1
。
FIN:Finish
,终止位。当FIN=1
时,表明数据已经发送完毕,要求释放连接。
TCP的首部为什么这么复杂?
一切都是为了数据的可靠传输。
4、TCP的几个基本问题
4.1 可靠传输
TCP的很重要的特点之一就是可靠传输。我们的信道一般是不可靠的,如何在不可靠的传输信道上实现可靠传输?下面从最简单的停止等待协议讲起。
停止等待ARQ协议
停止等待:就是每发送完一个分组数据后就停止发送,等待对方的确认。在收到确认之后再发送下一个分组数据。下面讲述停止等待协议需要处理的四种情况。
a) 在无差错的情况下,A每发送一个分组数据,都能得到B的确认,循环往复。
b) 如果A发送给B的数据出现差错,B将丢弃A的数据,或者B压根就没收到A发送的数据,B将不会给A发送确认。A在等待一定的时间后如果还没有收到来自B的确认,A将触发超时重传机制。
c)如果B确实收到了A的数据,但是B给A发送的确认在中途丢失了,A在经过一定时间之后,依然没有收到B的确认,A将触发超时重传,B将再一次收到来自的A的相同数据,但是这个数据B将会丢弃掉,仅重新给A发送确认。
d)如果B确实收到了A的数据,B给A发送的确认也没有丢失,但是因为路由和延迟的原因,B给A发送的确认迟迟未能传达,A在经过一定时间之后,将触发超时重传,B将再一次收到来自的A的相同数据,但是这个数据B将会丢弃掉,仅重新给A发送确认。经过一段时间后,最开始那个迟到的确认终于传达给A,但A收到确认后什么也不做,因为刚才已经处理过一次B的确认了。
使用上面的确认和重传机制,我们就可以在不可靠的信道上实现可靠传输,但是这种机制也有一个明显的问题:发送一个分组数据后,就得等待确认,然后再发送另一个分组数据,就像单线程操作,传输效率很低。
针对这种情况,我们介绍下面的连续ARQ协议和滑动窗口协议。
连续ARP协议 + 滑动窗口协议
为了提高传输效率,我们可以使用流水线式传输。发送方可以一次连续发送多个分组数据,不必每发送一个分组数据就停顿下来等待确认,这样可使信道上一直有数据不间断的在传送。这就是连续ARQ协议。
发送方规则:维持一个窗口,位于发送窗口内的分组都可以连续发送出去,而不需要等待对方的确认。
接收方规则:采用累积确认的方式,对接收到的分组不必要逐个发送确认,而是在收到几个分组后,对按序到达的最后一个分组发送确认。这就表示,这个分组(包括)之前的所有分组都已经正确收到了。
窗口滑动规则:假设上次接收到的确认序号减去本次的确认取号差是4
。发送方收到本次确认,就把窗口向前移动4
个分组的位置。
在TCP通信过程中,如果发送序列中间某个数据包丢失(比如1、2、3、4、5中的3丢失了),TCP会通过重传最后确认的分组后续的分组(最后确认的是2,会重传3、4、5)。这种操作也叫做Go-back-N
(回退N),表示需要再退回来重传已经发送过的N个分组。这样原先已经正确传输的分组也可能重复发送(比如4、5),降低了TCP性能。
为了改善上述情况,发展出了SACK
(Selective acknowledgment),选择性确认技术。
选择性确认SACK
选择性确认技术:告诉发送方哪些数据丢失,哪些数据已经正确收到,使得发送方只需要重新发送已丢失的包,而不用重新发送丢失包后面的所有包。
SACK
信息会放在TCP首部的选项部分。
Kind:占1
字节。值为5
代表这是SACK
选项。
Length:占1
字节。表明SACK
选项一共占用多少字节。
Left Edge:占4
字节,左边界。
Right Edge:占4
字节,右边界。
举个栗子方便理解左右边界:
假设接收方的接收窗口是1000个字节,发送方将1000个字节分成10个分组发送。途中棕色的表示接收方接收到的分组,白色的代表未收到的分组。
1、201
左边的数据都准确接收到了,所以确认号是201
,下次发送方从201
开始发送分组。
2、[301,401)、[501,601)、[701,801)、[901,1001)
表示分别用左右边界描述已经接收到的分组片段,注意左闭右开。接收方将这些左右边界信息告诉发送方,发送方就知道应该重发哪些分组了。
一对边界信息(左边界 + 右边界)需要占用8
字节,而TCP首部的选项部分最多40
字节,所以SACK
选项最多携带4
组边界信息。
SACK
选项的最大占用字节数:4 * 8 + 2 = 34
。
💡疑问
1、如果一个包重传了N还是还是失败,会一直重传直到重传成功么?
A:这个取决于系统的设置。比如有些系统在重传5次依然失败后,会发送Reset报文,重新建立连接。
2、如果接收窗口最多能接收四个分组,但是发送方本次只发送了两个分组,接收方如何知道后面是否还有两个分组?
A:接收方等待一段时间后,如果还没有收到后面的两个分组,则向发送方发送收到了两个包的确认报文。
3、传输层和网络层都具有将数据分片的能力,为什么在传输层就将数据大卸八块,而不是在网络层再进行分片?
A:因为可以提高重传的性能。需要明确的是,可靠传输是在传输层控制的。如果在传输层没有将数据分片,一旦数据丢失,则需要重传整个传输层的数据。如果在传输层分片,只需要重传丢失部分的片段即可。
4.2 流量控制
一般来说,我们总是希望数据传输得更快一些,但如果发送方把数据发送得过快,接收方就可能来不及接受,这就会造成数据的丢失。所谓流量控制,就是让发送方的发送速率不要太快,要让接收方来得及接收。
原理:
- 通过确认报文中的窗口字段控制发送方的窗口大小,控制发送方的发送速率。
- 发送方的窗口大小不能超过接收方给出的窗口大小。
- 若接收方给出的窗口大小为0,发送方就会停止发送数据。
下图的例子说明了:如果控制发送窗口的大小来进行流量控制。
假设建立连接时,B给出的接收窗口rwnd=400
。图中一共进行了三次流量控制,第一次rwnd=300
,第二次rwnd=100
,第三次rwnd=0
。
💡疑问
接收方给发送方发送零窗口报文之后,后面又有了一些存储空间,于是给发送方发送非零窗口报文,但这个非零窗口报文在发送过程中丢失了。这样发送方一直在等待接收方的非零窗口报文,接收方也一直在等待发送方的数据,双方就陷入了僵局。
为了解决这个问题,发送方在收到接收方的零窗口报文后,首先会停止向接收方发送数据,同时也会启动一个计时器,每隔一段时间就去询问接收方的窗口大小,直到接收方的窗口大小不为零。
4.3 拥塞控制
作用:防止过多的数据注入到网络中,避免网络中的路由器或链路过载。
网络上的拥塞和日常交通的拥塞比较类似。观察下面这张图:
假设网络上最多能同时传输一千份数据,理想的情况下,即便你同时往网络上传输一万分,十万份,能传输的数据依然是一千份数据。但实际情况是,吞吐量达到一定程度之后,随着链路上传输的数据越来越多,传输速率将越来越慢,到最后将完全堵死。
就跟我们的日常交通一样,拥塞控制是一个全局性的过程,涉及到所有的主机、路由器,以及与降低网络传输性能有关的所有因素,是大家共同努力的结果。而反观流量控制,是点对点之间的控制。
拥塞控制的算法有四种:慢开始、拥塞避免、快重传、快恢复。
慢开始
慢开始,顾名思义,慢慢地开始。当主机开始发送数据时,由于并不清楚网络的负荷情况,如果立即把大量数据注入到网络,就有可能引起网络拥塞。经验证明,较好的方法是先探测一下,由小到大逐渐增大发送窗口,即由小到大逐渐增大拥塞窗口数值。
MSS:Maximum Segment Size
,每个段最大的数据部分大小,在建立连接时确定。
swnd:send window
,发送窗口。
rwnd:receive window
,接收窗口。
cwnd:congestion window
,拥塞窗口。
其中,发送窗口的大小等于接收窗口和拥塞窗口的最小值:swnd = min(cwnd,rwnd)
。
cwnd
的初始值比较小,然后随着数据包被接收方确认(收到ACK
), cwnd
就呈指数级增长。
拥塞避免
拥塞避免,指的是拥塞窗口指数增长到了一定阈值之后,就转换成线性增长的方式,缓慢增大,防止网络过早出现拥塞。
ssthresh:slow start threshold
,慢开始阈值,cwnd
达到阈值后,以线性方式增加。
加法增大:拥塞窗口缓慢增大,以防止网络过早出现拥塞。
乘法减小:只要网络出现拥塞,把ssthresh
减为拥塞峰值的一半,同时执行慢开始算法,cwnd
恢复到初始值。 当网络出现频繁拥塞时,ssthresh
就下降得很快。
快重传
真假网络拥塞
有时候,个别报文段会在网络中丢失,但实际上网络并未发生拥塞。如果发送方迟迟收不到确认,就会产生超时,就会误认为网络发生了拥塞。这就导致发送方错误地启动慢开始,把cwnd
又设置为1
,降低了传输效率。
快重传算法
-
对于接收方,每收到
1
个失序的分组后就立即发出重复确认,使发送方及时知道有分组没有到达,而不要等待自己发送数据时才进行确认。 -
对于发送方,只要连续收到
3
个重复确认(3-ACK
),就应当立即重传对方尚未收到的报文段,而不必继续等待重传计时器到期后再重传。
如上图所示,接收方收到了M1、M2,并发出了确认报文,但是M3丢失了。假设后面接收方又收到了M4,按照快重传原则,接收方应该立刻发送对"上次成功接收的数据"(M2)的确认。同理,后面接收到M5、M6后,也应该立刻发送对M2的确认。发送方收到3
个连续的对M2的确认,就知道M3已经丢失了。赶紧立刻重新发送M3。这样就不会出现发送超时,发送方也就不会误认为发生了网络拥塞。
快恢复
发送方知道现在只是丢失了个别的报文段,于是不启动慢开始,而是执行快恢复算法。
- 先执行乘法减小算法,把
ssthresh
减为拥塞峰值的一半。并把cwnd
值设置为新的ssthresh
值。 - 与慢开始不同之处是,现在不执行慢开始算法,即
cwnd
现在不恢复到初始值,而是把cwnd
值设置为新的ssthresh
值。然后开始执行拥塞避免算法(加法增大),使cwnd
缓慢地线性增大。
需要注意的是,快重传和快恢复是TCP Reno
版本的特性,区别于老版本TCP Tahao
。
下面这个图综合体现了慢开始、拥塞避免、快重传、快恢复。
根据上面所述,TCP的拥塞控制可以归纳为下面的流程图。
5、连接管理
TCP建立连接的过程要解决如下的一些问题:
- 要使每一方都知道对方的存在,确定对方的收发能力正常。
- 双方协商一些参数,比如:MSS、是否使用SACK、窗口大小、窗口缩放系数等。
5.1 三次握手
TCP建立连接的过程叫做握手,握手需要在客户端和服务器之间交换三个TCP报文段。下图画出了三报文握手建立TCP连接的过程。
CLOSED
:处于关闭状态
LISTEN
:服务器处于监听状态,等待客户端连接
SYN-SENT
:表示客户端已发送SYN
报文,等待服务器的第2次握手
SYN-RCVD
:表示服务器接收到了SYN
报文。当继续收到客户端的ACK
报文后,它会进入到ESTABLISHED
状态
ESTABLISHED
:表示连接已建立
💡疑问
1、为什么一定要三次握手?两次行不行?
可能有人会想,客户端向服务器请求建立连接,服务端同意连接,并且服务端也向客户端请求建立连接,客户端同意连接后直接建立连接不就行了吗,这样两次握手就行了。为什么还要给服务端一个确认,要进行三次握手呢?
从节省资源的角度看,这样可以防止服务端一直等待,浪费资源
假设有这么一种情况。客户端第一次向服务端请求建立连接时,因为网络超时的原因触发重传,于是客户端第二次向服务端请求建立连接。这一次建立连接、传送数据、断开连接都很顺利。连接断开后许久,之前在某个网络节点滞留的第一次请求终于到达了服务端,本来这是一个已经失效的请求,但是服务端会认为这是客户端新建立的请求,于是向客户端发出确认。如果只需要两次握手,服务端将进入连接状态。但是客户端本质上并没有想建立连接,所以在收到服务端的确认之后,它什么也不会做。而服务端误认为已经进入了连接状态,还在一直等待客户端发送数据,白白浪费了资源。
如果采用三次握手,服务端在给出客户端的确认后,发现没有继续收到客户端的确认,就知道客户端并没有想建立连接。
从确认双方的收发能力角度看
1、客户端向服务端发起连接请求,若服务端能收到,则服务端知道自己的收信能力没有问题。
2、服务端接着向客户端发送确认,若客户端能收到,则客户端知道自己的发信能力和收信能力都没有问题。
3、此时,服务端还不知道自己的发信能力是否正常,所以客户端需要向服务端发送确认,服务端收到后,就知道自己的发信能力没有问题了。
以上就是需要三次握手的原因。
💡 第三次握手失败了,会怎么处理?
第二次握手之后,服务端的状态为SYN-RCVD
,若等不到客户端的ACK
,服务端会重新发送SYN+ACK
包。如果服务端多次重发SYN+ACK
都等不到客户端的ACK
,就会发送RST
包,强制关闭连接。
5.2 四次挥手
数据传输结束后,通信的双方都可以释放连接。现在A和B都处于ESTABLISHED
状态。
FIN-WAIT-1
:表示想主动关闭连接。向对方发送了FIN
报文,此时进入到FIN-WAIT-1
状态。
CLOSE-WAIT
:表示在等待关闭。当对方发送FIN
给自己,自己会回应一个ACK
报文给对方,此时则进入到CLOSE-WAIT
状态。在此状态下,需要考虑自己是否还有数据要发送给对方,如果没有,发送FIN
报文给对方。
FIN-WAIT-2
:只要对方发送ACK
确认后,主动方就会处于FIN-WAIT-2
状态,然后等待对方发送FIN
报文。
LAST-ACK
:被动关闭一方在发送FIN
报文后,最后等待对方的ACK
报文。当收到ACK
报文后,即可进入CLOSED
状态了。
TIME-WAIT
:表示收到了对方的FIN
报文,并发送出了ACK
报文,就等2MSL
后即可进入CLOSED
状态了。如果FIN-WAIT-1
状态下,收到了对方同时带FIN
标志和ACK
标志的报文,可以直接进入到TIME-WAIT
状态,而无须经过FIN-WAIT-2
状态。
客户端发送ACK后,需要有个
TIME-WAIT
阶段,等待一段时间后,再真正关闭连接。一般是等待2倍的MSL(Maximum Segment Lifetime,最大分段生存期)。MSL是TCP报文在Internet上的最长生存时间。每个具体的TCP实现都必须选择一个确定的MSL值,RFC 1122建议是2分钟。可以防止本次连接中产生的数据包误传到下一次连接中(因为本次连接中的数据包都会在2MSL时间内消失)
如果客户端发送ACK后马上释放了,然后又因为网络原因,服务端没有收到客户端的ACK,服务端就会重发FIN。
这时可能出现的情况是:
- 客户端没有任何响应,服务器那边会干等,甚至多次重发FIN,浪费资源。
- 客户端有个新的应用程序刚好分配了同一个端口号,新的应用程序收到FIN后马上开始执行断开连接的操作,本来它可能是想跟服务端建立连接的。
💡疑问
1、为什么释放连接的时候,要进行4次挥手?
因为TCP是全双工模式。通信中的两个主机都需要断开连接。主机1向主机2发送FIN,只表明FIN已经没有数据要发给主机2了,主机2还是可以向主机1发送数据的。
第1次挥手:
当主机1发出FIN报文段时,表示主机1告诉主机2,主机1已经没有数据要发送了,但是,此时主机1还是可以接受来自主机2的数据。
第2次挥手:
当主机2返回ACK报文段时,表示主机2已经知道主机1没有数据发送了,但是主机2还是可以发送数据到主机1的。
第3次挥手:
当主机2也发送了FIN报文段时,表示主机2告诉主机1,主机2已经没有数据要发送了。
第4次挥手:
当主机1返回ACK报文段时,表示主机1已经知道主机2没有数据发送了。随后正式断开整个TCP连接
2、为什么有时候在使用抓包工具的时候,只会看到3次挥手?
这其实是将第2、3次挥手合并了。当服务端接收到客户端的FIN时,如果服务端后面也没有数据要发送给客户端了,客户端就可以将第2、3次挥手合并,同时告诉客户端两件事:
- 已经知道客户端没有数据要发了
- 服务端也没有数据要发了
6、总结
本文主要介绍了TCP协议的几个核心知识,包括TCP的头部、几个问题基本问题、连接管理等,希望能给大家带来帮助。接下来的文章将继续帮助大家、也帮助自己构建网络知识体系。