前言

看到这个标题你可能会说,TCP 连接的建立与断开,这个我熟,不就是三次握手与四次挥手嘛。且慢,脑海中可以先尝试回答这几个问题:

  • 四次挥手是谁发起的?
  • 如果断电/断网了连接会断开吗?
  • 什么情况下没有四次挥手连接也会断开?

这不是面试,而是遇到了实际问题,至于是什么问题,容我先卖个关子,本文也不会解答,后面会有一篇专门的文章来说遇到的问题是啥,所以在讲实际问题之前,先弄懂理论。

正常断开

我们由浅入深,先了解正常情况下 TCP 连接是如何断开的,下图为 TCP 三次握手与四次挥手的经典图(来自《TCP/IP详解卷1》)

在我们的电脑上,可以使用 **python 的 SimpleHTTPServer ** 来快速起一个 http 服务(http 也是基于 TCP 协议),比如这样:

python -m SimpleHTTPServer 20880

再通过 nctelnet 这两个命令来创建 TCP 连接,比如我测试使用 nc 来创建连接

nc -v ip port

Connection to ip port [tcp/*] succeeded! 表示连接成功

我们如何观察这个连接呢?可以通过 netstatlsof 来查看这条"连接",这里我使用 lsof(mac 与 Linux 系统的 netstat 命令不太一样,使用起来有点别扭 )

lsof -i:20880

无论是客户端还是服务端都会占用一个端口,不过服务端端口是固定的,客户端端口是随机的。

如果我们想看 TCP 连接和断开时握手挥手的 TCP 报文怎么查看呢?可以使用 tcpdump 命令

三次握手

tcpdump -A -vv -i any -S host 10.179.245.95

为了方便查看,和上面的经典图放在了一起

这里的参数需要提一下的是 -S,如果不加 -S 参数看到的第三次握手的ack=1,与书上的理论不太一样,其实这里只是 tcpdump 简化了展示,想看实际值需要加 -S

这里的 Flags [S]/[S.]/[.]

  • [S] 代表 SYN
  • [.] 代表 ACK,[S.] 就是 SYN + ACK

四次挥手

命令与抓三次握手相同,我们抓到如下挥手数据

  • [F] 代表 FIN

这张图有点奇怪,四次挥手居然变成了三次,这其实是 TCP 协议的实现问题,如果第二次与第三次挥手之间没有数据发送,那么被动断开连接的一方就可能会把第二次的 ACK 与 第三次的 FIN 合并为一次挥手。

当然我也抓到过正常的四次挥手,大概长这样

异常断开

上面铺垫了这么多,现在开始进入正题。

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! 时,连接断开

如果我们在连接断开前使用 kill -9 强杀进程呢?(这里我用了两台电脑来测试)

我们发现 conn.Close() 并没有执行,但四次挥手还是发生了!

查阅资料发现如下结论:

a、b 两个正常连接的对端进程。假如 b 进程没有调用 close 就异常终止,那么发送 FIN 包是内核 OS 代劳

断电/断网时的连接是怎样断开的

我们通过上面的实验发现就算进程异常终止,操作系统也会帮忙发起四次挥手

但如果是断电或断网的情况下,操作系统就无法代劳了,这时会怎样呢?为了便于测试,这里用两台电脑,client 连接 server,断开 server 的网络来模拟断网断电情况。

可以肯定的是断网,断电后,连接不会立即断开,那么后续连接是否会断开呢?我们分成下面几种情况来看

断网时有数据传输

断网时如果有数据发送,由于收不到 ACK,所以会重试,但并不会无限重试下去,达到一定的重发次数之后,如果仍然没有任何确认应答返回,就会判断为网络或者对端主机发生了异常,强制关闭连接。此时的关闭是直接关闭,而没有挥手(数据都发不出去,还挥啥手),Linux 下的设置为

最小重传时间是200ms

最大重传时间是120s

重传次数为15

断电/断网时没有数据传输

断网时如果没有数据传输,还得看 TCP 连接的 KeepAlive 是否打开,关于 TCP 的 KeepAlive 简介如下:

  • TCP KeepAlive 是一种在不影响数据流内容的情况下探测对方的方式,采用 保活计时器实现,当计时器被触发时,一端发送保活报文,另一端接收到报文后发送 ACK 响应
  • 它并不是 TCP 的规范,但大部分的实现都提供了这一机制
  • 该机制存在争议,有的人保活机制应该在应用程序中实现

开启KeepAlive

操作系统中有这么几个参数控制 KeepAlive 的配置:

  • Keepalive_time:空闲时间,即多长时间连接没有发送数据时开始 KeepAlive 检测
  • Keepalive_intvl:发送间隔时间,即上述代码的设置
  • Keepalive_probs:最多发送多少个检测数据包

在 Linux 上可以通过如下文件查看

cat /proc/sys/net/ipv4/tcp_keepalive_time
cat /proc/sys/net/ipv4/tcp_keepalive_intvl
cat /proc/sys/net/ipv4/tcp_keepalive_probes

如果按照这个默认值来看,得2小时没有数据传输,KeepAlive 才开始工作!

而在 Go 中只有两个参数可以设置:

conn.SetKeepAlive(true)
conn.SetKeepAlivePeriod(5 * time.Second)

其中第二个 SetKeepAlivePeriod 源码是这样的:

func setKeepAlivePeriod(fd *netFD, d time.Duration) error {
// The kernel expects seconds so round to next highest second.
secs := int(roundDurationUp(d, time.Second))
if err := fd.pfd.SetsockoptInt(syscall.IPPROTO_TCP, sysTCP_KEEPINTVL, secs); err != nil {
return wrapSyscallError("setsockopt", err)
}
err := fd.pfd.SetsockoptInt(syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE, secs)
runtime.KeepAlive(fd)
return wrapSyscallError("setsockopt", err)
}

SetKeepAlivePeriod 的参数同时设置了 tcp_keepalive_intvl 和 tcp_keepalive_time,tcp_keepalive_probes 没法设置

做个简单测试:client 开启 KeepAlive 连接 server 后,什么数据都不发送,把server 的网断掉,可以看到 KeepAlive 心跳包,一段时间后连接被置为 CLOSED 状态

关闭KeepAlive

关闭 KeepAlive 后,如果没有数据传输,连接永远不会断开

断电/断网后 server 重启再恢复

再思考一个场景,如果 client 与 server 建立连接后,没有数据传输,断掉 server 端的网络,这时如果把 server 程序重启一下,再恢复网络,那这条连接还能用吗?

如果 server 重启后,client 还是不发数据,那这条连接看起来还是可用的,因为他们根本不知道对方是个什么情况,但如果此时 client 发送一点数据给 server,你会发现 server 会发送一个 RST 给client,然后 client 就断开连接了

总结

除了正常情况之外,本文从 TCP 连接断开的角度结合实验给出了一些结论:

  • TCP 连接断开的挥手,在进程崩溃时,会由操作系统内核代劳
  • 当 TCP 连接建立后,如果某一方断电或断网,如果此时刚好正在发送数据,TCP 数据包发送失败后会重试,重试达到上限时也会断开连接
  • 当 TCP 连接建立后,如果某一方断电或断网,且这条连接没有数据传输时
    • 如果开启了 KeepAlive 则会在一定心跳检测后断开连接,这个默认检测时间大概2个多小时,比较久
    • 如果未开启 KeepAlive 则连接永远存在
  • 如果一方发送 RST 包给另一方,也是会强制对方断开连接的

搜索关注微信公众号"捉虫大师",后端技术分享,架构设计、性能优化、源码阅读、问题排查、踩坑实践。

4个实验,彻底搞懂TCP连接的断开的更多相关文章

  1. TCP连接异常断开检测(转)

    TCP是一种面向连接的协议,连接的建立和断开需要通过收发相应的分节来实现.某些时候,由于网络的故障或是一方主机的突然崩溃而另一方无法检测到,以致始终保持着不存在的连接.下面介绍一种方法来检测这种异常断 ...

  2. (转)TCP连接异常断开检测

    TCP是一种面向连接的协议,连接的建立和断开需要通过收发相应的分节来实现.某些时候,由于网络的故障或是一方主机的突然崩溃而另一方无法检测到,以致始终保持着不存在的连接.下面介绍一种方法来检测这种异常断 ...

  3. wireshark抓包分析tcp连接与断开

    其实对于网络通信的学习,最好还是能够自己抓到包详细地一下,不然只单单通过文字和图的描述印象不够深刻.本文通过实际的抓包操作来看一下tcp的连接与断开是怎样的. 首先需要去https://www.wir ...

  4. 一文搞懂TCP与UDP的区别

    摘要:计算机网络基础 引言 网络协议是每个前端工程师都必须要掌握的知识,TCP/IP 中有两个具有代表性的传输层协议,分别是 TCP 和 UDP,本文将介绍下这两者以及它们之间的区别. 一.TCP/I ...

  5. 一文彻底搞懂 TCP三次握手、四次挥手过程及原理

    原创文章出自公众号:「码农富哥」,欢迎收藏和关注,如转载请注明出处! TCP 协议简述 TCP 提供面向有连接的通信传输,面向有连接是指在传送数据之前必须先建立连接,数据传送完成后要释放连接. 无论哪 ...

  6. 40 张图带你搞懂 TCP 和 UDP

    前言 欢迎阅读「程序员cxuan」 的文章,从今往后,你就是我的读者了. 我的 github bestJavaer 已经收录此文章,目录在 https://github.com/crisxuan/be ...

  7. 循序渐进搞懂 TCP 三次握手核心

    前言 本文旨在通过形象的例子和实操,把无形的.虚拟的网络转为具体的.可视化的.带领网络小白一步步的掌握 TCP 三次握手核心知识点,为后续深入学习 TCP 协议打基础. 通俗版 如下图所示,小明(客户 ...

  8. 设置TCP_USER_TIMEOUT参数来判断tcp连接是否断开

    [TOC] 1. bug描述 前段时间遇到这样的一个问题,openstack一个控制节点宕机后,在宕机后一段时间内创建的虚拟机,一直卡在创建中的状态.有的甚至要等到16分钟之后虚拟机才会切换到下一个状 ...

  9. TCP连接与断开详解(socket通信)

    http://blog.csdn.net/Ctrl_qun/article/details/52518479 一.TCP数据报结构以及三次握手 TCP(Transmission Control Pro ...

随机推荐

  1. springMVC学习总结(三) --springMVC重定向

    根据springMVC学习总结(一) --springMVC搭建搭建项目 在com.myl.controller包下创建一个java类WebController. 在jsp子文件夹下创建一个视图文件i ...

  2. 第06课:GDB 常用命令详解(中)

    本课的核心内容: info 和 thread 命令 next.step.util.finish.return 和 jump 命令 info 和 thread 命令 在前面使用 info break 命 ...

  3. vue+element+echarts柱状图+列表

    前端由vue+element搭建框架,引入vue和element的index.js和css就可以写页面: 页面和js可以echarts官网实例看下都是有的,主要看下如何动态赋值: 柱状图和列表: &l ...

  4. Linux-实战常用命令

    目录 关机/重启/注销 系统信息和性能查看 磁盘和分区 ⽤户和⽤户组 ⽹络和进程管理 常⻅系统服务命令 ⽂件和⽬录操作 ⽂件查看和处理 打包和解压 RPM包管理命令 YUM包管理命令 DPKG包管理命 ...

  5. Windows 10 之 WSL 2

    Windows Subsystem for Linux(WSL)无疑大大提升了Windows下程序开发的体验. WSL 2向开发者提供的完整的系统调用兼容,使得许多无法在WSL 1中安装的应用,如Do ...

  6. 修改statefulset 有些不允许直接修改

    1.比如修改一个storageClassName: hostpath   会报错 意思是除了 'replicas', 'template',  'updateStrategy'   其他部分都是不可以 ...

  7. nodejs安装 Later version of Node.js is already installed. Setup will now exit 及 node与npm版本不符

    暴力删除nodejs导致无法重新安装  Later version of Node.js is already installed. Setup will now exit 1.电脑全局搜索nodej ...

  8. 308 day06_线程、同步

      day06 [线程.同步] 主要内容 线程 同步 线程状态 教学目标 能够描述Java中多线程运行原理 能够使用继承类的方式创建多线程 能够使用实现接口的方式创建多线程 能够说出实现接口方式的好处 ...

  9. 【OI】C++STL 不定长数组 vector

    Vector 本来是向量的意思,只不过在用法上类似于一个不限长度的数组. 定义语法:vector<数据类型> 名称; 一.头文件:<vector> (bits/stdc++请忽 ...

  10. [原创]OpenEuler20.03安装配置PostgreSQL13.4详细图文版

    OpenEuler安装配置PostgreSQL 编写时间:2021年9月18日 作者:liupp 邮箱:liupp@88.com 序号 更新内容 更新日期 更新人 1 完成第一至三章内容编辑: 202 ...