前言
欢迎关注公众号《熊的猫》,本公众号会定期分享技术干货!
HTTP 协议 和 WebSocket 协议都是基于 TCP 协议,那么自然少不了对 TCP 协议的探究,当然本文不会涉及和 TCP 流量控制、拥塞控制、可靠性传输 等⽅⾯内容,因为这些部分涉及到的内容不算少,这里主要是针对大家常见的 TCP 连接 和 断开 进行探讨。
在开始正文之前,你可尝试着回答一下面给出的问题:
- 为什么不直接使用 TCP 协议,反而要使用 HTTP 协议 和 WebSocket 协议?
- 为什么需要 TCP 协议?
- TCP 握手 为什么需要 三次 握手建立连接?不能是 两次 或者 四次 吗?
- TCP 挥手 为什么需要 四次 挥手断开连接?不能是 三次 或者 五次 吗?
为什么不直接使用 TCP 协议?
我想应该有不少人也有一样的问题,那就是我们使用的 HTTP 协议 和 WebSocket 协议都是基于 TCP 协议,那么到底是什么原因使得我们不能直接使用 TCP 协议,反而要使用一个 二开 的东西呢?
首先,TCP 也作被称作为 传输层协议 (通常被称为 TCP/IP 协议
),TCP 协议支持两个进程通过 三次握手 建立稳定的通信信道,发送 字节流,并保证可靠传输。
说白了,TCP 作为 传输层协议,其主要功能就是 传输数据,那么问题就来了,它是可以传输数据,但是没有任何规范表明接收端要 如何接收 或 如何识别 数据信息,那么这数据虽然可以发送,但是没有意义。
这就好像,A 和 B 两个人打算进行交流,A 用自己的地方语言说,B 也用自己的地方语言说,结果两个人都没听明白,于是他们决定用 普通话 来作为互相交流的标准。
回过头来说,HTTP 协议建立在 TCP/IP 协议之上的意思是,TCP/IP 协议 让 收/发 双方支持并提供传输数据的能力,而 HTTP 协议定义了 接收/发送 数据的规范。
换句话来说,传输层协议 如果不用作用到 应用层, 那么这样的 数据传输 自然也就没有任何意义,因此应用层也有自己的协议 HTTP、DNS、FTP、SMTP 等等,当然也可以自己定义去应用层的协议。
TCP 协议
TCP 是什么?
TCP 是 ⾯向连接的、可靠的、基于字节流的
传输层通信协议.
- ⾯向连接:即 ⼀对⼀ 连接,不能像 UDP 协议可以⼀个主机同时向多个主机发送消息,即 ⼀对多
- 可靠的:TCP 可以保证⼀个报⽂⼀定能够到达接收端,⽆论⽹络链路中出现了怎样的链路变化
- 字节流:消息是 没有边界的,即⽆论多大de消息都可以进⾏传输
- 并且消息是 有序的,当 前⼀个 消息没有收到的时候,即使它先收到了后⾯的字节,那么也不能扔给应⽤层去处理
- 对 重复的 报⽂会⾃动丢弃
PS:
MAC 头部是以太⽹使⽤的头部,它包含了接收⽅和发送⽅的 MAC 地址等信息
头部/首部 格式
- 序列号(SeqNum):建⽴连接时由计算机⽣成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送⼀次数据,就累加⼀次该 数据字节数 的⼤⼩,⽤来解决⽹络包乱序问题
- 确认应答号(AckNum):期望 下⼀次收到的数据的 序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收,⽤来解决不丢包的问题
- 控制位:
- ACK:
ACK=1
时,确认应答字段变为有效,TCP 规定除了最初建⽴连接时的 SYN 包之外,该位必须设置为 1 - RST:
RST=1
时,表示 TCP 连接中出现异常必须强制断开连接 - SYN:
SYN=1
时,表示希望建⽴连接,并在其 序列号 字段进⾏序列号初始值的设定 - FIN:
FIN=1
时,表示后续不会再有数据发送,即希望断开连接,当通信结束希望断开连接时,通信双⽅的主机之间就可以相互交换 FIN 位为 1 的 TCP 段
为什么需要 TCP 协议?
通常都会说 TCP/IP 协议,那么就得先来简单了解下 IP 协议,上面的图中表明 IP 协议是处于 网络层.
TCP 和 IP 层都可以进行分片处理,TCP 层是按照 MSS
进行分片,而 IP 层是按照 MTU
进行分片.
说到这个就得先了解 MTU
和 MSS
:
- MTU :⼀个⽹络包的最⼤⻓度,以太⽹中⼀般为
1500
字节 - MSS :除去 IP 和 TCP 头部之后,⼀个⽹络包所能容纳的 TCP 数据的最⼤⻓度
IP 层分片处理:
- 当 IP 层有⼀个超过 MTU ⼤⼩的数据(TCP 头部 + TCP 数据)要发送,那么 IP 层就要进⾏分⽚,把数据分⽚成若⼲⽚,保证每⼀个分⽚都⼩于 MTU
- 把⼀份 IP 数据报进⾏分⽚以后,由⽬标主机的 IP 层来 进⾏᯿新组装后,再交给上⼀层 TCP 传输层
IP 层分片不可靠的:不保证⽹络包的交付
、不保证⽹络包的按序交付
、不保证⽹络包中的数据完整性
- 如果⼀个 IP 分⽚丢失
- 整个 IP 报⽂的所有分⽚都得重传,因为 IP 层本身没有超时重传机制,它由传输层的 TCP 来负责超时和重传
- 如果一个 TCP 分片丢失
- 一旦接收⽅发现 TCP 报⽂(头部 + 数据) 的某⼀⽚丢失后,则不会响应 ACK 给对⽅,那么发送⽅的 TCP 在超时后,就会重发整个 TCP 报⽂(头部 + 数据)
原因
- IP 层进⾏分⽚传输,是⾮常没有效率的,应该要尽量避免 IP 层进行分⽚传输
- 为了达到最佳的传输效能 TCP 协议在建⽴连接的时候通常要协商双⽅的 MSS 值,当 TCP 层发现数据超过 MSS 时,则就先会进⾏分⽚,当然由它形成的 IP 包的⻓度也就不会⼤于 MTU ,就能避免通过 IP 进行分⽚传输.
什么是 TCP 连接?
简单的说,就是⽤于保证可靠性和流量控制维护的某些状态信息,这些信息的组合,包括 Socket、序列号 和 窗口⼤⼩ 就称之为连接.
- Socket:由 IP 地址和端口号组成
- 序列号:⽤来解决乱序问题等
- 窗⼝⼤⼩:⽤来做流量控制
因此,建⽴⼀个 TCP 连接是需要客户端与服务器端达成上图中的三个信息共识。
如何唯⼀确定⼀个 TCP 连接呢?
TCP 四元组 可以唯⼀的确定⼀个连接:
- 源地址
- 源端⼝
- ⽬的地址
- ⽬的端⼝
TCP 三次握手
TCP 是⾯向连接的协议,所以使⽤ TCP 前必须 先建⽴连接,⽽建⽴连接是通过 三次握⼿ 来进⾏的.
- 初始时,客户端 和 服务端 都处于 CLOSED 状态,先是 服务端 主动监听某个端⼝,处于 LISTEN 状态
- 客户端 发起来连接
- 客户端 会随机初始化序列号(client_isn),将此序号置于 TCP ⾸部的 序列号 字段中
- 并将控制位 SYN 标志为 1 ,表示 SYN 报⽂,接着把第⼀个 SYN 报⽂发送给服务端,表示向服务端发起连接,该报⽂不包含应⽤层数据,之后客户端处于 SYN-SENT 状态
- 服务端 收到 客户端 的 SYN 报⽂
- ⾸先服务端也随机初始化⾃⼰的 序列号(server_isn),将此序号填⼊ TCP ⾸部的 序列号 字段中
- 其次把 TCP ⾸部的 确认应答号 字段填⼊ client_isn + 1, 并将控制位 SYN 和 ACK 标志位置为 1
- 将该报⽂发给客户端,该报⽂也不含应⽤层数据,服务端进入 SYN-RCVD 状态
- 客户端 收到 服务端 报⽂
- 向服务端回应最后⼀个应答报⽂,⾸先该应答报⽂ TCP ⾸部 ACK 标志位置为 1
- 确认应答号 字段填⼊ server_isn + 1,最后把报⽂发送给服务端,本次报⽂可携带要发送的数据
- 客户端进入 ESTABLISHED 状态
- 服务器 收到 客户端 的应答报⽂后,也进⼊ ESTABLISHED 状态
- 当双⽅都处于 ESTABLISHED 状态,标识本次连接就已建⽴完成,客户端 和 服务端 就可以相互发送数据了
为什么是三次握⼿?不是两次或者四次?
相信大多数的回答应该都是:“因为三次握⼿才能保证双⽅具有接收和发送的能力”,当然这回答是没问题,但是没有说出主要的原因.
三次握⼿的原因:
- 三次握⼿才可以 阻⽌重复历史连接的初始化 —— 主要原因
- 三次握⼿才可以 同步双⽅的初始序列号(SeqNum)
- 三次握⼿才可以 避免资源浪费
原因一:防⽌旧的重复连接初始化造成混乱
客户端连续发送多次 SYN 建⽴连接的报⽂,在⽹络拥堵情况下:
- ⼀个 旧 SYN 报⽂ ⽐ 最新的 SYN 报⽂ 早到达了服务端 ——
client --> server
- 此时服务端就会回⼀个 SYN + ACK 报⽂给客户端 ——
server --> client
- 客户端收到后可以根据⾃身的上下⽂,判断这是⼀个历史连接(序列号过期或超时),那么客户端 就会发送 RST 报⽂给服务端,表示中⽌本次连接 ——
client --> server
如果是 两次握⼿连接,就不能判断当前连接是否是历史连接,三次握⼿ 可以保证客户端因有⾜够的上下⽂来判断当前连接是否是历史连接,可以决定第三次报⽂时,是发送 ACK 报文,还是 RST 报文
原因二:同步双⽅初始序列号,保证可靠传输
TCP 协议的通信双⽅,都必须维护⼀个 序列号, 序列号是可靠传输的⼀个关键因素:
- 接收⽅ 通过 序列号 可去除重复的数据
- 接收⽅ 可根据数据包的 序列号按序接收
- 可标识发送出去的数据包中, 哪些是已经被 接收方 收到的,哪些属于丢失的
如果非要进行四次握手,无非就是服务端将 ACK 报文
和 SYN 报文
分别发送给客户端,但由于这两步可以被优化成⼀步,所以就成了 三次握⼿
,也就是说,四次握手也可以但是没必要.
原因三:避免资源浪费
假设只有 两次握⼿,当客户端的 SYN 请求连接在⽹络中阻塞,客户端没有接收到 ACK 报⽂,由于 TCP 的超时重传机制,会重新发送 SYN 请求,但此时因为没有 第三次握⼿,服务器不清楚客户端是否收到了⾃⼰发送建⽴连接的 ACK 确认报文,所以每收到⼀个 SYN 请求就只能先主动建⽴⼀个连接.
根据上面的情况会造成什么问题呢?
如果客户端的 SYN 阻塞了,重复发送多次 SYN 报⽂,那么服务器在收到请求后就会建⽴多个冗余的⽆效链接,造成不必要的资源浪费.
TCP 四次 握手/挥手
客户端
打算关闭连接,此时会发送⼀个 TCP ⾸部 FIN 标志位被置为 1 的报⽂,即 FIN 报⽂,之后客户端进⼊ FIN_WAIT_1 状态服务端
收到该报⽂后,就向客户端发送 ACK 应答报⽂,接着服务端进⼊ CLOSED_WAIT 状态客户端
收到服务端的 ACK 应答报⽂后,之后进⼊ FIN_WAIT_2 状态- 等待
服务端
处理完数据后,也向客户端发送 FIN 报⽂,之后服务端进⼊ LAST_ACK 状态 客户端
收到服务端的 FIN 报⽂后,回⼀个 ACK 应答报⽂,之后进⼊ TIME_WAIT 状态服务器
收到了 ACK 应答报⽂后,就进⼊了 CLOSED 状态,⾄此 服务端已经完成连接的关闭客户端
在经过 2MSL ⼀段时间后,⾃动进⼊ CLOSED 状态,⾄此 客户端也完成连接的关闭
服务端 和 客户端 双方都需要 ⼀个 FIN 和 ⼀个 ACK,因此通常被称为 四次挥⼿.
为什么挥⼿需要四次?
- 关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端 不再发送数据 了,但 还能接收数据
- 服务器收到客户端的 FIN 报⽂时,先返回 ACK 应答报⽂,⽽服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报⽂给客户端来表示同意现在关闭连接
简单的说,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN ⼀般都会分开发送,从⽽⽐ 三次握⼿ 导致多了⼀次,即 四次握手.
为什么 TIME_WAIT
等待的时间是 2MSL
?
主动关闭连接 的才有 TIME_WAIT 状态.MSL (Maximum Segment Lifetime),报⽂最⼤⽣存时间,它是任何报⽂在⽹络上存在的最⻓时间,超过这个时间的报⽂将被丢弃.
TIME_WAIT 等待 2 倍的 MSL,⽐较合理的解释是: ⽹络中可能存在来⾃ 发送⽅ 的数据包,当这些 发送⽅ 的数据包被 接收⽅ 处理后⼜会向 发送方 发送响应,即⼀个来回需要等待 2 倍的 MSL 时间.
为什么需要 TIME_WAIT
状态?
需要 TIME-WAIT 状态的主要原因:
- 防⽌具有相同 四元组 的 旧 数据包被收到,产⽣数据错乱等问题
- 保证 被动关闭连接⽅ 能被正确的关闭,即保证最后的 ACK 能让 被动关闭⽅ 接收,使其正常关闭
最后
网络相关的内容比较多,也是一环套一环,因此,这里没有展开太多,否则篇幅就很长了,感兴趣的可以自行学习,希望本文对你有所帮助!!
文中的图示从 图解网络 中进行借鉴。