「Linux 网络编程」基于 TCP 的半关闭
1 基于 TCP 的半关闭
TCP 具有核心特性——全双工通信(即通信双方可同时双向传输数据)。半关闭是 TCP 为适配 单向数据传输完成后需保留反向通道 场景设计的机制,本质是 仅关闭连接的一个方向(发送或接收),保留另一个方向的通信能力,而非完全断开连接。
1.1 半关闭的核心逻辑
TCP 默认的 完全关闭(通过四次挥手完成)会断开双向通道,但实际场景中常需 单向传输结束后,反向仍需传数据。
例如,客户端向服务器发送完文件(客户端→服务器的发送已完成),但服务器需向客户端返回 文件接收成功/失败 的确认信息(服务器→客户端的接收需保留)。此时若直接完全关闭,确认信息将无法传输 —— 半关闭恰好解决了“单向收尾、反向保留”的需求。
1.2 半关闭的实现
TCP 的连接关闭依赖 FIN报文(Finish,标识 此方向无更多数据要发送),半关闭的本质是 通信一方主动发送FIN报文关闭发送端,但保留接收端,另一方通过 ACK 确认后,完成单方向的关闭;反向通道仍可正常传输数据,直到另一方也发送 FIN 报文,才完成全关闭。
结合 TCP 的 四次挥手 过程,半关闭对应前两次挥手的核心阶段,即 半关闭状态 = 第一次挥手 + 第二次挥手。
当主动关闭方调用 shutdown(SHUT_WR)
(半关闭核心系统调用函数) 时:
-
第一次挥手:发送 FIN 报文
表示"我没有数据要发送了",但还可以接收数据。主动关闭方进入
FIN_WAIT_1
状态。 -
第二次挥手:接收对方的 ACK 响应
对端确认收到 FIN,主动关闭方进入
FIN_WAIT_2
状态。此时连接处于半关闭状态。
在代码中的体现:
1 |
|
2 半关闭核心调用 shutdown
2.1 函数原型及参数说明
1 |
|
参数说明:
-
sockfd
- 要关闭的套接字文件描述符
- 必须是已连接的套接字
-
how
指定关闭的方式,有三种选择:
参数值 说明 SHUT_RD
(0)关闭读通道,不再接收数据 SHUT_WR
(1)关闭写通道,不再发送数据 SHUT_RDWR
(2)同时关闭读写通道
返回值:
- 成功:返回 0
- 失败:返回 -1,并设置 errno
2.2 功能详解
-
SHUT_RD
- 关闭读通道1
shutdown(sockfd, SHUT_RD);
效果:
- 套接字不能再接收数据
- 接收缓冲区中的现有数据会被丢弃
- 后续的
recv()
,read()
调用返回 0(EOF) - 对端如果发送数据,会收到 RST 复位包
-
SHUT_WR
- 关闭写通道(最常用)1
shutdown(sockfd, SHUT_WR);
效果:
- 套接字不能再发送数据
- 发送缓冲区中未发送的数据会继续发送
- 发送 FIN 包给对端,触发 TCP 四次挥手的前两次
- 进入半关闭状态,仍然可以接收数据
-
SHUT_RDWR
- 同时关闭读写1
shutdown(sockfd, SHUT_RDWR);
效果:
- 结合了
SHUT_RD
和SHUT_WR
的效果 - 不能发送也不能接收数据
- 但连接还没有完全关闭,需要调用
close()
- 结合了
2.3 与 close()
的关键区别
方面 | shutdown() |
close() |
---|---|---|
关闭粒度 | 可以 单独控制读/写方向 | 总是关闭整个套接字 |
连接状态 | 连接仍然存在,可以 半关闭 | 连接完全终止 |
多进程影响 | 影响所有进程中的该连接 | 只影响当前进程的文件描述符 |
引用计数 | 不减少文件描述符引用计数 | 减少引用计数,为 0 时真正关闭 |
TCP行为 | 发送 FIN 包,进入半关闭状态 | 发送 FIN 包,完全关闭连接 |
3 半关闭简单 TCP 本地通信示例
3.1 服务端代码
1 |
|
3.2 客户端代码
1 |
|
3.3 编译和运行测试
4 半关闭的典型适用场景
-
HTTP/1.0 的短连接
HTTP/1.0 默认使用“短连接”,客户端发送请求后,通过
Connection: close
头告知服务器“请求已发送完成”;服务器返回响应后,会主动发送FIN报文关闭 “服务器→客户端” 的发送端(半关闭),客户端接收响应后再发送 FIN,完成全关闭。 -
文件传输协议(如FTP的数据连接)
FTP的数据连接用于传输文件:客户端向服务器发送“下载请求”后,关闭自己的数据连接发送端(半关闭),仅保留接收端用于接收文件;服务器传输完文件后,发送FIN关闭自己的发送端,完成全关闭。
-
双向数据流的“单向收尾”
例如,即时通讯中,用户A向用户B发送“结束对话”的消息后,关闭自己的发送端(不再发消息),但保留接收端以接收用户B的最后回复;用户B回复后,再关闭自己的发送端,最终断开连接。