文章目录
- shutdown
- 意义与应用
-
-
- 一、实现"半关闭",支持双向通信的灵活拆分
- 二、明确告知对方"通信状态变更"(协议层面的信号)
-
- [对比:`shutdown(SHUT_WR)` vs 直接断网](#对比:
shutdown(SHUT_WR)vs 直接断网)
- [对比:`shutdown(SHUT_WR)` vs 直接断网](#对比:
- 三、避免资源泄露,规范套接字生命周期
- [四、区别于 `close()`,解决"共享 FD"场景的问题](#四、区别于
close(),解决“共享 FD”场景的问题) - [五、补充:UDP 套接字中 `shutdown()` 的意义(有限但有用)](#五、补充:UDP 套接字中
shutdown()的意义(有限但有用)) - [总结:`shutdown()` 的核心价值](#总结:
shutdown()的核心价值)
-
shutdown
cpp
/* Shut down all or part of the connection open on socket FD.
HOW determines what to shut down:
SHUT_RD = No more receptions;
SHUT_WR = No more transmissions;
SHUT_RDWR = No more receptions or transmissions.
Returns 0 on success, -1 for errors. */
extern int shutdown (int __fd, int __how) __THROW;
用于关闭套接字连接的部分或全部功能 (区别于 close() 直接关闭整个文件描述符)。以下是详细解析:
一、函数核心作用
关闭套接字(socket)的读、写或读写双向通信能力,无论套接字是否处于连接状态(TCP/UDP 均可用,但 UDP 为无连接协议,效果有限)。
- 与
close()的区别:close()会释放文件描述符本身,而shutdown()仅禁用通信功能(文件描述符仍可复用,除非后续调用close()); - 对 TCP 而言:
shutdown()会触发 TCP 协议层面的挥手(如SHUT_WR会发送 FIN 包),而close()仅在最后一个引用被释放时才触发挥手。
二、参数说明
| 参数名 | 类型 | 含义 | 合法取值 |
|---|---|---|---|
__fd |
int |
套接字文件描述符(由 socket() 函数创建返回) |
有效的套接字 FD(非负整数) |
__how |
int |
关闭方式(指定禁用的通信方向) | 3 个预定义常量(需包含 <sys/socket.h>) |
__how 的合法取值(核心)
| 常量 | 含义 | 具体行为 |
|---|---|---|
SHUT_RD |
关闭"读"功能 | 不再接收数据(即使缓冲区有未读数据,也会丢弃);后续调用 read() 会返回 0(表示 EOF)或 -1(错误) |
SHUT_WR |
关闭"写"功能 | 不再发送数据(已缓冲的数据会尽力发送);后续调用 write() 会返回 -1(错误);对 TCP 而言,会发送 FIN 包(告知对方"我方不再发送数据") |
SHUT_RDWR |
关闭"读写"双向功能 | 等价于先 SHUT_RD 再 SHUT_WR,完全禁用套接字的收发能力 |
三、返回值
- 成功 :返回
0; - 失败 :返回
-1,并设置全局变量errno指示错误原因(需包含<errno.h>查看)。
常见错误场景(errno 取值)
errno |
含义 |
|---|---|
EBADF |
__fd 不是有效的文件描述符 |
ENOTSOCK |
__fd 是文件描述符,但不是套接字(如普通文件、管道 FD) |
ENOTCONN |
套接字未处于连接状态(部分系统对 UDP 或未连接的 TCP 套接字调用 shutdown() 会触发) |
EINVAL |
__how 取值非法(不是 SHUT_RD/SHUT_WR/SHUT_RDWR) |
四、使用场景与示例
1. 典型场景
- 半关闭 TCP 连接 :如客户端发送完请求后,用
SHUT_WR关闭写端,告知服务器"请求已发送完毕",服务器收到 FIN 后可继续发送响应,客户端仍能读取响应(直到服务器关闭写端); - 强制关闭双向通信 :用
SHUT_RDWR快速禁用套接字收发,避免后续误操作发送/接收数据。
2. 代码示例(TCP 客户端半关闭)
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
int main() {
// 1. 创建 TCP 套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 2. 连接服务器(假设服务器地址为 127.0.0.1:8080)
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
if (inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr) <= 0) {
perror("invalid server address");
close(sockfd);
exit(EXIT_FAILURE);
}
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("connect failed");
close(sockfd);
exit(EXIT_FAILURE);
}
// 3. 发送数据给服务器
const char* send_buf = "Hello, server!";
ssize_t sent = write(sockfd, send_buf, strlen(send_buf));
if (sent == -1) {
perror("write failed");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("Sent %zd bytes: %s\n", sent, send_buf);
// 4. 关闭写端(告知服务器:我方不再发送数据)
if (shutdown(sockfd, SHUT_WR) == -1) {
perror("shutdown SHUT_WR failed");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("Shut down write end\n");
// 5. 继续读取服务器的响应(读端仍可用)
char recv_buf[1024] = {0};
ssize_t recv_len = read(sockfd, recv_buf, sizeof(recv_buf)-1);
if (recv_len == -1) {
perror("read failed");
} else if (recv_len == 0) {
printf("Server closed connection\n");
} else {
printf("Received %zd bytes: %s\n", recv_len, recv_buf);
}
// 6. 关闭套接字(释放文件描述符)
close(sockfd);
return 0;
}
五、关键注意事项
- UDP 套接字的特殊性 :UDP 是无连接协议,
shutdown(SHUT_RD)会禁止后续recv(),shutdown(SHUT_WR)会禁止后续send(),但不会触发任何协议层面的交互(无 FIN 包); shutdown()后仍需close():shutdown()仅禁用通信功能,文件描述符仍占用资源,需调用close()释放;- 多进程/多线程共享 FD :若多个进程/线程共享同一个套接字 FD,
shutdown()会影响所有共享者(禁用整个套接字的对应方向),而close()仅减少 FD 引用计数,直到计数为 0 才释放。
六、头文件依赖
使用时需包含以下头文件(POSIX 标准):
c
#include <sys/socket.h> // 定义 shutdown()、SHUT_* 常量
#include <errno.h> // 错误码定义(可选,用于错误排查)
如果是 Windows 系统(非 POSIX 环境),函数声明略有差异(需包含 winsock2.h),但核心逻辑一致。
意义与应用
shutdown() 的核心意义是在"协议层面"精细控制套接字的通信能力 ,解决 close() 无法实现的"部分关闭"或"明确告知对方通信状态"的需求,尤其对面向连接的 TCP 协议至关重要。其意义可从以下 4 个核心维度理解:
一、实现"半关闭",支持双向通信的灵活拆分
TCP 连接是全双工 (双方可同时收发数据),而 close() 是"全关闭"(直接切断所有通信),shutdown() 则允许只关闭一个方向的通信(读或写),保留另一个方向可用------这就是"半关闭"(Half-close),是其最核心的意义。
典型场景:客户端"发送完请求后等待响应"
比如 HTTP 1.0 客户端:
- 客户端连接服务器后,发送请求数据(
POST /login ...); - 发送完毕后,用
shutdown(sockfd, SHUT_WR)关闭"写端"------告知服务器"我的请求已发完,不会再发数据了"; - 服务器收到 FIN 包后,知道无需再等待客户端的后续数据,可直接处理请求并发送响应;
- 客户端的"读端"仍处于打开状态,可正常读取服务器的响应;
- 客户端读完响应后,再调用
close()释放套接字。
如果不用 shutdown(),仅用 close():
- 客户端发送完请求后
close(),会直接关闭整个套接字,服务器可能还没来得及发送响应,连接就被切断; - 或者客户端需要一直保持写端打开,服务器无法判断"请求是否已完整发送",可能会一直等待(导致超时)。
二、明确告知对方"通信状态变更"(协议层面的信号)
shutdown() 会触发 TCP 协议的标准化交互(发送 FIN 包),对方通过 read() 返回 0(表示 EOF)就能明确感知到"对方关闭了写端"------这是一种可靠的"状态通知",而不是单纯的"断连"。
对比:shutdown(SHUT_WR) vs 直接断网
- 若客户端用
shutdown(SHUT_WR):服务器read()会返回 0,明确知道"客户端主动停止发送",可安全处理后续逻辑; - 若客户端直接断网(无 FIN 包):服务器
read()会一直阻塞,直到超时(不确定是网络故障还是客户端未发完数据)。
这种"明确通知"能避免双方的无效等待,让协议交互更可靠。
三、避免资源泄露,规范套接字生命周期
shutdown() 虽然不释放文件描述符(仍需 close()),但能提前禁用无用的通信方向,避免后续误操作(比如其他线程误写数据到已完成发送的套接字),同时让内核提前清理对应方向的缓冲区资源。
举例:多线程共享套接字
如果一个线程负责"发送数据",另一个线程负责"读取数据":
- 当发送线程完成任务后,用
shutdown(sockfd, SHUT_WR)关闭写端------即使发送线程未退出,其他线程也无法再通过该套接字发送数据(write()会返回错误),避免误发垃圾数据; - 读取线程仍可继续读取,直到服务器关闭写端,再调用
close()释放 FD。
四、区别于 close(),解决"共享 FD"场景的问题
在多进程/多线程程序中,套接字 FD 可能被多个进程/线程共享(比如 fork() 后子进程继承父进程的 FD):
close()的本质是"减少 FD 引用计数",只有当引用计数为 0 时,才会真正关闭套接字并发送 FIN 包;shutdown()的作用是"禁用套接字的通信功能",无论 FD 被多少进程共享,都会立即生效(所有共享者都无法再使用被关闭的方向)。
场景:父进程监听,子进程处理连接
- 父进程
socket()创建监听 FD,fork()多个子进程; - 子进程 accept() 得到客户端连接 FD 后,处理完请求,用
shutdown(connfd, SHUT_RDWR)禁用该连接的收发功能; - 即使父进程仍持有该 connfd(引用计数未到 0),也无法再通过该 FD 与客户端通信,避免父进程误操作;
- 最后子进程
close(connfd)减少引用计数,父进程后续也close()后,FD 被释放。
如果只用 close():子进程 close() 后,父进程的 FD 仍有效,可能误写数据到已处理完的客户端连接。
五、补充:UDP 套接字中 shutdown() 的意义(有限但有用)
UDP 是无连接协议,没有 TCP 的 FIN 包交互,shutdown() 不会触发协议层面的通知,但仍有实际作用:
shutdown(sockfd, SHUT_RD):禁用后续recv()操作,即使缓冲区有数据,也会被丢弃,后续read()会返回 -1(EINVAL);shutdown(sockfd, SHUT_WR):禁用后续send()操作,后续write()会返回 -1(EINVAL);- 意义:避免在 UDP 套接字的"生命周期后期"误收发数据(比如已决定停止通信,但代码逻辑可能误调用
send()),起到"逻辑层面的禁用保护"。
总结:shutdown() 的核心价值
- 提供 半关闭能力,适配全双工通信的灵活需求(如"发完等响应");
- 发送 协议级别的明确通知(FIN 包),让对方可靠感知通信状态;
- 规范套接字使用,避免误操作和资源泄露;
- 解决多进程/多线程共享 FD 时的"功能禁用"问题。
简单说:close() 是"物理层面的断连+资源释放",shutdown() 是"逻辑层面的功能控制+协议通知"------前者是"终结",后者是"精细化管理"。