TCP连接断开:进程终止、主机断电与网络断开

一、程序运行过程中,一方突然终止进程会怎么样

TCP连接建立以后,客户端和服务端都需要依靠操作系统维护socket。

我们之前知道,socket本质上也属于文件:

复制代码
进程
  ↓
文件描述符表
  ↓
sockfd
  ↓
struct file
  ↓
struct socket
  ↓
struct sock
  ↓
TCP连接

因此,当程序结束时,TCP连接最终也会随着socket文件描述符的关闭而被处理。

1. 进程正常退出

例如客户端正在运行:

复制代码
客户端 A                                      服务端 B
   │                                             │
   │──────────── TCP数据 ───────────────────────►│
   │                                             │

此时客户端调用:

复制代码
exit(0);

或者正常从 main() 返回。

进程退出时,操作系统会负责清理这个进程打开的文件描述符。

也就是:

复制代码
进程退出
   ↓
关闭进程打开的文件描述符
   ↓
关闭socket
   ↓
TCP进行正常关闭
   ↓
发送FIN

所以:

程序退出并不会直接导致TCP连接凭空消失,本质上还是操作系统在清理进程资源时关闭了socket。

最终TCP还是会按照正常的连接关闭流程处理。


2. 进程被强制终止呢?

这里需要区分一个非常容易混淆的地方。

例如:

复制代码
kill -9 PID

这属于强制终止进程。

虽然程序本身没有机会执行:

复制代码
close(sockfd);

但是:

kill -9****杀掉的是用户态进程,并不代表操作系统内核也没了。

进程被终止以后,内核依然会负责清理这个进程的资源,其中就包括文件描述符。

所以过程仍然是:

复制代码
kill -9 PID
      ↓
进程被内核终止
      ↓
内核清理进程资源
      ↓
关闭文件描述符
      ↓
关闭socket
      ↓
TCP进行正常关闭
      ↓
发送FIN

因此,从TCP连接的角度来看,进程被强制终止和正常退出通常没有本质区别。

区别主要在于:

复制代码
正常退出:
程序自己执行退出流程
      ↓
正常结束

kill -9:
程序来不及自己清理
      ↓
内核强制终止并清理资源

但是最终操作系统仍然会负责释放这个进程持有的socket资源。

所以可以记住:

进程终止 ≠ 主机断电。只要操作系统内核还活着,进程退出以后,内核仍然可以清理socket。


3. 为什么进程退出以后TCP连接会关闭

因为对于Linux来说:

复制代码
socket

本身也是一种文件。

程序中的:

复制代码
int sockfd;

只是文件描述符表中的一个下标。

所以:

复制代码
sockfd
   ↓
struct file
   ↓
socket

当进程退出时,操作系统会清理这个进程的文件描述符。

因此最终就是:

复制代码
进程退出
   ↓
文件描述符关闭
   ↓
socket关闭
   ↓
TCP连接关闭

这里要注意:

不是因为"进程没了,所以TCP立刻没了",而是因为进程退出时,操作系统会清理它打开的socket文件描述符。


4. 如果只是关闭某个socket呢?

其实不需要等整个进程退出。

例如:

复制代码
close(sockfd);

也会关闭这个socket对应的文件描述符。

过程就是:

复制代码
close(sockfd)
      ↓
关闭socket文件描述符
      ↓
TCP开始关闭连接

所以:

复制代码
close(sockfd)

和:

复制代码
进程退出

最终都可能导致socket被关闭。

区别只是:

复制代码
close()
→ 主动关闭某个socket

进程退出
→ 操作系统统一清理进程拥有的文件描述符

二、机器重启会怎么样?

机器重启需要单独看。

如果是正常重启,例如系统执行:

复制代码
reboot

那么操作系统在重启过程中仍然有机会进行资源清理。

从TCP连接的角度来看,它和进程终止的思路比较接近:

复制代码
正常重启
   ↓
操作系统仍然运行
   ↓
关闭相关资源
   ↓
关闭socket
   ↓
TCP正常关闭

所以:

正常重启和进程终止类似,本质上都是操作系统还活着,可以进行socket资源清理。

但是如果不是正常重启,而是直接:

复制代码
突然断电

那就完全不同了。

因为:

复制代码
正常重启:

操作系统还活着
      ↓
可以清理socket
      ↓
可以进行TCP关闭流程

而:

复制代码
突然掉电:

电源消失
   ↓
CPU停止运行
   ↓
操作系统停止运行
   ↓
没有机会清理socket
   ↓
没有机会发送FIN

所以这里真正需要区分的是:

复制代码
进程终止 / 正常重启
        VS
     突然掉电

而不是简单地认为"机器重启一定和掉电一样"。


三、一方突然关机或者网线断了怎么办

1. 突然关机和进程退出完全不同

假设客户端A正常退出:

复制代码
A
 ↓
进程退出
 ↓
操作系统还在
 ↓
关闭socket
 ↓
TCP发送FIN

但是如果客户端直接断电:

复制代码
A
X
突然断电

这时候操作系统都没了。

因此A根本没有机会执行:

复制代码
close(sockfd)

也没有机会发送:

复制代码
FIN

于是服务端B可能还认为:

复制代码
TCP连接 = ESTABLISHED

2. 网线突然断开也是类似的

例如:

复制代码
客户端 A                    服务端 B
    │                          │
    │────── TCP连接 ───────────│
    │                          │
    X
  网线断开

网络突然断开以后,A和B都不会自动收到一个:

复制代码
"网络已经断开"

的TCP报文。

所以TCP连接可能不会马上消失。

例如:

复制代码
网络断开
   ↓
双方暂时没有数据
   ↓
TCP没有新的通信
   ↓
连接可能暂时仍然保持ESTABLISHED

这也是为什么:

TCP处于ESTABLISHED状态,并不代表对方主机此刻一定还在线。


四、掉电或者断网以后,TCP怎么发现对方已经不在了?

1. 接收端主动写数据

假设A突然掉电了,但是B暂时不知道。

此时B可能仍然认为:

复制代码
TCP连接 = ESTABLISHED

如果B随后向A发送数据:

复制代码
发送端 B                                      接收端 A
   │                                             │
   │──────────── TCP数据 ───────────────────────►│
   │                                             X

但是A已经掉电,数据自然无法得到正常处理。

于是B可能经历:

复制代码
发送数据
   ↓
等待ACK
   ↓
没有收到ACK
   ↓
超时
   ↓
重新发送
   ↓
仍然没有响应
   ↓
最终判断连接异常
   ↓
释放连接

所以:

即使对方已经掉电,TCP连接也不会凭空瞬间消失,而是需要通过后续通信逐渐发现对方已经不可达。


2. TCP本身还有保活机制

如果双方长时间没有任何数据通信,上面的"发送数据以后发现没有ACK"可能一直不会发生。

因此TCP还提供了:

复制代码
TCP Keepalive

也就是TCP保活机制。

它的思路非常简单:

复制代码
长时间没有通信
      ↓
启动保活检测
      ↓
定期询问对方
      ↓
对方正常响应
      ↓
说明连接仍然存在

如果对方已经掉电:

复制代码
长时间没有通信
      ↓
启动保活检测
      ↓
发送探测
      ↓
对方没有响应
      ↓
继续探测
      ↓
仍然没有响应
      ↓
判断连接已经失效
      ↓
释放TCP连接

因此,即使应用层一直没有发送数据,TCP也可以通过保活机制检测长时间没有响应的连接。

需要注意的是,TCP保活并不是"断网瞬间就能发现",而是经过一定时间的探测和等待以后,才会判断连接失效。


五、应用层也可以自己检测连接是否还活着

除了TCP自己的机制之外,应用层协议也经常会设计自己的连接检测机制。

例如一个长时间保持的TCP连接:

复制代码
客户端 A                                      服务端 B
   │                                              │
   │──────────── 心跳/探测 ──────────────────────►│
   │                                              │
   │◄─────────── 心跳响应 ────────────────────────│
   │                                              │

如果客户端连续发送心跳:

复制代码
心跳
 ↓
没有响应
 ↓
再次发送
 ↓
还是没有响应
 ↓
认为对方已经失联

那么应用程序就可以主动关闭本地连接,并进行重新连接。

例如:

复制代码
TCP连接
   ↓
应用层定期发送心跳
   ↓
对方正常响应
   ↓
继续保持连接

如果发现:

复制代码
连续多个心跳都没有响应

应用层就可以认为:

复制代码
对方可能已经掉线

然后:

复制代码
关闭旧连接
   ↓
重新建立TCP连接

这也是很多长连接程序常见的做法。

例如即时通信、长连接服务等,都可能在应用层设计自己的心跳和重连机制。


六、把几种情况放在一起

现在可以把TCP连接异常分成几种情况来看:

复制代码
                         TCP连接
                            │
              ┌─────────────┴─────────────┐
              │                           │
              ▼                           ▼
          操作系统还活着              操作系统已经消失
              │                           │
      ┌───────┴────────┐            ┌─────┴─────┐
      │                │            │           │
      ▼                ▼            ▼           ▼
  进程终止          正常重启       突然掉电    网线断开
      │                │            │           │
      └───────┬────────┘            │           │
              ▼                     │           │
        内核清理socket              │           │
              │                     │           │
              ▼                     │           │
          发送FIN                    │           │
              │                     │           │
              ▼                     │           │
          正常关闭                   │           │
                                    ▼           ▼
                              无法发送FIN   无法发送FIN
                                    │           │
                                    └─────┬─────┘
                                          ▼
                                    对端暂时不知道
                                          │
                              ┌───────────┴───────────┐
                              │                       │
                              ▼                       ▼
                         后续发送数据              TCP保活
                              │                       │
                              ▼                       ▼
                         没有ACK响应             探测无响应
                              │                       │
                              └───────────┬───────────┘
                                          ▼
                                     判断连接失效
                                          │
                                          ▼
                                       释放连接

七、最终记住这几个区别

1. 进程正常退出

复制代码
进程退出
   ↓
内核清理文件描述符
   ↓
关闭socket
   ↓
TCP正常关闭
   ↓
发送FIN

2. 进程被强制终止

例如:

复制代码
kill -9 PID

虽然程序本身来不及调用:

复制代码
close(sockfd);

但是:

复制代码
进程被终止
   ↓
内核仍然存在
   ↓
内核清理文件描述符
   ↓
关闭socket
   ↓
TCP正常关闭

所以它和正常关闭在TCP连接释放这一点上通常没有本质区别。

3. 正常机器重启

复制代码
正常重启
   ↓
操作系统仍然运行
   ↓
清理相关资源
   ↓
关闭socket
   ↓
TCP进行正常关闭

4. 突然掉电

复制代码
突然掉电
   ↓
操作系统停止运行
   ↓
没有机会关闭socket
   ↓
没有FIN
   ↓
对端暂时不知道
   ↓
后续通信/保活探测
   ↓
发现没有响应
   ↓
判断连接失效

5. 网线突然断开

复制代码
网线断开
   ↓
无法正常通信
   ↓
不会立刻收到"连接断开"报文
   ↓
连接可能暂时仍然ESTABLISHED
   ↓
后续数据没有ACK
      或
   TCP保活没有响应
   ↓
最终判断连接失效

因此最核心的一句话就是:

只要操作系统内核还活着,即使进程被强制终止,内核也可以清理它的文件描述符并关闭socket;而机器突然掉电或者网络突然断开时,内核没有机会发送FIN,对端只能通过后续通信、TCP保活等机制逐渐发现连接已经失效。

相关推荐
acd120091 小时前
RPC接口超时配置明明没问题,线上却随机超时
网络·网络协议·rpc
发量惊人的中年网工1 小时前
服务器托管一个机柜一年多少钱?2026年9月最新机柜租用价格参考
运维·服务器·网络
星恒随风1 小时前
Linux进程基础(二):进程调度、上下文切换、O(1)调度器、环境变量与虚拟地址空间
linux·笔记·学习·状态模式
fundoit1 小时前
资源服务器如何对 JWT 进行验签
java·运维·服务器·spring boot·php·oauth2
云贝贝贝1 小时前
【无标题】TDSQL 分片键怎么选?选错等于数据倾斜加跨分片慢查询
运维·服务器·数据库·腾讯云
hzxpaipai1 小时前
AI把网站页面做出来了,谁来负责后台、服务器和正式上线?
运维·服务器·人工智能
对讲机数码科普1 小时前
黑龙江无线电对讲机专网通信系统的射频工程实战:从链路预算到天馈施工的完整设计方法
网络·射频工程
ClickHouseDB1 小时前
ClickHouse Terraform Provider 正式支持 ClickStack 资源管理
网络·数据库·python
砚凝霜1 小时前
【软考信息安全】第九章 虚拟专用网与IPSec/SSL安全协议技术原理
网络·网络协议·ssl