【Linux网络】shutdown()与close()的区别

文章目录

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_RDSHUT_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;
}

五、关键注意事项

  1. UDP 套接字的特殊性 :UDP 是无连接协议,shutdown(SHUT_RD) 会禁止后续 recv()shutdown(SHUT_WR) 会禁止后续 send(),但不会触发任何协议层面的交互(无 FIN 包);
  2. shutdown() 后仍需 close()shutdown() 仅禁用通信功能,文件描述符仍占用资源,需调用 close() 释放;
  3. 多进程/多线程共享 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 客户端:

  1. 客户端连接服务器后,发送请求数据(POST /login ...);
  2. 发送完毕后,用 shutdown(sockfd, SHUT_WR) 关闭"写端"------告知服务器"我的请求已发完,不会再发数据了";
  3. 服务器收到 FIN 包后,知道无需再等待客户端的后续数据,可直接处理请求并发送响应;
  4. 客户端的"读端"仍处于打开状态,可正常读取服务器的响应;
  5. 客户端读完响应后,再调用 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 被多少进程共享,都会立即生效(所有共享者都无法再使用被关闭的方向)。
场景:父进程监听,子进程处理连接
  1. 父进程 socket() 创建监听 FD,fork() 多个子进程;
  2. 子进程 accept() 得到客户端连接 FD 后,处理完请求,用 shutdown(connfd, SHUT_RDWR) 禁用该连接的收发功能;
  3. 即使父进程仍持有该 connfd(引用计数未到 0),也无法再通过该 FD 与客户端通信,避免父进程误操作;
  4. 最后子进程 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() 的核心价值

  1. 提供 半关闭能力,适配全双工通信的灵活需求(如"发完等响应");
  2. 发送 协议级别的明确通知(FIN 包),让对方可靠感知通信状态;
  3. 规范套接字使用,避免误操作和资源泄露;
  4. 解决多进程/多线程共享 FD 时的"功能禁用"问题。

简单说:close() 是"物理层面的断连+资源释放",shutdown() 是"逻辑层面的功能控制+协议通知"------前者是"终结",后者是"精细化管理"。

相关推荐
A小辣椒1 天前
TShark:Wireshark CLI 功能
linux
A小辣椒1 天前
TShark:基础知识
linux
AlfredZhao1 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5203 天前
Linux 11 动态监控指令top
linux
网络研究院3 天前
2026年网络安全
网络·安全·法律·法规·趋势·发展