1. 概述

Socket 是用于进程间通信的端点(endpoint),在现代系统中被广泛使用。常见的操作包括创建、绑定、发送、接收、关闭等。其中,shutdownclose 是两个用于终止通信的操作,但它们的行为和用途有明显区别。

本文将从原理和使用方式两个层面,详细讲解 shutdownclose 的区别,帮助你在实际开发中避免踩坑。


2. Socket 简要回顾

Socket 是进程间通信的核心机制,尤其在 TCP/IP 协议栈中扮演重要角色。我们常说的网络通信,其实大多数时候都是基于 socket 实现的。

根据通信方式的不同,socket 通常分为以下三类:

  • Datagram Socket(数据报套接字):使用 UDP 协议进行通信,无连接,不保证消息顺序和可靠性。
  • Stream Socket(流式套接字):使用 TCP 协议,面向连接,提供有序、可靠的数据传输。
  • Raw Socket(原始套接字):直接操作 IP 数据包,常用于网络监控或自定义协议开发。

Socket 位于 TCP/IP 协议的应用层,在 OSI 模型中则属于会话层(第 5 层)。


3. Socket 的运行机制

创建一个 socket,本质上是在操作系统中申请一个文件描述符(file descriptor),这个描述符用于读写数据,实现进程间的双向通信。

  • 读操作:接收来自对端的数据包。
  • 写操作:向对端发送数据包。

下图展示了 socket 通信的基本流程:

SocketStd500

当通信完成时,我们通常需要调用 shutdownclose 来结束通信。但这两个操作的行为截然不同,下面将详细介绍。


4. shutdown 方法详解

shutdown 并不会销毁 socket,而是有选择性地禁用其通信功能。根据传入的参数不同,可以实现以下三种行为:

✅ SHUT_RD(禁用读操作)

  • 阻止 socket 接收新数据。
  • 已缓存的数据仍可被读取。
  • 后续读取操作将返回 0(即 EOF)。
  • 不影响写操作

✅ SHUT_WR(禁用写操作)

  • 阻止 socket 发送新数据。
  • 已排队的数据仍会被发送完成。
  • 向连接的对端发送 FIN 标志(TCP 中)。
  • 不影响读操作

✅ SHUT_RDWR(同时禁用读写)

  • 等价于同时调用 SHUT_RD 和 SHUT_WR。
  • 通信完全阻断。
  • 但 socket 本身仍然存在,并未被销毁。

下图总结了不同模式下 socket 的行为变化:

SocketShutdown500


5. close 方法详解

close 是一个更彻底的操作:

  • 销毁 socket 的文件描述符
  • 断开所有连接(如 TCP 连接)
  • 释放系统资源

调用 close 后,任何对该 socket 的读写操作都会触发异常。

下图展示了 close 操作后的状态变化:

SocketClose500

⚠️ 注意:即使调用了 close,TCP 协议栈仍可能在后台维持一段时间的连接状态(如 FIN-WAIT、TIME-WAIT),直到资源真正释放。


6. shutdown 与 close 的区别总结

特性 shutdown close
是否销毁 socket ❌ 否 ✅ 是
是否释放文件描述符 ❌ 否 ✅ 是
是否保留连接状态 ✅ 是 ❌ 否
可控性 ✅ 高(可选读/写/读写) ❌ 低(全关闭)
多次调用 ✅ 可多次调用 ❌ 通常只调用一次

7. 实际使用建议

  • ✅ 在需要单向关闭通信(如发送完数据后关闭写操作)时,使用 shutdown(SHUT_WR)
  • ✅ 当通信完全结束,且不再需要 socket 时,使用 close()
  • ✅ 在多线程或多进程环境中,确保所有引用 socket 的线程都已完成操作后再调用 close,否则可能导致资源泄露或异常。

8. 示例代码(Java)

import java.io.*;
import java.net.*;

public class SocketExample {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        Socket socket = new Socket();
        socket.connect(new InetSocketAddress("localhost", 8080));

        // 模拟 shutdown 写操作
        socket.shutdownOutput(); // 等价于 shutdown(SHUT_WR)

        // 模拟 close 操作
        socket.close();
    }
}

9. 小结

  • shutdown 是“优雅关闭”的一种方式,允许你有选择地关闭读或写通道。
  • close 是“彻底销毁”,释放 socket 资源,不可再操作。
  • 在实际开发中,应根据业务需求选择合适的操作,避免资源泄露或连接异常。

理解 shutdownclose 的区别,有助于编写更健壮、高效的网络通信程序。


原始标题:Sockets: Close vs. Shutdown