按照国际惯例,了解或者学习一个东西之前,先过一遍这个东西的定义。
TCP:
传输控制协议(英语:Transmission Control Protocol,缩写为TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。( 维基百科 )
TCP
TCP 相关知识点是我们程序员来说必须掌握的一个基本技能点。基本上是面试必问题( 当然大佬就不需要问了咯 )。
TCP 是在传输层上工作的一个比较复杂协议。比起 UDP 来说,TCP 的复杂程度简直不是一个量级的关系。 了解传输层 —— 走你
TCP 特性
1.面向连接可靠的传输协议。
2.不支持组播地址,仅支持单播地址。
3.传输基于字节流,不是报文。
4.通过滑动窗口机制实现流量控制,根据网络状态动态改变窗口的大小进行拥塞控制。
5.支持对数据进行分节排序,保证数据完整型以及正确性。
6.通过序号的确认和重发,确保传输的可靠性。
TCP 包头
源端口号: 这个字段占了 UDP 包头的前面的 16 bits( 两个字节 ). 一般是包含发送数据包的应用程序使用的端口。偶尔我们想实现单向传输的时候,就可以在源端口填 0,这样接收端就不知道,发送端的端口地址。
目的端口: 16 bits表示接收端的应用程序的端口地址。
序列号:主要是在三次握手的时候,会同步这个数据,以保证数据的连续的正确,不会出现分节数据的乱序问题
当 SYN 标记不为 1 时,这是当前数据分段第一个字节的序列号,如果 SYN 的值是 1 ,这个字段的值就是初始序列值( ISN ), 用于对序列号进行同步,这时第一个字节的序列号比这个字段的值大 1 (也就是 ISN 加 1)。
确认号:主要用于数据确认的标识,保证数据的可靠性。如果丢包,通过确认号重发丢失的分节数据。这个数据只有在 ACK 为 1 时才有意义。
用于确认已经接收到的数据分节,具体的数值是即将接收的下一个序列号,也就是已接收到的分节序列号加 1 。
数据偏移:表示的是包头的长度,接收到可以知道那个地方数据就是真正的数据区。单位是 32 bits( 4个字节 )。因为这个长度是 4 bits。所以最大是 15 。因此 TCP 的包头最大是 60 个字节(包含端口号)。目前大多数是 20 字节。
保留:暂时未有使用,必须为 0,
URG:这个值为 1 时,标识紧急指针区域有效,督促中间层设备要尽快处理这些数据。
ACK:这个值为 1 时,标识确认号有意义。
PSH:当这个值为 1 时,表示 Push 操作,Push 操作就是数据到达接收到后,立即传送给应用程序,而不需要在缓冲区排队(正常情况下,需要等到缓存区塞满之后上报给应用程序)。
RST:当这个值为 1 时,连接会被重置,用来针对那些错误或者非法连接。
SYN:当这个值为 1 时,表示同步序列号,主要是在建立连接的时候,三次握手时同步序列号。
在三次握手的时候 SYN = 1 ACK = 0 表示请求连接, SYN = 1, ACK = 1 表示连接被响应。
FIN:当这个值为 1 时,表示发送端的数据发送完成,没有其他的数据了。这是一个连接断开的标识。
窗口大小:这是窗口滑动机制进行流量控制和窗口大小进行拥塞控制。这是一个很深的问题,后续的相关文章可以聊一聊。
检验和:这个检验和 UDP 一致,发端生成后,接收端通过二进制计算对接收的数据进行相关的校验。确保数据的完整以及正确性
紧急指针:标识数据中有多少是紧急数据,紧急数据放在数据的开头。
选项:额外的填充数据,确保包头数据是 32 bits 的倍数关系。
数据:需要传输的数据。
理解数据协议是理解三次握手以及四次挥手、窗口的前提条件。
TCP 三次握手
TCP 是面向连接的,数据通信之前必须要建立连接,这个连接可以是 1 对多。
TCP 三次握手的过程
上图展示三次握手的流程图,文字描述过程如下:
客户端和服务端从 CLOSED 的状态变成在线状态。服务端需要处于 LISTEN.
1、客户端向服务端发起一个连接。报文中包含了:
SYN = 1,
「序列号」的值,
发送之后客服端进入到 SYN-SENT 状态。
2、服务端接收到 SYN 和 序列号信息后,需要对这个报文进行的确认。服务端收到客户端发送过来的「序列号」的值,服务端会返回一个「确认号」的值(序列号+1)。同时返回
SYN = 1 表示还在同步阶段。
ACK = 1 表示确认号有意义。
序列号:表示服务端的序列号 y。
确认号:针对客户端发起连接的一个确认号。
服务端发送之后,进入到 SYN-RCVD 状态。
3、客户端接收到服务端返回的数据,使得客户端这一边的相关通信建立,并且同步了相关序列号和确认号的数值,但是服务端还没有接收到客户端回复的确认号。所以客户端需要再发送一个数据到服务端,主要包含:
ACK = 1,这是确认号的数据有意义。
SYN = 0 序列号已经同步完成,不需要再同步序列号。
序列号:因为 SYN 已经不是第一次同步序列号的信息了。这个时候的序列号,就表示的是一个单纯的基于序列号最新的数据包的序列号。其值为:x+1。
确认号:返回服务端序列号 y 的确认号,为 y+1。
发送这个值之后,客户端进入到 ESTABLISHED 状态,服务端接受到这个值之后也进入到ESTABLISHED 状态。然后就可以开始传递数据通信了。
至此三次握手已经完成。
TCP 为什么进行是三次握手?
为什么 TCP 需要进行三次握手,这个问题很多人的解释都引用了
为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。—— 谢希仁的《计算机网络》
首先我们需要明白三次握手的意义在于什么,也就是说三次握手需要达到的一个目的是什么,
简单来说,其实是为了同步序列号和确认号的相关信息,而序列号和确认号又是保障数据的正确性以及窗口滑动机制的最基本的根据。所以我最需要明白的可能还是为什么要同步序列号就是为什么要三次握手:(举个例子来说明)(
如果 A 给 B发送一个数据包,这个包由于网络的原因,很久才到 B 端,而这段时间,A 和 B 已经断开,并且重新建立了连接关系。没有相关同步序列号和确认号,B 端认为这个数据还是认为 A 这个时候要发给我的,但是其实这个数据上一次传输的数据,相当于这次传输的数据中插入了其他的数据,那么就会导致这次的数据出现异常。
对于每次连接的序列号基本都是不一样的,这个序列号是随着时间会变化的,这个是一个 4 个字节的计数器。每隔 4 ms就会自加 1。如果需要产生重复的,需要 4 个小时才会回到同一个序列号。4 个小时的时候,那么在网络中的上一次数据包早已经失效了。在 IP 协议中,每个 IP 包头都是有 TIL ( 生存时间的)。
为什么不是两次:
对于 A 来看说,两次实现了信息一来一回,但是 TCP 的数据的可靠性,如果要实现对于 B 来说的信息的一来一回,那么第三次是一定要存在的。对两端的数据一来一回才是建立可靠连接的基本要求。
为什么不是四次:
理论上其实这个同步序列号和确认号的过程,大于三次也是没有的,应该说几十次上百次都是可以的,但是三次握手的过程已经实现了序号数据同步,在进行太多次的序号同步,已经没有意义。无故浪费宽带。
解析 TCP 四次挥手
当通信完成,当然需要能够进行连接断开。
TCP 四层挥手的过程:
1、任意一段( A 端)发出一条 FIN 的数据。数据包含:
FIN = 1,
序列号
A 端发出这个数据之后,进入到 FIN - WAIT -1 的状态,等待 B 端的回复.
2、B 收到 A 端的 FIN 的信息就回复一个数据,表示已经知道 A 端请求断开连接这个事情了。此时 B 端进入到 CLOSED - WAIT 的状态。A 端接收到这个数据进入到 FIN - WAIT -2 的状态。 数据中包含 :
ACK = 1
确认号
3、B 端给 A 端发起一次 FIN 的数据。请求结束连接,发送完成之后 B 端进入到 LAST - ACK 状态。发送的数据包含:
FIN = 1;
ACK = 1;
序列号
确认号
4、 A 端接收到这个 B 端发送的 FIN 数据进入到 TIME- WAIT 状态,同时回复 B 端,已经接收到了 FIN 数据。回复的包中包含:
ACK = 1;
确认号
至此整个过程全部结束。顺利断开连接。
其实上述是正常且十分顺利的四次挥手。
其中会出现多次不同的异常。
异常1:如果在 FIN-WAIT - 1 A 端不能接收到 B 断的 ACK 信息。TCP 协议层上是没有对这个地方任何处理的,但是 LINUX 是有相关处理的,有一个 TIMEOUT 的参数。FIN-WAIT-1 这个过程中其实不容易出异常的。因为在 TCP 的协议中,每次发送一个数据,另外一端会立马回复 ACK 确认信息的。所以理论上来说 A 端一般很少会能够直接看到 FIN - WAIT 1.
A端:
FIN-WAIT 1 —— A 端可以数据都已经发送完成,但是 B 端可能还有数据处理完成。
FIN-WAIT 2 -- B 端回复了 A 端结束连接的请求,并且做出了返回之后, A 进入一种半关闭的等待的状态。
TIME -WAIT —— 协议规定 A 端必须有一个驻守时间。这是一个为了异常处理而规定的时间。
B端:
COLSED - WAIT -- 数据没有处理,处理完成没有处理完的数据。
LAST - ACK -- 这是 B 给 A 端发送一个 FIN 请求之后,进入到一个等待 A 端回复的状态。
异常2:在 B 端发送一个 FIN 之后, 由于种种原因 B 端没有接收到 A 端的 ACK. 此时 根据 TCP 协议自身的机制 B 端重新发送 FIN 请求数据。但是如果这个时候 A 端已经回复了 B 的 ACK ,到了设置的 TIME - WAIT 的时间之后,就会关闭这个连接。如果后续还收到这个信息。那么直接会 RST ( 错误连接或者非法连接 )
为什么需要设置一个 TIME-WAIT 的时间:
这个文章讲的特别好(https://blog.csdn.net/smileiam/article/details/78226816)
一、保证TCP协议的全双工连接能够可靠关闭
先说第一点,如果Client直接CLOSED了,那么由于IP协议的不可靠性或者是其它网络原因,导致Server没有收到Client最后回复的ACK。那么Server就会在超时之后继续发送FIN,此时由于Client已经CLOSED了,就找不到与重发的FIN对应的连接,最后Server就会收到RST而不是ACK,Server就会以为是连接错误把问题报告给高层。这样的情况虽然不会造成数据丢失,但是却导致TCP协议不符合可靠连接的要求。所以,Client不是直接进入CLOSED,而是要保持TIME_WAIT,当再次收到FIN的时候,能够保证对方收到ACK,最后正确的关闭连接。
二、保证这次连接的重复数据段从网络中消失
再说第二点,如果Client直接CLOSED,然后又再向Server发起一个新连接,我们不能保证这个新连接与刚关闭的连接的端口号是不同的。也就是说有可能新连接和老连接的端口号是相同的。一般来说不会发生什么问题,但是还是有特殊情况出现:假设新连接和已经关闭的老连接端口号是一样的,如果前一次连接的某些数据仍然滞留在网络中,这些延迟数据在建立新连接之后才到达Server,由于新连接和老连接的端口号是一样的,又因为TCP协议判断不同连接的依据是socket pair,于是,TCP协议就认为那个延迟的数据是属于新连接的,这样就和真正的新连接的数据包发生混淆了。所以TCP连接还要在TIME_WAIT状态等待2倍MSL,这样可以保证本次连接的所有数据都从网络中消失。
TCP 为什么进行是四次挥手?
断开比连接更复杂,比较直接的理解是资源回收比资源分配会更麻烦。使得所有资源能够有效并且不产生错误的情况下释放。如上所述,四次挥手的意义。