网络 - TCP(1)

123 阅读6分钟

什么是TCP

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

  • 面向连接:一定是「一对一」才能连接,不能像 UDP 协议可以一个主机同时向多个主机发送消息,也就是一对多是无法做到的;
  • 可靠的:无论的网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端;
  • 字节流:用户消息通过 TCP 协议传输时,消息可能会被操作系统「分组」成多个的 TCP 报文,如果接收方的程序如果不知道「消息的边界」,是无法读出一个有效的用户消息的。并且 TCP 报文是「有序的」,当「前一个」TCP 报文没有收到的时候,即使它先收到了后面的 TCP 报文,那么也不能扔给应用层去处理,同时对「重复」的 TCP 报文会自动丢弃

TCP 头格式

image.png

序列号:在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题

确认应答号:指下一次「期望」收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决丢包的问题

控制位:

  • ACK:该位为 1 时,「确认应答」的字段变为有效,TCP 规定除了最初建立连接时的 SYN 包之外该位必须设置为 1 。
  • RST:该位为 1 时,表示 TCP 连接中出现异常必须强制断开连接。
  • SYN:该位为 1 时,表示希望建立连接,并在其「序列号」的字段进行序列号初始值的设定。
  • FIN:该位为 1 时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 FIN 位为 1 的 TCP 段

如何唯一确定一个TCP连接

TCP 四元组可以唯一的确定一个连接,四元组包括如下:

  • 源地址
  • 源端口
  • 目的地址
  • 目的端口

源地址和目的地址的字段(32 位)是在 IP 头部中,作用是通过 IP 协议发送报文给对方主机

源端口和目的端口的字段(16 位)是在 TCP 头部中,作用是告诉 TCP 协议应该把报文发给哪个进程。

TCP 建立连接

建立连接是通过三次握手来进行的

image.png

从上面的过程可以发现第三次握手是可以携带数据的,前两次握手是不可以携带数据的

为什么是三次握手

  • 三次握手才可以阻止重复历史连接的初始化(主要原因)
  • 三次握手才可以同步双方的初始序列号
  • 三次握手才可以避免资源浪费

如何避免资源浪费:

如果只有「两次握手」,当客户端发生的 SYN 报文在网络中阻塞,客户端没有接收到 ACK 报文,就会重新发送 SYN ,由于没有第三次握手,服务端不清楚客户端是否收到了自己回复的 ACK 报文,所以服务端每收到一个 SYN 就只能先主动建立一个连接

tcp 断开连接

TCP 断开连接是通过四次挥手方式

image.png

  • 客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文,也即 FIN 报文,之后客户端进入 FIN_WAIT_1 状态。
  • 服务端收到该报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSE_WAIT 状态。
  • 客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态。
  • 等待服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。
  • 客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态
  • 服务端收到了 ACK 应答报文后,就进入了 CLOSE 状态,至此服务端已经完成连接的关闭。
  • 客户端在经过 2MSL 一段时间后,自动进入 CLOSE 状态,至此客户端也完成连接的关闭

主动关闭连接的,才有 TIME_WAIT 状态

为什么 TIME_WAIT 等待的时间是 2MSL

MSL 是 Maximum Segment Lifetime,报文最大生存时间Linux 将 MSL 设置为 30 秒

2MSL时长 这其实是相当于至少允许报文丢失一次。比如,若 ACK 在一个 MSL 内丢失,这样被动方重发的 FIN 会在第 2 个 MSL 内到达,TIME_WAIT 状态的连接可以应对

TIME_WAIT 过多有什么危害

  • 第一是占用系统资源,比如文件描述符、内存资源、CPU 资源、线程资源等
  • 第二是占用端口资源,端口资源也是有限的,一般可以开启的端口为 32768~61000

如何优化TIME_WAIT

  • 打开 net.ipv4.tcp_tw_reuse 和 net.ipv4.tcp_timestamps 选项, 复用处于 TIME_WAIT 的 socket 为新的连接所用
  • net.ipv4.tcp_max_tw_buckets,当系统中处于 TIME_WAIT 的连接一旦超过这个值时,系统就会将后面的 TIME_WAIT 连接状态重置
  • 程序中使用 SO_LINGER ,应用强制使用 RST 关闭,会立该发送一个RST标志给对端,该 TCP 连接将跳过四次挥手,也就跳过了TIME_WAIT状态,直接关闭

服务器出现大量 TIME_WAIT 状态的原因有哪些

  • 第一个场景:HTTP 没有使用长连接
  • 第二个场景:HTTP 长连接超时
  • 第三个场景:HTTP 长连接的请求数量达到上限

服务器出现大量 CLOSE_WAIT 状态的原因有哪些

CLOSE_WAIT 状态是「被动关闭方」才会有的状态,当服务端出现大量 CLOSE_WAIT 状态的连接的时候,说明服务端的程序没有调用 close 函数关闭连接

如何在linux下查看tcp状态

netstat -napt

image.png

什么是syn 攻击

TCP 连接建立是需要三次握手,假设攻击者短时间伪造不同 IP 地址的 SYN 报文,服务端每接收到一个 SYN 报文,就进入SYN_RCVD 状态,但服务端发送出去的 ACK + SYN 报文,无法得到未知 IP 主机的 ACK 应答,久而久之就会占满服务端的半连接队列,使得服务端不能为正常用户服务

正常流程:

  • 当服务端接收到客户端的 SYN 报文时,会创建一个半连接的对象,然后将其加入到内核的「 SYN 队列」;
  • 接着发送 SYN + ACK 给客户端,等待客户端回应 ACK 报文;
  • 服务端接收到 ACK 报文后,从「 SYN 队列」取出一个半连接对象,然后创建一个新的连接对象放入到「 Accept 队列」;
  • 应用通过调用 accpet() socket 接口,从「 Accept 队列」取出连接对象

SYN 攻击方式最直接的表现就会把 TCP 半连接队列打满,这样当 TCP 半连接队列满了,后续再在收到 SYN 报文就会丢弃,导致客户端无法和服务端建立连接

避免 SYN 攻击方式,可以有以下四种方法:

  • 调大 netdev_max_backlog;
  • 增大 TCP 半连接队列;
  • 开启 tcp_syncookies;
  • 减少 SYN+ACK 重传次数