0%

tcp原理和三次握手四次挥手

1. TCP

TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。

  • 面向连接:一定是「一对一」才能连接,不能像 UDP 协议可以一个主机同时向多个主机发送消息,也就是一对多是无法做到的;

  • 可靠的:无论的网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端;

  • 字节流:用户消息通过 TCP 协议传输时,消息可能会被操作系统「分组」成多个的 TCP 报文,如果接收方的程序如果不知道「消息的边界」,是无法读出一个有效的用户消息的。

    并且 TCP 报文是「有序的」,当「前一个」TCP 报文没有收到的时候,即使它先收到了后面的 TCP 报文,那么也不能扔给应用层去处理,同时对「重复」的 TCP 报文会自动丢弃。

1.1 TCP 头部字段

1

1. 头部格式

20字节固定 + 最大40字节扩展

2. 固定20字节详情

  • 源端口 2字节, 目的端口 2字节
  • 序号 4字节 , 我发送的是以 n 开始的序号
  • 确认号 4字节, 之前的都已经接收,下次希望给我传递 n, ACK位置必须=1
  • 数据偏移(说明头部字节是20还是到60) + 保留 + URG(紧急指针有效) + ACK + PSH(推送,尽快交给应用层) + RST(复位,重新建立连接) + SYN(tcp建立标志) + FIN(tcp释放标志) 一共2字节, 窗口2字节, 我的接收窗口大小 (例如rwnd=20)
  • 检验和2字节(检错算法), 紧急指针2字节(帮忙取出紧急数据)

3. 最大40字节扩展字段

1.2 TCP 连接

Connections: The reliability and flow control mechanisms described above require that TCPs initialize and maintain certain status information for each data stream. The combination of this information, including sockets, sequence numbers, and window sizes, is called a connection.

用于保证可靠性和流量控制维护的某些状态信息,这些信息的组合,包括 Socket、序列号和窗口大小称为连接。

所以我们可以知道,建立一个 TCP 连接是需要客户端与服务端达成上述三个信息的共识。

  • Socket:由 IP 地址和端口号组成
  • 序列号:用来解决乱序问题等
  • 窗口大小:用来做流量控制

1.3 唯一确定一个 TCP 连接

TCP 四元组

TCP 四元组可以唯一的确定一个连接,服务端理论上可以接受多少链接呢?

服务端通常固定在某个本地端口上监听,等待客户端的连接请求。对 IPv4,客户端的 IP 数最多为 2 的 32 次方,客户端的端口数最多为 2 的 16 次方,也就是服务端单机最大 TCP 连接数,约为 2 的 48 次方。

当然,服务端最大并发 TCP 连接数远不能达到理论上限,会受以下因素影响:

  • 文件描述符限制

    每个 TCP 连接都是一个文件,如果文件描述符被占满了,会发生 Too many open files。Linux 对可打开的文件描述符的数量分别作了三个方面的限制:

    • 系统级:当前系统可打开的最大数量,通过 cat /proc/sys/fs/file-max 查看; // 9223372036854775807
    • 用户级:指定用户可打开的最大数量,通过 cat /etc/security/limits.conf 查看;// 100000
    • 进程级:单个进程可打开的最大数量,通过 cat /proc/sys/fs/nr_open 查看; // 1048576
  • 内存限制,每个 TCP 连接都要占用一定内存,操作系统的内存是有限的,如果内存资源被占满后,会发生 OOM。

2. TCP 和 UDP

2.1 区别

UDPTCP
数据传输数据传输3报文握手+数据传输+4报文挥手
连接方式单播(一对一), 多播(一对多), 广播 (一对全)单播(一对一)
应用报文每个报文添加个UDP首部一系列字节流放到缓存中, 通过滑动窗口策略发送
发送方加个 TCP 头部
接收方取出字节流,组合送给接收方进程
首部8字节最小20字节, 最大60字节

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 层组装完数据,接着再传给传输层。

2.2 Socket 模型

  • TCP
img
  • UDP
img

3. 连接

3.1 建立连接

1

客户端服务器
首先处于 closed 状态首先处于 closed 状态
listen 监听
1️⃣ 发送握手①SYN=1 seq=x 进入SYN-SENT(同步已发送状态)
2️⃣ 发送握手② SYN=1 ACK=1 seq=y ack=x+1 进入SYN-REVD (同步已接收状态)
3️⃣ 发送握手③ACK=1 seq=x+1 ack=y+1 进入 ESTABLISHED(连接已建立状态)
进入ESTABLISHED(连接已建立状态)
开始数据传输开始数据传输

3.2 释放连接

1

客户端服务器
双方建立连接状态双方建立连接状态
1️⃣ 发送挥手① FIN=1 ACK=1 seq=u ack=v 进入 FIN_WAIT_1(终止等待1状态)
2️⃣ 发送挥手② ACK=1 seq=v ack=u+1 并进入CLOSE_WAIT(关闭等待状态)
收到挥手②进入 FIN_WAIT_2(终止等待2状态)通知应用进程断开连接,客户端到服务器方向连接关闭,属于半关闭状态。
接受数据发送未发完的数据
3️⃣ 发送挥手③ FIN=1 ACK=1 seq=w ack=u+1 进入 LAST-ACK(最后确认状态)
4️⃣ 发送挥手④ ACK=1 seq=u+1 ack=w+1 进入 TIME_WAIT(时间等待状态)
收到挥手④后进入 CLOSED(关闭状态)
经过2MSL后, 进入 CLOSED(关闭状态)

4. 可靠性

4.1 如何保证可靠性

  1. 基于数据块传输:应用数据被分割成 TCP 认为最适合发送的数据块,再传输给网络层,数据块被称为报文段或段。
  2. 对失序数据包重新排序以及去重:TCP 为了保证不发生丢包,就给每个包一个序列号,有了序列号能够将接收到的数据根据序列号排序,并且去掉重复序列号的数据就可以实现数据包去重。
  3. 校验和 : TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。
  4. 超时重传 : 当发送方发送数据之后,它启动一个定时器,等待目的端确认收到这个报文段。接收端实体对已成功收到的包发回一个相应的确认信息(ACK)。如果发送端实体在合理的往返时延(RTT)内未收到确认消息,那么对应的数据包就被假设为已丢失并进行重传。(重传只针对SYN和SYN+ACK,也就是第一次和第二次握手,ACK 报文是不会重传的)
  5. 流量控制 : TCP 连接的每一方都有固定大小的缓冲空间,TCP 的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议(TCP 利用滑动窗口实现流量控制)。
  6. 拥塞控制 : 当网络拥塞时,减少数据的发送。

4.2 为什么不两次握手

主要是为了避免历史连接。

假如第一个连接发送失败,重传后过了好久好久,到达了。

服务器对失败后的连接又建立了一次请求, 但客户端可能处于关闭了都无法理会,服务器就无法释放这个连接。

4.3 为什么 TIME_WAIT

参考:https://www.liuvv.com/p/7b71ec65.html

因为客户端发送挥手④的时候,有可能失败。

如果客户端直接关闭,服务器重发挥手③,客户端处于关闭不响应,服务器无法释放资源。

4.4 保活计时器

假如建立连接后, 客户端出现了故障

  • 服务器每次收到请求后, 重新启动定时器(2小时)。
  • 服务器2小时后没收到客户端请求,发送探测报文段。
  • 服务器75秒间隔发送一个,达到 9 个无响应,关闭连接。

也就是说在 Linux 系统中,最少需要经过 2 小时 11 分 15 秒才可以发现一个「死亡」连接。

4.5 流量控制和拥塞控制

参考:https://www.liuvv.com/p/7eb83068.html

4.6 如何避免 SYN 攻击

服务端每接收到一个 SYN 报文,就进入SYN_RCVD 状态,但服务端发送出去的 ACK + SYN 报文,无法得到未知 IP 主机的 ACK 应答,久而久之就会占满服务端的半连接队列,使得服务端不能为正常用户服务。

  1. 调大 netdev_max_backlog

    当网卡接收数据包的速度大于内核处理的速度时,会有一个队列保存这些数据包。控制该队列的最大值如下参数,默认值是 1000,我们要适当调大该参数的值,比如设置为 10000

  2. 增大 TCP 半连接队列

  3. 开启 net.ipv4.tcp_syncookies

    开启 syncookies 功能就可以在不使用 SYN 半连接队列的情况下成功建立连接,相当于绕过了 SYN 半连接来建立连接。

  4. 减少 SYN+ACK 重传次数

    SYN-ACK 报文的最大重传次数由 tcp_synack_retries内核参数决定(默认值是 5 次),比如将 tcp_synack_retries 减少到 2 次:

5. 头脑风暴

  • socket 过程:服务器 bind+listen+for(accept),客户端 connect,相互 read+write。
  • 三次握手:SYN+ 序列号,SYN+ACK+ 序列号增加,ACK+ 序列号增加,最后进入 ESTABLISHED。
  • 四次挥手:客户端 FIN,服务器 ACK 进入 CLOSE_WAIT,服务器继续发消息,服务器 FIN,客户端 ACK 进入 TIME_WAIT。
  • TIME_WAIT: 只是主动端特有的状态。如果不等待直接close,万一第四次挥手失败,服务器重发得不到响应,无法释放。

6. 参考资料

可以加首页作者微信,咨询相关问题!