文章目录
- TCP基本认识
- TCP头部格式
- 为什么需要 TCP 协议? TCP 工作在哪一层?
- 什么是 TCP ?
- 如何唯一确定一个 TCP
- UDP 和 TCP 有什么区别呢?分别的应用场景是?
- TCP 和 UDP 可以使用同一个端口吗?
- TCP连接建立
- TCP三次握手过程
- 为什么是三次握手?不是两次、四次?
- 避免历史连接
- 同步双方初始化序列号
- 避免资源浪费
- 为什么每次建立 TCP 连接时,初始化的序列号都要求不一样呢?
- 初始序列号 ISN 是如何随机产生的?
- 既然 IP 层会分片,为什么 TCP 层还需要 MSS 呢?
- 第一次握手丢失了,会发生什么?
- 第二次握手丢失了,会发生什么?
- 第三次握手丢失了,会发生什么?
- 什么是 SYN 攻击?如何避免 SYN 攻击?
TCP基本认识
TCP头部格式
序列号:
在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题
。确认应答号:
指下一次「期望」收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决丢包的问题。
控制位:
ACK
:该位为 1 时,「确认应答」的字段变为有效,TCP 规定除了最初建立连接时的 SYN 包之外该位必须设置为 1 。RST
:该位为 1 时,表示 TCP 连接中出现异常必须强制断开连接。SYN
:该位为 1 时,表示希望建立连接,并在其「序列号」的字段进行序列号初始值的设定。FIN
:该位为 1 时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 FIN 位为 1 的 TCP 段。
为什么需要 TCP 协议? TCP 工作在哪一层?
- IP 层是「不可靠」的,它不保证网络包的交付、不保证网络包的按序交付、也不保证网络包中的数据的完整性。
- TCP 是一个工作在
传输层
的可靠
数据传输的服务,它能确保接收端接收的网络包乱序、丢包。保证数据的完整性
什么是 TCP ?
TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。
- 面向连接:一定是「一对一」才能连接,不能像 UDP 协议可以一个主机同时向多个主机发送消息,也就是一对多是无法做到的;
- 可靠的:TCP 都可以保证一个报文一定能够到达接收端;
- 字节流:用户消息通过 TCP 协议传输时,消息可能会被操作系统「分组」成多个的 TCP 报文,如果接收方的程序如果不知道「消息的边界」,是无法读出一个有效的用户消息的。并且 TCP 报文是「有序的」,当「前一个」TCP 报文没有收到的时候,即使它先收到了后面的 TCP 报文,那么也不能扔给应用层去处理,同时对「重复」的 TCP 报文会自动丢弃。
建立一个 TCP 连接是需要客户端与服务端达成上述三个信息的共识。
- Socket:由 IP 地址和端口号组成
- 序列号:用来解决乱序问题等
- 窗口大小:用来做流量控制
如何唯一确定一个 TCP
- TCP 四元组可以唯一的确定一个连接,四元组包括如下:
- 源地址
- 源端口
- 目的地址
- 目的端口
- 源地址和目的地址的字段(32 位)是在 IP 头部中,作用是通过 IP 协议发送报文给对方主机。
- 源端口和目的端口的字段(16 位)是在 TCP 头部中,作用是告诉 TCP 协议应该把报文发给哪个进程。
有一个 IP 的服务端监听了一个端口,它的 TCP 的最大连接数是多少?
UDP 和 TCP 有什么区别呢?分别的应用场景是?
目标和源端口
:主要是告诉 UDP 协议应该把报文发给哪个进程。包长度
:该字段保存了 UDP 首部的长度跟数据的长度之和。校验和
:校验和是为了提供可靠的 UDP 首部和数据而设计,防止收到在网络传输中受损的 UDP 包。
TCP和UDP区别
1.连接
- TCP 是面向连接的传输层协议,传输数据前先要建立连接。
- UDP 是不需要连接,即刻传输数据。
2.服务对象
- TCP 是一对一的两点服务,即一条连接只有两个端点。
- UDP 支持一对一、一对多、多对多的交互通信
3.可靠性
- TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按序到达。
- UDP 是尽最大努力交付,不保证可靠交付数据。但是我们可以基于 UDP 传输协议实现一个可靠的传输协议,比如 QUIC 协议,
4.拥塞控制、流量控制
- TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。
- UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率。
5.首部开销
- TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是 20 个字节,如果使用了「选项」字段则会变长的。
- UDP 首部只有 8 个字节,并且是固定不变的,开销较小。
6.传输方式
- TCP 是流式传输,没有边界,但保证顺序和可靠。
- UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序。
7.分片不同
- TCP 的数据大小如果大于 MSS 大小,则会在传输层进行分片,目标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片。
- UDP 的数据大小如果大于 MTU 大小,则会在 IP 层进行分片,目标主机收到后,在 IP 层组装完数据,接着再传给传输层。
为什么 UDP 头部没有「首部长度」字段,而 TCP 头部有「首部长度」字段呢?
- TCP 有
可变长的
「选项」字段,而 UDP 头部长度则是不会变化
的,无需多一个字段去记录 UDP 的首部长度。
TCP 和 UDP 可以使用同一个端口吗?
- 传输层的「端口号」的作用,是为了区分同一个主机上不同应用程序的数据包。
- 传输层有两个传输协议分别是 TCP 和 UDP,在内核中是两个完全独立的软件模块。
- TCP/UDP 各自的端口号也相互独立,如 TCP 有一个 80 号端口,UDP 也可以有一个 80 号端口,二者并不冲突。
TCP连接建立
TCP三次握手过程
- TCP 是面向连接的协议,所以使用 TCP 前必须先建立连接,而
建立连接是通过三次握手
来进行的。
三次握手建立连接流程
- 第一次握手:
- 客户端和服务端都处于
CLOSE
状态。先是服务端主动监听某个端口,处于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
状态。
第三次握手是可以携带数据的,前两次握手是不可以携带数据的。
- TCP 的连接状态查看,在 Linux 可以通过
netstat -napt
命令查看。
为什么是三次握手?不是两次、四次?
- 因为三次握手才能保证双方具有接收和发送的能力。(回答太简单)
三次握手的原因
- 三次握手为了防止旧的重复连接初始化造成混乱。(主要原因)
- 三次握手才可以同步双方的初始序列号
- 三次握手才可以避免资源浪费
避免历史连接
二次握手
- 两次握手连接,就无法阻止历史连接
为什么 TCP 两次握手为什么无法阻止历史连接呢?
在两次握手的情况下,服务端没有中间状态给客户端来阻止历史连接,导致服务端可能建立一个历史连接,造成资源浪费。
- 在两次握手的情况下,服务端在收到 SYN 报文后,就进入 ESTABLISHED 状态,意味着这时可以给对方发送数据,但是客户端此时还没有进入 ESTABLISHED 状态,假设这次是历史连接,客户端判断到此次连接为历史连接,那么就会回 RST 报文来断开连接,而服务端在第一次握手的时候就进入 ESTABLISHED 状态,所以它可以发送数据的,但是它并不知道这个是历史连接,它只有在收到 RST 报文后,才会断开连接。
客户端发送三次握手(ack 报文)后就可以发送数据了,而被动方此时还是 syn_received 状态,如果 ack 丢了,那客户端发的数据是不是也白白浪费了?
- 不是的,即使服务端还是在 syn_received 状态,收到了客户端发送的数据,还是可以建立连接的,并且还可以正常收到这个数据包。这是因为数据报文中是有 ack 标识位,也有确认号,这个确认号就是确认收到了第二次握手。
同步双方初始化序列号
- TCP 协议的通信双方, 都必须维护一个「序列号」, 序列号是可靠传输的,它的作用
- 接收方可以去除重复的数据;
- 接收方可以根据数据包的序列号按序接收;
- 可以标识发送出去的数据包中, 哪些是已经被对方收到的(通过 ACK 报文中的序列号知道);
两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收。
- 四次握手其实也能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了「三次握手」。
当客户端发送携带「初始序列号」的 SYN 报文的时候,需要服务端回一个 ACK 应答报文,表示客户端的 SYN 报文已被服务端成功接收,那当服务端发送「初始序列号」给客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能确保双方的初始序列号能被可靠的同步。
避免资源浪费
- 如果只有「两次握手」,当客户端发生的 SYN 报文在网络中阻塞,客户端没有接收到 ACK 报文,就会重新发送 SYN ,
由于没有第三次握手,服务端不清楚客户端是否收到了自己回复的 ACK 报文,所以服务端每收到一个 SYN 就只能先主动建立一个连接
- 如果客户端发送的 SYN 报文在网络中阻塞了,重复发送多次 SYN 报文,那么
服务端在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。
总结
- TCP 建立连接时,通过三次握手能防止历史连接的建立,能减少双方不必要的资源开销,能帮助双方同步初始化序列号。序列号能够保证数据包不重复、不丢弃和按序传输。
- 不使用「两次握手」和「四次握手」的原因:
- 「两次握手」:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号;
- 「四次握手」:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数。
为什么每次建立 TCP 连接时,初始化的序列号都要求不一样呢?
主要原因有两个方面:
为了防止历史报文被下一个相同四元组的连接接收(主要方面);
- 为了安全性,防止黑客伪造的相同序列号的 TCP 报文被对方接收;
流程
- 客户端和服务端建立一个 TCP 连接,在客户端发送数据包被网络阻塞了,然后超时重传了这个数据包,而此时服务端设备断电重启了,之前与客户端建立的连接就消失了,于是在收到客户端的数据包的时候就会发送 RST 报文。
- 紧接着,客户端又与服务端建立了与上一个连接相同四元组的连接;
- 在新连接建立完成后,上一个连接中被网络阻塞的数据包正好抵达了服务端,刚好该数据包的序列号正好是在服务端的接收窗口内,所以该数据包会被服务端正常接收,就会造成数据错乱。
如果每次建立连接,客户端和服务端的初始化序列号都是一样的话,很容易出现历史报文被下一个相同四元组的连接接收的问题。
初始序列号 ISN 是如何随机产生的?
- ISN = M + F(localhost, localport, remotehost, remoteport)。
- 随机数是会基于时钟计时器递增的,并且根据源 IP、目的 IP、源端口、目的端口生成一个随机数值,基本不可能会随机成一样的初始化序列号。
既然 IP 层会分片,为什么 TCP 层还需要 MSS 呢?
MTU
:一个网络包的最大长度,以太网中一般为 1500 字节;MSS
:除去 IP 和 TCP 头部之后,一个网络包所能容纳的 TCP 数据的最大长度;
如果在 TCP 的整个报文(头部 + 数据)交给 IP 层进行分片,会有什么异常呢?
- 当如果一个 IP 分片丢失,整个 IP 报文的所有分片都得重传。因为 IP 层本身没有超时重传机制,它由传输层的 TCP 来负责超时和重传。
经过 TCP 层分片后,如果一个 TCP 分片丢失后,进行重发时也是以 MSS 为单位,而不用重传所有的分片,大大增加了重传的效率。
第一次握手丢失了,会发生什么?
- 当客户端想和服务端建立 TCP 连接的时候,首先第一个发的就是 SYN 报文,然后进入到
SYN_SENT
状态。 - 如果客户端迟迟收不到服务端的
SYN-ACK
报文(第二次握手),就会触发「超时重传」机制,重传 SYN 报文,而且重传的 SYN 报文的序列号都是一样的。 - 一般重传5次SYN 报文后,再等上一段时间(时间为上一次超时时间的 2 倍)如果还是没能收到服务端的第二次握手(SYN-ACK 报文),那么客户端就会断开连接。
第二次握手丢失了,会发生什么?
- 当服务端收到客户端的第一次握手后,就会回 SYN-ACK 报文给客户端,这个就是第二次握手,此时服务端会进入 SYN_RCVD 状态。
- 第二次握手的 SYN-ACK 报文其实有两个目的 :
- 第二次握手里的 ACK, 是对第一次握手的确认报文;
- 第二次握手里的 SYN,是服务端发起建立 TCP 连接的报文;
如果第二次握手丢了,具体会怎样
- 如果客户端迟迟没有收到第二次握手,那么客户端就觉得可能自己的 SYN 报文(第一次握手)丢失了,于是
客户端就会触发超时重传机制,重传 SYN 报文。
-服务端就收不到第三次握手,于是服务端这边会触发超时重传机制,重传 SYN-ACK 报文。
第三次握手丢失了,会发生什么?
- 客户端收到服务端的 SYN-ACK 报文后,就会给服务端回一个 ACK 报文,也就是第三次握手,此时客户端状态进入到 ESTABLISH 状态。
- 当服务端超时重传 2 次
SYN-ACK
报文后,由于tcp_synack_retries
为 2,已达到最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到客户端的第三次握手(ACK 报文),那么服务端就会断开连接
什么是 SYN 攻击?如何避免 SYN 攻击?
什么是 SYN 攻击?
- TCP 连接建立是需要三次握手,假设攻击者短时间伪造不同 IP 地址的 SYN 报文,服务端每接收到一个 SYN 报文,就进入SYN_RCVD 状态,但服务端发送出去的 ACK + SYN 报文,无法得到未知 IP 主机的 ACK 应答,久而久之就会
占满服务端的半连接队列
,使得服务端不能为正常用户服务。
在 TCP 三次握手的时候,Linux 内核会维护两个队列,分别是:
- 半连接队列,也称 SYN 队列;
- 全连接队列,也称 accept 队列;
Linux 内核的 SYN 队列(半连接队列)与 Accpet 队列(全连接队列)是如何工作的?
- 当服务端接收到客户端的 SYN 报文时,会创建一个半连接的对象,然后将其加入到内核的「 SYN 队列」;
- 接着发送 SYN + ACK 给客户端,等待客户端回应 ACK 报文;
- 服务端接收到 ACK 报文后,从「 SYN 队列」取出一个半连接对象,然后创建一个新的连接对象放入到「 Accept 队列」;
- 应用通过调用 accpet() socket 接口,从「 Accept 队列」取出连接对象。
SYN 攻击方式最直接的表现就会把 TCP 半连接队列打满,这样当 TCP 半连接队列满了,后续再在收到 SYN 报文就会丢弃,导致客户端无法和服务端建立连接。
如何避免 SYN 攻击?
- 调大 netdev_max_backlog;
- 增大 TCP 半连接队列;
- 开启 tcp_syncookies;
- 减少 SYN+ACK 重传次数
文章参考:https://xiaolincoding.com/