TCP 半关闭是什么?shutdown 和 close 有什么区别?
简化版
TCP 半关闭指连接的一方关闭发送方向但仍保留接收方向,典型表现是发送 FIN 后还能继续收对端数据。shutdown 可以只关闭读或写方向,close 通常释放整个 socket 引用并在合适时关闭连接。
详细版
TCP 是全双工协议,读写两个方向可以独立结束。半关闭常见流程是:一方调用 shutdown(SHUT_WR),内核发送 FIN,表示本端不会再发送数据;对端读到 EOF,但仍可以继续写回剩余数据。本端在发送 FIN 后仍可读取对端数据。
close 更偏资源释放:应用不再使用这个 socket,内核根据引用计数、发送缓冲区、linger 配置等决定如何关闭。多个进程或线程共享 fd 时,一次 close 未必立刻发 FIN;而 shutdown 会直接作用于连接方向。
面试重点:FIN 只关闭一个方向,RST 才是异常重置;半关闭常用于“请求发完后继续等响应”的协议场景。
完整版教学
一、半关闭来自 TCP 的全双工语义
TCP 连接包含两个独立方向的数据流:A 到 B,B 到 A。A 发送 FIN,只表示 A 到 B 这个方向没有更多数据,并不影响 B 到 A 的方向继续发送。所以 TCP 可以半关闭。
A -- FIN --> B A 不再发送
A <-- data -- B B 仍可发送
A <-- FIN --- B B 也发送完
A -- ACK --> B 完整关闭
如果把 FIN 理解成“整个连接断开”,就很难解释为什么四次挥手里双方都要各自发 FIN。更准确的理解是:每个 FIN 关闭一个发送方向。
记忆钩子:TCP 连接像双向车道,FIN 只是关闭自己这条出方向车道,对面车道还能继续开。
二、shutdown 能精确关闭读写方向
shutdown 的特点是作用在连接方向上,而不只是释放文件描述符。常见选项可以关闭读方向、写方向或双方向。关闭写方向时,内核会发送 FIN;关闭读方向时,本端不再接收后续数据。
shutdown(SHUT_WR): 不再写,发送 FIN,还可以 read
shutdown(SHUT_RD): 不再读,后续接收受影响
shutdown(SHUT_RDWR): 读写都关闭
举个例子:客户端上传一段请求体后调用 shutdown(SHUT_WR),告诉服务端“请求已经发完”。服务端读到 EOF 后开始计算结果,并继续把响应写给客户端。客户端虽然不能再写,但还能读响应。
三、close 更偏资源生命周期
close 表示应用释放 socket fd。若该 socket 没有其他引用,内核通常会关闭连接;如果还有其他 fd 引用同一个 socket,连接可能不会立刻关闭。这在 fork、多线程或 fd 复制场景里尤其容易踩坑。
| 操作 | 关注点 | 是否可半关闭 | 常见语义 |
|---|---|---|---|
shutdown | 连接读写方向 | 可以 | 通知对端某方向结束 |
close | 本进程 fd 资源 | 不直接表达方向 | 释放引用,可能触发关闭 |
RST | 异常复位 | 不保留 | 连接作废 |
这也是为什么网络编程面试会追问二者区别:shutdown 是协议语义更明确的方向关闭,close 是操作系统资源释放动作。
四、半关闭和四次挥手的关系
四次挥手本质上就是两个方向分别半关闭。A 发 FIN,B 回 ACK,此时 A 到 B 方向关闭;B 处理完剩余数据后发 FIN,A 回 ACK,B 到 A 方向也关闭。
方向1:A -> B
A 发 FIN,B 回 ACK,方向1关闭
方向2:B -> A
B 发 FIN,A 回 ACK,方向2关闭
如果 B 收到 A 的 FIN 后立刻也没有数据要发,FIN 和 ACK 有机会合并,看起来像三次挥手。但语义上仍是两个方向分别结束。
五、半关闭适合什么业务场景
半关闭适合“请求已经发送完,但响应还要继续读”的场景。比如某些自定义协议、代理转发、命令式协议,都可能需要明确告诉对端请求体结束,而不是直接断开连接。
| 场景 | 半关闭价值 |
|---|---|
| 上传后等待处理结果 | 客户端停止写,但继续读结果 |
| 代理转发 EOF | 把上游结束信号传给下游 |
| 长连接协议结束一段流 | 保留反向通知能力 |
| 测试 TCP 行为 | 验证 FIN、EOF、半关闭 |
不过 HTTP/1.1 常用 Content-Length、chunked 等应用层边界表达请求结束,不一定依赖 TCP 半关闭。
六、半关闭容易引出的线上问题
如果应用没有理解半关闭,可能在读到 EOF 后误以为对端异常,或者在已经收到 FIN 后仍尝试大量写入,导致错误处理混乱。另一个常见问题是只 close 了某个 fd 引用,但其他引用还在,FIN 没有发出去。
排查时可以看报文和状态:
收到 FIN 后:
本端回 ACK -> 进入 CLOSE_WAIT
应用继续读会得到 EOF
应用 close/shutdown 写方向 -> 发送 FIN -> LAST_ACK
如果 CLOSE_WAIT 长期堆积,说明内核已经收到对端 FIN,但应用迟迟没有释放或关闭写方向。
七、常见误区与追问
- 误区:FIN 表示整条连接立即不可用。 FIN 只表示发送方不再发送,反方向仍可继续传数据。
- 误区:shutdown 和 close 完全一样。 shutdown 作用于连接方向,close 释放 fd 引用,语义和时机不同。
- 误区:半关闭只存在于理论里。 文件传输、代理、自定义协议和 EOF 传递都可能用到半关闭。
- 追问:为什么四次挥手能体现半关闭? 因为两个方向要分别发送 FIN,各自结束自己的发送方向。
- 追问:close 后一定马上发 FIN 吗? 不一定,若还有 fd 引用或特殊 linger 行为,表现可能不同。
- 追问:CLOSE_WAIT 和半关闭有什么关系? CLOSE_WAIT 表示对端已半关闭发送方向,本端应用还没完成关闭。
八、加强记忆
半关闭抓住“两个方向独立”这件事:shutdown(SHUT_WR) 是我不写了但还能读,close 是我释放这个 socket 资源,FIN 关闭一个方向,RST 才是异常复位。这样再解释四次挥手、CLOSE_WAIT 和 EOF,逻辑就会很顺。