【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() 是"逻辑层面的功能控制+协议通知"------前者是"终结",后者是"精细化管理"。

相关推荐
LCG元2 小时前
Linux 磁盘管理从入门到精通:LVM 扩容实战案例
linux
liu****2 小时前
12.线程(二)
linux·开发语言·c++·1024程序员节
咯哦哦哦哦2 小时前
vscode arm交叉编译 中 cmakeTools 编译器设置
linux·arm开发·vscode·编辑器
工具人55553 小时前
Linux 抓取 RAM Dump 完整指南
linux·运维·安全
不懂音乐的欣赏者4 小时前
Windows 下 ROS/ROS2 开发环境最优解:WSL 比直接安装、虚拟机、双系统更优雅!
linux·windows·ubuntu·ros·wsl·ros2·双系统
AuroraDPY5 小时前
计算机网络:基于TCP协议的自定义协议实现网络计算器功能
网络·tcp/ip·计算机网络
小狗爱吃黄桃罐头5 小时前
正点原子【第四期】Linux之驱动开发学习笔记-10.1 Linux 内核定时器实验
linux·驱动开发·学习
张人玉5 小时前
TCP 的三次握手和四次挥手
网络·tcp/ip·c#
Kang强6 小时前
tcpdump 抓到 icmp 包,但是抓不到 tcp 包??
linux