概述
TCP,传输控制协议(Transmission Control Protocol),是一种面向连接的、可靠的、基于字节流的传输层通信协议。前面我们了解过同为传输层协议的 UDP,那么这两者的区别是什么呢?
与 UDP 区别
- 连接方式不同,UDP 面向无连接;TCP 面向有连接。
- 传输方式不同,UDP 面向报文,直接将报文发给网络层;TCP 面向字节流,会将报文按照 MTU 进行分包后再发给网络层。
- 可靠性不同,UDP 没有重发机制,传输不可靠;TCP 有重发机制,传输可靠。
- 传输速度不同,UDP 传输速度快;TCP 传输速度慢。
连接过程
三次握手
在学习 HTTP 时,我们提到过一个 HTTP 请求发送前需要建立 TCP 连接,而一个 TCP 连接的建立需要三次握手,如图所示:
这里会有一个问题,为什么 TCP 需要三次握手,而不是两次或者四次?
网上的回答都很抽象,这里说下个人理解:
在我个人看来,TCP 的三次握手是由 TCP 的可靠性(发送端发送的数据,接收端必须要返回一个 ACK)和连接阶段的 MSS(最大消息长度,Maximum Segment Size)及序列号协商(客户端和服务器端要轮流作为发送端)来保证的。在连接时,要就本次连接的 MSS 及序列号,客户端和服务器端进行协商,协商就是要两端都知道对端想要多少值,如果这样,连接过程就是这样:
- 客户端作为发送端,告诉服务器端他的 MSS;
- 服务器端作为接收端,向发送端回复 ACK;
- 服务器端作为发送端,告诉客户端他接受的 MSS;
- 客户端作为接收端,向发送端回复 ACK; 这样就需要四次握手,但是2,3同为服务器端到客户端是可以合并到一起的,也就是说只需要三次握手。
这样,TCP 的三次握手就可以这样理解:
- 客户端:我想跟你建立连接同时告诉你我的 MSS。(发送端)
- 服务器端:知道了,也告诉你我的 MSS。(接收端,发送端)
- 客户端:知道了。(接收端)
这样的话,如果只有两次是没办法进行协商的,如果四次就浪费了。
状态变化
TCP 首部格式
由上图可以看出 TCP 首部格式,有 12 项:源端口号,目标端口号,序列号,确认应答号,数据偏移,保留,控制位,窗口大小,校验和,紧急指针,选项,填充。
源端口号、目标端口号
表明发送端及接收端端口号,各 16 位。
序列号
长度32位,建立连接时,发送端生成初始值,通过 SYN 包发送给接收端,没发送一次数据,该数据就会累加一次。
确认应答号
长度32位,指下次应该收到的数据的序列号。(抓包时有的 TCP 包应答号不变化)
数据偏移
长度4位,表示 TCP 所传输的数据部分应该从哪个位开始算起,也可以看做是首部长度。
保留
长度4位,为了以后扩展时使用,一般设置为0
控制位
长度8位,从左至右依次为 CWR、ECE、URG、ACK、PSH、RST、SYN、FIN。
窗口大小
长度16位,用于通知从相同 TCP 首部的确认应答号所指位置开始能够接收的数据大小(8位)。TCP 不允许发送超过窗口大小的数据。
校验和
长度16位,与 UDP 检验和类似,但无法关闭。
紧急指针
长度16位,只在 URG 控制位为 1 时有效,从数据首位到紧急指针所指示的位置为止为紧急数据。
选项
长度最大40字节,
TCP 的特点
可靠性
序列号与确认应答
简单来说就是发送端发送的报文,接收端收到之后需要向发送端发送确认应答(ACK),发送端收到确认应答,就说明数据已成功到达对端。
如果在一定时间内没有收到,这就可能存在两种情况:
- 发送端发送的报文没有到达接收端;
- 接收端的确认应答没有到达发送端;
重发超时的确定
重发超时是指在重发数据之前,等待确认应答到来的特定时间间隔。如果超过了这个时间仍未收到确认应答,发送端将进行数据重发;最理想的重发超时是能保证确认应答一定在这个时间内返回,随着网络情况不同,重发超时会进行调整。
TCP 在每次发包时都会计算往返时间(RTT,指报文段的往返时间)及其偏差,重发超时取往返时间及偏差之和稍大点的值。
最初的重发超时一般设置在6s左右,如果数据被重发后仍然没有应答,等待应答的时间将会以2倍,4倍的指数函数增长。达到一定重发次数之后,如果仍没有确认应答,就会判断为网络或对端主机异常,强制关闭连接。
以段为单位
MSS(Maximum Segment Size):最大消息长度,TCP 报文长度的最大值,最理想情况下为不会被 IP 分片的最大数据长度(不包含首部长度),MSS 在 TCP 三次握手时进行协商。接收端在接收到数据包之后,回复的确认应答包头部的序列号指示了下次应当接受的数据的序列号。
窗口大小
TCP 以段为单位进行发送数据,但是如果在 RTT 较长时会非常影响传输性能,为了解决这个问题,TCP 引入了窗口大小来解决。
窗口大小,即无需等待确认应答而可以继续发送数据的最大值。
窗口下的重发机制
在 TCP 的可靠性中,对于未完成的应答存在两种情况,TCP 对着两种情况的处理稍有不同:
- 报文没有到达接收端:接收端会发送三次相同序列号的确认应答包,发送端接收到三次相同序列号的确认应答之后,就会重发响应的数据包。
- 确认应答没有到达发送端:接收端在接收到数据包后回复带有下个数据包序列号的确认应答,如果该确认应答丢失,而下个数据包的确认应答没有丢失,成功被接收,则发送端认为丢失的确认应答对应的数据包已经被成功接收,如果确认应答全部丢失,理论上会触发超时。
流量控制
在实际情况中,接收端可能存在处理其他任务耗费时间等问题,导致无法对发送的包处理;或者在高负荷状态下无法接收任何数据;如果发送端接着发送数据,就会造成流量的浪费,流量控制就是为了解决这类问题。
接收端向发送端发送自己能够接收的数据大小,发送端不会发送超过这个值的数据,这个值就被称为窗口大小。接收端缓冲区的值一旦面临数据溢出时,窗口大小的值也会随之被设置为更小的值,从而控制数据发送量。
拥塞控制
流量控制是对接收端能够接受的最大数据量的控制,而拥塞控制是对网络能够承载的最大数据量进行的控制。
拥塞控制也是通过窗口大小来控制,拥塞控制的窗口叫做拥塞窗口。实际过程中发送的窗口大小由拥塞窗口及滑动窗口共同决定(取其中最小值)。
拥塞控制主要靠超时及重复确认应答来控制。
慢启动
在数据刚开始传输时由于不知道网络所能承载的数据量大小,所以存在一个慢启动的机制,在慢启动时,拥塞窗口大小为 1,即每次发送一个报文段,发送端每接收到一次确认应答,拥塞窗口大小增加一倍,即呈 1,2,4,8,... 指数形式增长,这样可以快速将网络填满。如果期间发生了丢包,会进行相应的处理:
- 超时:将拥塞窗口调整为 1,慢启动阈值调整为当前窗口大小的一半;
- 重复确认应答:将拥塞窗口调整为当前窗口大小的一半+3,慢启动阈值调整为当前窗口的一半;
慢启动阈值:当窗口大小超过此阈值后,将以线性增长的方式对拥塞窗口大小进行调整,初始值为滑动窗口大小。
四次挥手
虽说都是说是四次挥手但是不一定需要四次,至少我抓的大部分包都是三次挥手。
- 主动端向被动端发送 FIN,表明主动端数据发送完毕,进入
FIN_WAIT_1。 - 被动端回复 ACK,但仍然可能有未发送的数据,进入
CLOSE_WAIT,主动端收到 ACK 进入FIN_WAIT_2。 - 被动端在发送完毕后,向主动端发送 FIN,表示数据发送完毕,进入
LAST_ACK。 - 主动端接收到 FIN,进入
TIME_WAIT,被动端收到 ACK 后进入CLOSED。