加入收藏 | 设为首页 | 会员中心 | 我要投稿 漯河站长网 (https://www.0395zz.cn/)- 云服务器、混合云存储、网络、内容创作、云渲染!
当前位置: 首页 > 服务器 > 搭建环境 > Linux > 正文

4个实验,彻底搞懂TCP连接的断开

发布时间:2022-08-09 10:01:37 所属栏目:Linux 来源:互联网
导读:前言 看到这个标题你可能会说,TCP 连接的建立与断开,这个我熟,不就是三次握手与四次挥手嘛。且慢,脑海中可以先尝试回答这几个问题: 四次挥手是谁发起的? 如果断电/断网了连接会断开吗? 什么情况下没有四次挥手连接也会断开? 这不是面试,而是遇到了
  前言
  看到这个标题你可能会说,TCP 连接的建立与断开,这个我熟,不就是三次握手与四次挥手嘛。且慢,脑海中可以先尝试回答这几个问题:
 
  四次挥手是谁发起的?
  如果断电/断网了连接会断开吗?
  什么情况下没有四次挥手连接也会断开?
  这不是面试,而是遇到了实际问题,至于是什么问题,容我先卖个关子,本文也不会解答,后面会有一篇专门的文章来说遇到的问题是啥,所以在讲实际问题之前,先弄懂理论。
 
  正常断开
  我们由浅入深,先了解正常情况下 TCP 连接是如何断开的,下图为 TCP 三次握手与四次挥手的经典图(来自《TCP/IP详解卷1》)
 
  image
 
  在我们的电脑上,可以使用 **python 的 SimpleHTTPServer ** 来快速起一个 http 服务(http 也是基于 TCP 协议),比如这样:
 
  python -m SimpleHTTPServer 20880
 
  再通过 nc 或 telnet 这两个命令来创建 TCP 连接,比如我测试使用 nc 来创建连接
 
  nc -v ip port
 
  Connection to ip port [tcp/*] succeeded! 表示连接成功
 
  image
 
  我们如何观察这个连接呢?可以通过 netstat 或 lsof 来查看这条"连接",这里我使用 lsof(mac 与 Linux 系统的 netstat 命令不太一样,使用起来有点别扭 )
 
  lsof -i:20880
 
  image
 
  无论是客户端还是服务端都会占用一个端口,不过服务端端口是固定的,客户端端口是随机的。
 
  如果我们想看 TCP 连接和断开时握手与挥手的 TCP 报文怎么查看呢?可以使用 tcpdump 命令
 
  三次握手
  tcpdump -A -vv -i any -S host 10.179.245.95
 
  为了方便查看,和上面的经典图放在了一起
 
  image
 
  这里的参数需要提一下的是 -S,如果不加 -S 参数看到的第三次握手的ack=1,与书上的理论不太一样,其实这里只是 tcpdump 简化了展示,想看实际值需要加 -S
 
  这里的 Flags [S]/[S.]/[.]
 
  [S] 代表 SYN
  [.] 代表 ACK,[S.] 就是 SYN + ACK
  四次挥手
  命令与抓三次握手相同,我们抓到如下挥手数据
 
  image
 
  [F] 代表 FIN
  这张图有点奇怪,四次挥手居然变成了三次,这其实是 TCP 协议的实现问题,如果第二次与第三次挥手之间没有数据发送,那么被动断开连接的一方就可能会把第二次的 ACK 与 第三次的 FIN 合并为一次挥手。
 
  当然我也抓到过正常的四次挥手,大概长这样
 
  image
 
  异常断开
  上面铺垫了这么多,现在开始进入正题。
 
  TCP 连接断开是谁发起的
  我们来思考一个问题:TCP 连接的断开是谁发起的?程序本身还是操作系统?
 
  我们来看一段非常简单的 TCP 连接创建与断开的代码
 
  tcpAddr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:20880")
  conn, err := net.DialTCP("tcp", nil, tcpAddr)
  if err != nil {
   fmt.Println("Client connect error ! " + err.Error())
   return
  }
 
  defer func() {
   err := conn.Close()
   fmt.Println("Client connect closed !")
   if err != nil {
   fmt.Println(err)
   }
  }()
 
  fmt.Println(conn.LocalAddr().String() + " : Client connected!")
  time.Sleep(10 * time.Second)
  运行后,效果如下,也符合我们预期:当程序打印 Client connected! 时,能看到连接,当打印 Client connect closed! 时,连接断开
 
  image
 
  如果我们在连接断开前使用 kill -9 强杀进程呢?(这里我用了两台电脑来测试)
 
  image
 
  我们发现 conn.Close() 并没有执行,但四次挥手还是发生了!
 
  查阅资料发现如下结论:
 
  a、b 两个正常连接的对端进程。假如 b 进程没有调用 close 就异常终止,那么发送 FIN 包是内核 OS 代劳
 
  断电/断网时的连接是怎样断开的
  我们通过上面的实验发现就算进程异常终止,操作系统也会帮忙发起四次挥手
 
  但如果是断电或断网的情况下,操作系统就无法代劳了,这时会怎样呢?为了便于测试,这里用两台电脑,client 连接 server,断开 server 的网络来模拟断网断电情况。
 
  可以肯定的是断网,断电后,连接不会立即断开,那么后续连接是否会断开呢?我们分成下面几种情况来看
 
  断网时有数据传输
  断网时如果有数据发送,由于收不到 ACK,所以会重试,但并不会无限重试下去,达到一定的重发次数之后,如果仍然没有任何确认应答返回,就会判断为网络或者对端主机发生了异常,强制关闭连接。此时的关闭是直接关闭,而没有挥手(数据都发不出去,还挥啥手),Linux 下的设置为
 
  最小重传时间是200ms
  最大重传时间是120s
  重传次数为15

(编辑:漯河站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读