今天看啥  ›  专栏  ›  zhangdianp

TCP 连接状态及相关命令学习

zhangdianp  · 掘金  ·  · 2019-12-08 14:18
阅读 31

TCP 连接状态及相关命令学习

在平时的开发工作中,我们都使用被封装完好的 TCP/HTTP 库去完成需求开发,很少关心底层 TCP 的连接状态,但是一旦遇到较难定位的线上事故,往往都是因为 TCP 连接参数或者使用姿势不对导致的,本文对 TCP 连接状态以及相关命令做一下梳理总结:

什么是 TCP 协议

  • 位于 OSI 模型中的传输层(第四层),是一种端对端的传输协议
  • 面向连接的、可靠的协议
  • 通过校验和、序列号、确认应答、重发控制、窗口控制等机制实现可靠传输
  • 由建立连接、数据传输和连接释放三个阶段组成
  • 采用三次握手建立连接,采用四次挥手关闭连接

想了解 TCP 的连接状态,必须先理清楚 TCP 的三次握手和四次挥手是怎么回事,下图关于 TCP 三次握手和四次挥手的状态转换图(图片来自于知乎文章 《“三次握手,四次挥手”你真的懂吗?》):

这里不要把图里的 Client/Server 和项目里的客户端服务器端混淆,主动发起连接的一方或者主动关闭连接的一方就是 Client,被动的一方便为 Server。一个服务既可以充当 Client 的角色,也可以充当 Server 的角色。

TCP 的三次握手

三次握手为了使 Client 和 Server 都确认是否有接受对方的数据和发送数据给对方的能力:

  • 第一次握手:Client 先发送一个 SYN(j) 包作为建立连接的请求,确认自己的发送能力是否正常
  • 第二次握手:Server 针对 Client 的 SYN 包回复一个 ACK(j + 1) 包确认应答,同时发送 SYN(k) 包给 Client 表示要求建立连接,在明确自己有接收能力的同时确认一下自己的发送能力
  • 第三次握手:Client 收到 SYN + ACK 包后发送 ACK(k + 1) 包确认应答,表示自己的接收能力正常,此时完成三次握手建立可靠的连接

TCP 的四次挥手

四次挥手主要是为了让 Client 和 Server 双方都可以正常关闭连接,保证关闭连接前不丢失数据:

  • 第一次挥手:Client 先发送一个 FIN 包表示请求断开连接
  • 第二次挥手:当 Server 收到 FIN 包后,立即回复 ACK 进行确认应答,表示我已经收到你关闭连接的请求,此时 Server 还有接收数据的能力
  • 第三次挥手:一段时间后,当 Server 端确认 Client 端的数据已经接受完毕,发送一个 FIN 包表示关闭连接,不再接收数据
  • 第四次挥手:当 Client 收到 FIN 后,立即回复一个 ACK 进行确认,然后等待 2MSL 后关闭连接

TCP 的连接状态

在 TCP 的三次握手、数据传输以及四次挥手的过程中,我们给 Client 和 Server 定义了很多状态用于描述整个流程,结合上面的状态转换图来理解这些状态定义:

  • LISTEN(Server): 正在侦听来自远方的 TCP 端口的连接请求,服务端启动后处于 LISTEN 状态用于监听不同客户端的 TCP 请求并建立连接
  • SYN-SENT(Client): 三次握手时,Client 在发送 SYN 以请求后处于等待建立连接的状态
  • SYN_RCVD(Server):三次握手时,当 Server 收到 Client 的 SYN 信号时,将标志位 ACK 和 SYN 发送给 Client 后到建立连接之间,Server 处于 SYN_RCVD 状态
  • ESTABLISHED(Server And Client):三次握手成功以后,Client 和 Server 处于数据传输的状态
  • FIN-WAIT-1(Client):四次挥手时,Client 端发送中断请求 FIN 到收到 Server 端的中断确认的过程
  • CLOSE_WAIT(Server):四次挥手时,Client 接收到 Client 的 FIN 请求响应后回复 ACK 确认到发送 FIN 包的状态
  • FIN-WAIT-2(Client):四次挥手时,当 Client 接收到 Server 对于 FIN 的响应 ACK 后到收到 Server 端的 FIN 包的状态
  • LAST_ACK(Server):四次挥手时,Server 发送 FIN 请求关闭连接到关闭连接前的状态
  • TIME_WAIT(Client):四次挥手时 Client 对于 Server 的 FIN 回复 ACK 到连接关闭前的状态,又称 2MSL 状态
  • CLOSE(Server And Client):Server 和 CLient 关闭连接后的状态

正常情况下,客户端的状态转换流程如下:

CLOSED -> SYN_SENT -> ESTABLISHED -> FIN_WAIT_1 -> FIN_WAIT_2 -> TIME_WAIT -> CLOSED
复制代码

正常情况下,服务端的状态转换流程如下:

CLOSED -> LISTEN -> SYN_RCVD -> ESTABLISHED -> CLOSE_WAIT -> LAST_ACK -> CLOSED
复制代码

关于 TCP 三次握手和四次挥手状态转换中的一些问题

  • 什么是 MSL ?
- MSL 全称是 maximum segment lifetime,报文最大生存时间
- MSL 是任何 IP 数据报能够在因特网存活的最长时间,超过这个时间报文将被丢弃
- MSL 的具体值通常为 30 秒或者是 2 分钟
复制代码
  • 什么是 TTL ?
- TTL(time to live) 通常表示在数据传输中,包在被丢弃前最多能经过的路由器个数
- 每经过一个路由器都至少要把 TTL 减 1,当记数到 0 时,路由器决定丢弃该包,并发送一个 ICMP 报文给最初的发送者(表示不可达)
- TTL 是由发送主机设置的,以防止数据包不断在 IP 互联网络上永不终止地循环
- TTL 的值可以修改,但是不能超过 256,一般是 64,128...
- ping 命令返回的 TTL 值表示当包达到目标主机时,还剩下的 TTL 值,所以假设系统 TTL 值是 64,ping 出来的 TTL 结果是 58,那么从源主机到达目标主机需要经过 64 - 58 = 6 次跳转
- traceroute 在追踪网络数据包传输路由时,也可以通过参数 -m 指定 TTL,表示经过的网关最大数量
复制代码
  • 什么是 RTT ?
- RTT 是客户到服务器往返所花时间(round-trip time,简称RTT)
- TCP 含有动态估算 RTT 的算法,TCP 还持续估算一个给定连接的 RTT,因为 RTT 受网络传输拥塞程序的变化而变化
复制代码
  • 什么是 MTU ?
- Maximum Transmit Unit(最大传输单元),即物理接口(数据链路层)提供给其上层最大一次传输数据的大小
- 每个网络接口都可以设置自己的 MTU 值,默认值一般为 1500,表示整个 IP 包从这个接口最大可以发送 1500 个字节
- 因为一般情况下 IP 层传输的数据大于 MTU 值,需要需要采用分片技术进行传输
- 路径 MTU:任意两台主机之间的 MTU 不一定相同,因为数据在传输中会经过多台主机,需要找出所有路径中最小的 MTU(路径 MTU)进行传输
            否则数据传输会失败,一般通过 ICMP 协议进行路径 MTU 的发现
复制代码
  • 什么是 MSS ?
- Maximum Segment Size(最大分段大小), 不包含 TCP Header 和 TCP Option,只包含 TCP Payload
- MSS 是 TCP 用来限制应用层最大的发送字节数,如果底层物理接口 MTU = 1500 byte,则 MSS = 1500- 20(IP Header) -20 (TCP Header) = 1460 byte
复制代码
  • 为什么建立连接协议是三次握手,而关闭连接却是四次挥手呢?
主要是因为建立连接之前双方都没有任何数据传输发生,双方处于干净的状态,所以 Server 可以直接把 ACK 和 SYN 放在一个数据包里发送到 Client,
少一次数据传输的过程。而四次挥手时,Client 发送了 FIN 报文,仅仅表示 Client 已经没有数据要发送给 Server 了,但是 Client 仍然可以接受数据,
所以当 Server 收到 Client 数据的 FIN 报文后,它可以对于 FIN 请求进行应答(ACK),但是不能同时发送 FIN 也请求关闭连接,因为此时可能存在没有
传输完的数据,而一旦发送 FIN 报文就意味着要停止发送数据,所以需要等待数据传输完再发送 FIN 报文,因此需要四次挥手。
复制代码
  • 四次挥手时,Client 完成对于 Server 的 ACK 后为什么不立即关闭连接,要存在一个 TIME_WAIT(2MSL) 状态?
之所以要等待这个状态主要有两个原因:
- 最后一次挥手数据很有可能丢失,维持这个状态主要为了可以继续重发 ACK
- 因为 TCP 发送出去的包和接收到的包顺序不一定保持一致,如果 Server 先发出去的包延迟到达后,Client 将收不到,并且会干扰新建立的连接
复制代码
  • 如果服务器上存在大量 TIME_WAIT 情况,如何改善?
如果出现 TIME_WAIT 状态一般因为客户端频繁的建立和关闭连接,从而可能影响后续连接的建立,这种情况一般可以通过调节系统参数优化,修改这些参数要慎重:
- tcp_tw_recycle:表示开启 TCP 连接中 TIME-WAIT sockets 的快速回收
- tcp_tw_reuse:表示开启重用。允许将 TIME-WAIT sockets 重新用于新的 TCP 连接
- tcp_max_tw_buckets:指定系统在同一时间最多能有多少 TIME_WAIT 状态,当超过这个值时,系统会直接干掉 TIME_WAIT 的 socket
复制代码
  • 如果服务器上存在大量 CLOSE_WAIT 情况,如何改善?
- 存在大量 CLOSE_WAIT 状态一般都是自身代码的原因,所以首先还是从自身的代码出发,不要随便甩锅
- 一般是因为服务器调用第三方应用(mysql, redis等)时,处理完结果忘记关闭连接,导致第三方应用因为超时发起主动关闭,而服务器无响应进而出现大量 CLOSE_WAIT 状态
- 例如在 mysql 连接过程中,sql 回滚或者提交时忘记关闭与 mysql 的连接,导致 mysql 因为超时主动关闭连接,就会大量 CLOSE_WAIT 的状态
- 例如在 javascript 中同步和异步代码乱用,导致处理完请求后无法回调错误引起超时
复制代码

SYN FLOOD 攻击

  • 什么是 SYN FLOOD 攻击:

在进行三次握手时,攻击软件向被攻击的服务器发送 SYN 连接请求(三次握手的第一步),但是客户端的地址是伪造的,服务器在收到连接请求时将标志位 ACK 和 SYN 发送给客户端(三次握手的第二步),但是由于这些客户端的 IP 地址都是伪造的,服务器根本找不到客户端,导致出现大量握手失败的情况。这种情况下服务器端一般会选择重试(再次发送 SYN+ACK 给客户端)并等待一段时间后丢弃这个未完成的连接,这段时间的长度我们称为 SYN Timeout,服务器端将为了维护一个非常大的半连接列表而消耗非常多的资源,消耗非常多的 CPU 时间和内存,可能导致服务器失去响应,SYN FLOOD 攻击就达到目的。

  • 如何防御 SYN FLOOD 攻击:
1. 可以减少半开状态下等待 ACK 消息的时间 
2. 减少重试发送 SYN-ACK 消息的次数 
3. 部署支持“ IP 防伪”的路由器,将伪造过 IP 地址的 SYN 消息直接过滤掉
4. SYN Cache:分多个 SYN 缓存 Bucket,进入每个 Bucket 是随机的,每个 bucket 处于 SYN 的连接个数有限,超过限制就踢出先加入的 SYN 连接,
   攻击者很难均匀填满所有 Bucket
5. SYN Cookies:服务端通过特定的算法把半开连接信息编码成 “Cookie”,用作服务端给客户端的消息编号(SequenceNum),随 SYN-ACK
   消息一同返回给连接发起方客户端,这样在连接完全建立前服务端不保存任何信息。也不用一直等待,缺点是服务端丧失了重发 SYN-ACK 的能力
复制代码

关于 Socket

  • 一个 TCP 连接对应一个 Socket
  • 一个 Socket 的唯一标识是: {SRC-IP, SRC-PORT, DEST-IP, DEST-PORT, PROTOCOL}
  • 一个处于监听状态的 TCP 服务可以同时接口来自多个客户端的 Socket
  • 不同进程可以监听同一个端口,如果他们的协议(TCP/UDP)不同
  • 一个进程可以打开和关闭多个 Socket
  • 子进程可以继承所有的文件描述符(FD)从父进程上,所以不同的进程或者线程之间如果有父子关系,可以使用同一个Socket
  • 一个处于监听状态的 TCP 服务只需要一个监听端口,但可以建立多个 Socket
  • 服务器一个端口可以创建的 socket 连接数理论上是没有上限的,取决于系统的内存大小和可以创建的文件描述符的上限,可以通过修改文件描述符上限进行设置

TCP 连接中的相关命令

netstat 命令

netstat 用于打印网络连接、路由表、连接的数据统计等,我们上文中介绍的各种连接状态都可以通过该命令进行统计

  • 参数介绍
- -a:列出所有状态的连接
- -l:列出正在监听状态(State=LISTEN的连接
- -t:列出所有 tcp 协议的连接
- -u:列出所有 udp 协议的连接
- -n: 开启域名解析,将对应的域名解析为 IP
- -p:列出正在监听的进程名称和进程 PID(PID/Program name),在 root 用户下启动的进程,普通用户是查不到对应的进程名称的
- -e:列出进程对应的用户 ID 或者用户(USER),是用户 ID 还是用户名由 -n 参数决定
- -s:打印每种协议网络包统计数据
- -r:打印内核路由信息
- -i:打印网络接口信息,同 ifconfig 命令
复制代码
  • 列出所有 tcp 的连接:
$ netstat -ant

Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 127.0.0.1:15778         0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:514             0.0.0.0:*               LISTEN
tcp        0      0 10.122.139.165:62411    10.199.136.43:80        ESTABLISHED
tcp        0      0 106.2.124.34:443        115.236.119.139:39120   ESTABLISHED
...
复制代码
  • 只列出处于监听状态的连接:
$ netstat -tnl

Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 127.0.0.1:15778         0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:514             0.0.0.0:*               LISTEN
...
复制代码
  • 查看监听中的进程名和用户名
$ netstat -tnl

(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       User       Inode       PID/Program name
tcp        0      0 localhost:15778         *:*                     LISTEN      root       538503951   -
tcp        0      0 *:shell                 *:*                     LISTEN      root       509846560   -
tcp        0      0 localhost:15779         *:*                     LISTEN      root       538504573   -
复制代码
  • 查看网络接口:

和 ifconfig 和 ip a 的效果一样

$ netstat -ie
复制代码
  • 统计 TCP 每个连接状态信息:
$ netstat -n | awk `/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}`

LAST_ACK 5
SYN_RECV 30
ESTABLISHED 15
FIN_WAIT1 51
FIN_WAIT2 5
TIME_WAIT 10
复制代码
ping 命令

ping 命令用于测试到达目的主机的网络是否可用,可以接收域名或者 ip,不能检查端口,工作在 OSI 参考模型的第三层-网络层

ping 命令和 traceroute 命令一样都是通过 ICMP 协议实现的,尽管 ping 工具也可以进行侦测,但是因为 ip 头的限制,ping 不能完全的记录下所经过的路由器。所以 traceroute 正好就填补了这个缺憾,所以如果想查询整个路由信息还是需要 traceroute

# 直接 ping ip
$ ping 10.165.124.134
PING 10.165.124.134 (10.165.124.134): 56 data bytes
64 bytes from 10.165.124.134: icmp_seq=0 ttl=63 time=3.923 ms
64 bytes from 10.165.124.134: icmp_seq=1 ttl=63 time=4.103 ms

# ping域名,可以获取到指定域名的 ip
$ ping dev.youdata.com
PING dev.youdata.com (106.2.44.33): 56 data bytes
64 bytes from 106.2.44.33: icmp_seq=0 ttl=58 time=3.477 ms
64 bytes from 106.2.44.33: icmp_seq=1 ttl=58 time=3.904 ms
64 bytes from 106.2.44.33: icmp_seq=2 ttl=58 time=3.878 ms

// -c 参数指定发送包的个数
$ ping -c 2 dev.youdata.com

// -W 参数指定 ping 命令的等待时间
$ ping -W 2 dev.youdata.com
复制代码
traceroute 命令

raceroute 原理是利用 ICMP 的 ttl expired 通知机制,每次通过不断增加 ttl 从而不断发现下一跳路由,traceroute 发送的是端口号 > 30000 的数据报,所以到达目的主机的时候,会收到端口不可达的 ICMP 回应,这个时候源主机就知道主机可以连通了。

$ traceroute www.baidu.com
traceroute to www.baidu.com (61.135.169.125), 30 hops max, 40 byte packets
 1  192.168.74.2 (192.168.74.2)  2.606 ms  2.771 ms  2.950 ms
 2  211.151.56.57 (211.151.56.57)  0.596 ms  0.598 ms  0.591 ms
 3  211.151.227.206 (211.151.227.206)  0.546 ms  0.544 ms  0.538 ms
 4  210.77.139.145 (210.77.139.145)  0.710 ms  0.748 ms  0.801 ms
 5  202.106.42.101 (202.106.42.101)  6.759 ms  6.945 ms  7.107 ms
 6  61.148.154.97 (61.148.154.97)  718.908 ms * bt-228-025.bta.net.cn (202.106.228.25)  5.177 ms
 7  124.65.58.213 (124.65.58.213)  4.343 ms  4.336 ms  4.367 ms
 8  202.106.35.190 (202.106.35.190)  1.795 ms 61.148.156.138 (61.148.156.138)  1.899 ms  1.951 ms
 9  * * *
30  * * *
复制代码
  • raceroute 命令可以让你实现追踪网络数据包的路由途径,以及每个网关消耗的时间,预设数据包大小是 40 Bytes
  • raceroute 输出结果会看到有一些行是以星号表示的记录,可能是因为防火墙封掉了 ICMP 的返回信息,所以我们得不到什么相关的数据包返回数据
  • 每次数据包由某一同样的出发点(source)到达某一同样的目的地(destination)走的路径可能会不一样,但基本上来说大部分时候所走的路由是相同的
  • 每个跳跃点默认情况下会发送 3 次数据包,但是不一定会是同一个 ip,因为两个网关之间也有可能有负载均衡策略,所以有些记录中可能会有 2 个或者 3 个 ip
  • ip 头所能纪录的路由列表是非常有限的,所以 traceroute 采取不断增加 ttl 的方式去发送,这也正是 traceroute 越到后面记录返回越慢的原因
  • 主要参数说明:
-q: 探测数据包向每个网关发送数据包的个数,默认是 3 次,会记录每次的时间长度
-m: 设置数据包经过网关的最大个数(TTL) 
-n: 显示 ip,不查主机名
复制代码
lsof 命令

lsof(list open files)是一个查看当前系统文件的工具。在 linux 环境下,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件。例如传输控制协议 (TCP) 和用户数据报协议 (UDP) 套接字等,系统都会在后台都为该应用程序分配了一个文件描述符,该文件描述符提供了大量关于这个应用程序本身的信息。

  • 参数说明
- -a: 列出打开文件存在的进程查找某个文件相关的进程
- -c<进程名>: 列出指定进程所打开的文件
- -g:列出GID号进程详情
- -d<文件号>: 列出占用该文件号的进程
- +d<目录>: 列出目录下被打开的文件
- +D<目录>: 递归列出目录下被打开的文件
- -n<目录>: 列出使用NFS的文件
- -i<条件>: 列出符合条件的进程。(4、6、协议、:端口、 @ip )
- -p<进程号>: 列出指定进程号所打开的文件
- -u: 列出UID号进程详情
- -h: 显示帮助信息
- -v: 显示版本信息
复制代码
  • 查找某个文件相关的进程
$ lsof /bin/bash
复制代码
  • 列出某个用户打开的文件信息
$ lsof -u username
复制代码
  • 列出某个程序进程所打开的文件信息
$ lsof -c mysql
复制代码
  • 通过某个进程号显示该进程打开的文件
$ lsof -p 11968
复制代码
  • 列出所有 tcp 网络连接信息
$ lsof -i tcp
复制代码
  • 列出某个端口被哪个进程占用
$ lsof -i :3306
复制代码
telnet 命令
  • telnet 命令用于登录远程主机
  • telnet 因为采用明文传送报文,安全性不好,很多 Linux 服务器都不开放 telnet 服务,而改用更安全的 ssh 方式
  • telnet 位于 OSI 模型的第七层(应用层)的一种协议
  • telnet ip port 用于测试远程主机的某个端口是否开放,而 ping 命令是测试远程主机网络是否能通

参考文献




原文地址:访问原文地址
快照地址: 访问文章快照