写点什么

4 个实验,彻底搞懂 TCP 连接的断开,实战 nginx 张宴

用户头像
极客good
关注
发布于: 刚刚

=============


我们来思考一个问题: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


【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


.


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 连接断开的?挥手?,在进程崩溃时,会由操作系统内核代劳

用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
4个实验,彻底搞懂TCP连接的断开,实战nginx张宴