上一篇:计算机网络(二)- TCP/IP协议群介绍
1、概述
TCP协议全名是 Transport Control Protocol
,是一个可以提供 可靠的、支持全双工、连接导向的协议,因此在客户端和服务端之间传输数据的时候,是必须先建立连接的。
1.1、什么是建立连接
- 连接本身是个虚拟、抽象的概念。他能让两个通信的程序之间确保彼此都在线
- 建立连接可以加快相应请求的速度
- 连接也被称为 会话(Session)
- 建立连接可以使得通信更加的稳定、安全
- 但是同样建立连接也会消耗相应的资源
1.2、单工、半双工、全双工
- 单工: 任何时刻数据只能单向传输
- 半双工:允许数据在两个方向上传输, 但是在某一个时刻(同一时刻),只允许数据在一个方向上传输。
- 全双工: 任何时刻数据都可以双向传输。
1.3、可靠性
- 可靠性是为了保证数据的无损传输
- 使用无序的数据恢复原有顺序
- 多播时每个接收方都获得无损的副本。
2、特点
- 1、基于连接的(点对点的通信)
- 传输数据之前是要建立好连接的
- 2、是双工通信的
- TCP协议一旦建立连接, 就可以在连上上实现双向的通信
- 3、基于字节流而非报文--保证了TCP协议的可靠性
- 将数据按字节大小进行编号,接收端通过ACK来确认收到的数据编号,通过这个机制保证TCP协议的有序性和完整性
- 4、拥塞控制
- TCP协议通过 慢启动、拥塞避免、拥塞发生、快速恢复 在不同的过程中的算法来控制拥塞的
- 5、流量控制能力
- 通过滑动窗口控制数据的发生速率,滑动窗口的本质是动态缓冲区,接收区根据自己的能力在 TCP 的请求头header中动态调整窗口大小,通过ACK应答包通知到给发送端,发送端根据窗口的大小调控发送速率。
3、TCP协议详解
在解释TCP工作流程之前呢, 先解释一下几个名词,这样更有助于我们去了解其过程。
3.0、TCP首部信息组成以及各个部分的作用
下图是TCP协议报文段的组成图片
3.0.1、源端口和目的端口
- 源端口,用来存放 发送TCP报文的进程对应的端口号。占 2个字节(16位)
- 目的端口,用来存放 接受TCP报文段的进程对应的端口号。 占 2个字节(16位)
3.0.2、32位序列号 Sequence Number
- 占用4个字节(一个字节(byte)8个比特位(bit))。
- TCP的序列号对数据包进行标记,以便达到目的地后重新组装数据包,假设当前序列号为
s
,发送数据长度为l
,则下一次发送数据时的序列号为s+l
。 - 在建立连接时通常由计算机生成随机数作为序列号的初始值。
3.0.3、32位确认号 Acknowledgement Number
- 占用 4个字节(一个字节(byte)8个比特位(bit))
- 是有接收端计算机使用,用于重组分组的报文成最初形式。
- 如果设置了ACK控制位, 那么这个值表示准备接受的下一个包的序列号
3.0.4、数据偏移 offset
- 占用4位,即0.5个字节
- 这部分实际上是指出TCP报文的首部的长度,即TCP报文段数据起始位置距离TCP报文的起始位置有多远(这里指 TCP拆包后的报文段的起始位置 与 TCP整个报文的起始位置有多远)
3.0.5、保留字 Reserved
- 占6位,即 0.75个字节
- 保留为以后使用,当前是值为零。
3.0.6、标志位 Tcp Flags
- 每个标志位占1 位,一共6个标志位 。即 1.5个字节。
- URG - 紧急指针有效标识
- 此标志位用来表示TCP包的紧急指针域有效,用来保证TCP连接不会被终端,并且督促中间层设备要尽快处理这些数据。相当于告诉接收端,有高优先级的数据。
- ACK - 确认序号有效标识
- 只有当
ACK=1
的时候,确认号字段才有效。 - 当
ACK=0
的时候,确认号是无效的。
- PSH - 用来表示接收方应尽快将这个报文段交给应用层
- 接收到
PSH = 1
的TCP报文段, 应尽快的交付接收报文段的应用进程, 而不再等待整个缓存都填满后再交付
- RST - 重建连接标识
- 当
RST = 1
时,表明TCP连接出现严重错误(如由于主机崩溃或其他原因),必须释放连接,然后再重新简历连接。
- SYN - 表示同步序号,用来建立连接。
- 当
SYN = 1
时,表示这是一个连接请求或者连接接受请求。
- FIN - 发送完成任务标识,用来释放一个连接
- 当
FIN = 1
表明此报文段的发送端和数据已经发送完成了
3.0.7、窗口大小 Window Size
- 占 16位 即两个字节
- 该字段表明指出了现在允许对方发送的数据量,他告诉对方本端TCP连接缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。
- 窗口大小的值 指的是 从本报文段的首部中的确认号算起, 接方法目前允许对法发送的数据量。
3.0.8、校验和 TCP Checksum
- 占用16位,即两个字节。
- 由发送端填充,接收端对TCP报文段执行CRC算法,以校验TCP报文段在传输过程中是否损坏,如果损坏则丢失。
- 检验范围包括首部和数据两个部分
- 这也是TCP可靠性的一个重要保障。
3.0.9、紧急指针 Urgent Pointer
- 占用16位,即两个字节
- 这部分仅在标志位
URG = 1
的时候才有意义,他指出本报文段中的数据的字节数。 - 当
URG = 1
时,发送方TCP就把紧急数据插入到本报文段数据的最前面,而在紧急数据后面任然是普通数据。 - 因此, 紧急指针指出了数据的末尾在报文段中的位置。
3.1、TCP协议的三次握手四次挥手
参考文档 :
先看一下TCP从建立连接到传输数据到断开连接的完整过程
3.1.0、三次握手建立连接的过程
流程如下:
- 第一次握手(由客户端发起的)
- 客户端发送
SYN=1 seq = x
的请求建立连接的报文
- (SYN 标志位中的同步序列号,用来建立连接的)
- seq 是 32位序列号
Sequence Number
,即在建立连接的时候, 随机生成的初始序列号x。
- 此时,客户端进程进入了
SYN-SENT(同步已发送状态)
状态 - 此次建立连接的报文是不能携带数据的。
- 第二次握手(由服务端发起)
- 服务器接收到请求建立连接的报文后,如果同意连接, 则发出确认报文。
- 标志位
ACK = 1
, 确认号ack = x+1
,这个确认号为 x+1 就是基于上面请求的的seq随机生成的初始序列号+1得出的。 - 标志位
SYN = 1
, 确认号seq = y
, 这个seq
也是服务端自己随机生成的一个初始序列号。
- 此时 , 服务器进程进入到
SYN-RCVD(同步收到状态)
的状态。 - 这个报文也不能携带数据。
- 第三次握手(由客户端发起)
- 客户端收到 服务器发送的的确认报文后, 还要再向服务器发送确认报文。
- 标志位
ACK = 1
, 确认号ack = y+1
,序列号seq = x+1
- 此时 TCP连接建立,客户端和服务端都进入
ESTABLISHED(连接已建立)
状态。
- 举例解释一下为什么要三次握手
- 当客户端发送第一个连接的请求报文, 但是由于网络不好,这个请求没有立即到达服务器, 而是在某个节点停留了,知道某个时间才到达服务器,本来这已经是一个失效的报文了, 但是 服务端接收到这个请求报文后,还是会像client 发出确认报文,表示同意建立连接。
- 假设 不采用三次握手,那么只要服务器发出去确认报文,新的连接就建立了,但是其实这个请求已经超时并失效了,客户端是不会理睬服务器的确认信息的 ,也不会向服务端发送确认的请求。
- 但是此时的服务器是已经认为连接建立了, 换句话说就是处于
ESTABLISHED(连接已建立)
的状态,并且一直在等待客户端发来数据。 - 这样的话, 服务器端就有很多资源浪费了。
- 而采用三次握手的话, 服务端如果收不到确认报文话,就知道客户端没有服务端建立连接不成功了。
3.0.2、TCP 数据传输过程
客户端 与 服务端 建立连接之后,就可以相互传输数据了。
- 如上图所示,正常传输数据的过程如下:
- 主机A 开始发送数据的时候,假设初始的
seq = 1200
,滑动窗口大小为100,向主机B发送数据传递 - 主机B在完全接收到数据后, 为了确认收到 ,需要向主机A 发送ACK包,设置的 值为
1200+100+1
.
- ACK = seq + 传递数据的字节数 + 1
- 主机A在接收到 主机B 的确认信息后,开始发送下个报文, 此时
seq = 1301
,同样滑动窗体的大小为100的数据。 - .......
- 注: 与 三次握手协议相同, 最后加1 是为了告诉对象要传递 seq信号的。
- 如上图所示 , 数据丢包的情况过程如下:
- 在
seq = 1301
数据包向主机B 传递 100字节的数据,但中间发生了错误,主机B 未收到。 - 经过一段时间的等待后, 主机A 任然未收到
Seq = 1402
的确认号,此时就会尝试数据重新传递。 - 为了完成数据包的重传, TCP Socket套接字每次发送数据包时都会启动定时器, 如果在一定时间内没有收到目标主机的传回的ACK包, 那么定时器超时, 数据包会重传。
3.0.3、四次挥手断开连接
- 首先TCP连接断开, 是一个客户端主动关闭, 服务端被动关闭的过程。
- 关闭连接需要经过4次会话,具体的流程如下:
第一次挥手
- TCP 连接的客户端发送一个 标志位
FIN(结束)=1
的报文,用来请求关闭客户端到服务端的连接。 - 客户端进程发出释放连接的报文。 释放连接报文的首部,
FIN =1
,其序列号是seq = u
(序列号等于前面已传送过来的数据的最后一个字节的序号加1) - 客户端在发送释放连接的请求后进入
FIN-WAIT-1(终止等待1)
的状态。 - TCP协议规定, FIN 报文即时不携带数据也要消耗一个序号。
第二次挥手
- 服务端收到客户端发送的
FIN
报文时,客户端会先发送一个ACK(确认)
的报文给客户端。 - 服务端回复的
ACK
报文中,ACK = 1
,seq = v
,ack = u + 1
。
-
ACK = 1
表示确认报文 -
ack = u + 1
是针对上面第一次挥手服务端的 seq = u 再加上1 -
seq = v
是服务端自己随机生成的序列号。
- 此时,服务端就进入到
CLOSE-WAIT(关闭等待状态)
。
- TCP 会通知上层应用进程, 客户端向服务器的方向就释放了。
- 此时,是处于半关闭状态, 即客户端已经没有数据要发送了, 但是服务器若要发送数据,客户端依然要接收。
- 这个状态还要持续一段时间, 也就是整个
CLOSE-WAIT
状态持续的时间。
- 客户端 在接收到服务器的 断开连接的确认ack报文后,此时
- 客户端就进入了
FIN-WAIT-2(终止等待2)
状态,等待服务器发送连接释放报文。
第三次挥手
- 服务端发送完
ACK
报文后,在确认数据传输完毕后,会发送一条FIN(结束)
的报文到客户端。 - 由于处于半关闭状态, 服务器可能又发送可一些数据,假定此时的序列号为 seq = w
-
FIN = 1
,ack = u + 1
。 - 此时, 服务器就进入
LAST-ACK(最后确认)
状态了。
第四次挥手
- 最后, 客户端收到了服务端的
FIN = 1
的报文后,知道客户端已经没有数据需要传输了,由客户端最后再向服务器发送ACK = 1
的确认报文。并ack = w(服务端发送的seq)+1
以及seq = u+2
. - 此时 客户端进入
TIME-WAIT(时间等待)
状态。 - 注意,此时TCP 连接还没有释放, 必须经过 2 ** MSL(最长报文段寿命) 的时间后, 当前客户端撤销相应的TCB后才进入 CLOSED状态。
- 服务器端只要接收到客户端发送的确认, 就立即进入CLOSED状态。
- 综上所述: 服务器端结束TCP连接的时间要比客户端早一些。
- 思考(一):为什么是 4次挥手?
- 首先肯定是为了保证数据能个完成全部的传输过程。
- 当关闭连接时, 当收到对方发送的
FIN
报文是, 仅仅表示对方没有数据发送给你了。但未必是你发送给对方的数据是否全部发送完毕了,所以你未必可以马上就关闭SOCKET 套接字。所以你可能需要继续传输数据给对方。数据发送完完毕后再发送一个FIN报文给对方。 所以此时服务端的ACK
报文 和FIN
报文是分开来发送的。
思考(二):数据传输过程中客户端突然挂了怎么办?
- 正常连接时, 客户端会挂了,如果没有措施处理这种情况的话, 那么就会出现客户端和服务端出现长时间空闲挂起。
- 解决办法就是在服务器端设置保活计时器,每当服务器收到客户端的消息, 就将计时器复位。计时器的超时时长通常设置为2小时。
- 如果服务器超过2小时没收到客户端的消息, 他就发送探测报文段,若发送10个探测报文段, 每个相隔75s,还没有相应, 就认为客户端出现故障了, 因而终止该连接。
思考(三): 为什么客户端最后还要等待 2MSL?
MSL(Maximun Segment Lifetime)
最长报文寿命- 1、保证客户端发送的最后一个ACK报文能够顺利服务器,因此这个报文在数据传输过程中可能会丢失
- 站在服务器角度来看,服务器已经发送了
FIN+ACK
报文请求断开了, 客户端没有给我相应,应该是我发送的请求断开的报文,客户端没有接收到, 于是服务器又会重新发送一次,而客户端就可以再2MSL时间内, 重传这个报文,接着给出ACK报文后, 会重启2MSL计时器。
- 2、防止已经失效的连接请求报文段出现在在本连接中。
- 客户端发送完最后一个确认报文后,在这个2MSL时间中, 就可以是本连接吃的时间内所产生的所有报文段都从网络中小时。这样新的连接中不会出现连接的请求报文。
- 3、那么等待2MSL 就一定没问题了嘛?
- 不,还有一个超时机制, 超时了,即使没有收到回复也会关闭连接。