protocol

Socket 工作原理

·10 分钟阅读·3716 字

Socket 定义、TCP 三次握手、BIO 模式下 Socket 完整工作流程(服务端/客户端)

📋 目录

Socket 工作原理

Socket 基础概念、TCP 三次握手、传统 BIO 模式下 Socket 的完整工作流程。


结合操作系统原理 + Java 代码示例 + 通俗比喻,一步步讲清 Socket 完整工作流程,区分服务端、客户端,同时结合之前的 BIO/NIO 帮你串联理解。

一、先搞懂:Socket 到底是什么

Socket 中文叫套接字,是操作系统内核提供的网络编程接口,是应用程序和 TCP/IP 协议栈之间的桥梁。

  • 网络通信本质:两台主机通过 IP地址 + 端口号 定位进程,Socket 就是这个「通信端点」。
  • 一组完整 TCP 连接 = 客户端Socket ↔ 服务端Socket 成对出现。

比喻:
IP 地址 = 小区地址,端口 = 住户门牌号,Socket = 入户电话线。
两台机器通信,就是两根电话线对接、传数据。


二、核心前置:TCP 三次握手(连接建立)

Socket 基于 TCP 时,底层会自动完成三次握手,这是通信的前提:

  1. 客户端 → 服务端:我要建立连接(SYN)
  2. 服务端 → 客户端:收到请求,我也准备好(SYN+ACK)
  3. 客户端 → 服务端:确认收到,连接正式建立(ACK)
    握手完成,TCP 通道打通,双方可以收发数据。

连接断开则是四次挥手,Socket 关闭时内核自动处理。


三、传统 BIO 下 Socket 完整工作流程(最经典、最好理解)

服务端客户端 两条链路,按执行顺序拆解,附简易 Java 伪代码。

(一)服务端流程(被动等待连接)

步骤1:创建 ServerSocket(监听端口)

  1. 应用程序调用系统接口,在内核中创建一个监听型 Socket
  2. 绑定本机 IP + 端口(例如 0.0.0.0:9092);
  3. 开始监听,等待客户端接入。

Java 代码:

// 绑定 9092 端口,开始监听
ServerSocket serverSocket = new ServerSocket(9092);

步骤2:阻塞等待客户端连接 accept()

调用 serverSocket.accept()线程进入阻塞状态

  • 此时线程卡住,什么都不做,一直等到有客户端发起连接;
  • 客户端连上后,内核完成 TCP 三次握手,然后返回一个新的普通 Socket 对象

重点:
ServerSocket 只负责「监听、接连接」,不负责收发数据
每一个客户端连接,都会生成一个全新独立 Socket

// 阻塞等待连接,有客户端接入才会往下执行
Socket clientSocket = serverSocket.accept(); 

步骤3:通过 Socket 读写数据

拿到 clientSocket 后,通过输入流、输出流和客户端通信:

  • getInputStream():读客户端发来的数据(阻塞)
  • getOutputStream():向客户端写回数据

只要对方没发数据,read() 方法就会持续阻塞线程

// 读数据(阻塞)
InputStream in = clientSocket.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf); 

// 写数据
OutputStream out = clientSocket.getOutputStream();
out.write("hello client".getBytes());

步骤4:关闭资源

通信结束,依次关闭流、Socket,释放操作系统端口和文件句柄。

in.close();
out.close();
clientSocket.close();
serverSocket.close();

BIO 服务端整体运行逻辑

创建ServerSocket → 绑定端口 → accept()阻塞等连接

客户端接入 → 生成专属Socket → 新开线程处理读写(一连接一线程)

读写阻塞 → 通信完成 → 关闭Socket

(二)客户端流程(主动发起连接)

客户端不需要 ServerSocket,直接创建普通 Socket 主动去连服务端。

步骤1:创建 Socket,发起连接

指定服务端IP + 端口,内核自动发起 TCP 三次握手,建立连接。

// 连接 127.0.0.1:9092 服务端
Socket socket = new Socket("127.0.0.1", 9092);

步骤2:读写数据

和服务端对称,用输入输出流收发数据:

// 发数据给服务端
OutputStream out = socket.getOutputStream();
out.write("hello server".getBytes());

// 读取服务端返回数据(阻塞)
InputStream in = socket.getInputStream();
byte[] buf = new byte[1024];
in.read(buf);

步骤3:关闭连接

in.close();
out.close();
socket.close();

四、关键特性:为什么 BIO 的 Socket 会阻塞?

这是理解瓶颈的核心:

  1. accept() 阻塞:没有客户端连接,线程一直等;
  2. read() 阻塞:连接已建立,但对方没发数据,线程一直等。

结合之前饭店比喻:

  • accept() = 前台等客人上门;
  • read() = 服务员等客人说话;
    全程卡死,线程无法做其他事。

这就是 BIO 无法支撑高并发的根源。


五、NIO 中 Socket 的变化(过渡到 Channel)

前面说过,Java NIO 不再用传统 Socket/ServerSocket,而是改用:

  • ServerSocketChannel(替代 ServerSocket)
  • SocketChannel(替代 Socket)

底层依然是操作系统 Socket,只是 Java 层做了改造:

  1. 可以设置 非阻塞模式
  • accept() 没有连接 → 直接返回,不卡线程;
  • read() 没有数据 → 直接返回,不卡线程。
  1. 配合 Selector 多路复用:一个线程轮询上千个 Channel(底层Socket)。

工作简化流程:

ServerSocketChannel(非阻塞) → 注册到Selector
Selector轮询事件 → 有新连接 → 生成SocketChannel
SocketChannel注册到Selector → 轮询读写事件 → 非阻塞读写

本质没变:底层还是操作系统 Socket,只是调用方式、阻塞行为、线程模型改了。


六、Socket 必懂的补充知识点

1. 端口占用问题

  • 一个端口同一时间只能被一个进程监听
  • Socket 正常关闭后,内核会进入 TIME_WAIT 状态(默认几十秒),端口不会立刻释放,短时间内无法重启服务。

2. 文件句柄限制

操作系统中,每一个 Socket 连接都会占用一个文件句柄

  • 连接数越多,占用句柄越多;
  • 系统有最大句柄上限,连接超限会直接报错:Too many open files
    这也是高并发服务必须优化系统参数的原因。

3. 全双工通信

Socket 建立的 TCP 连接是全双工
双方可以同时收发数据,互不干扰(输入流、输出流独立工作)。

4. 两种关闭方式

  1. 主动 close():正常断开连接,内核走四次挥手;
  2. 进程意外退出/断网:连接异常断开,内核回收资源。

七、结合 Kafka 串联总结

  1. Kafka 对外监听 9092 端口,底层本质也是操作系统 Socket;
  2. 但它不用 Java BIO 的 Socket,而是用 NIO 的 SocketChannel
  3. Acceptor 线程负责接收新连接(对应 ServerSocketChannel.accept());
  4. Processor 线程通过 Selector 管理大量 SocketChannel,非阻塞读写;
  5. 最终所有网络通信,落脚点都是操作系统内核的 Socket 套接字。

八、一句话极简总结

  1. Socket 是操作系统提供的网络通信端点,基于 IP+端口定位进程
  2. TCP 通信需要客户端Socket + 服务端Socket 成对,依靠三次握手建立连接;
  3. 传统 BIO 中 Socket 默认阻塞,一连接一线程,并发能力弱;
  4. NIO 的 Channel 是 Java 对底层 Socket 的新封装,支持非阻塞、多路复用,适配高并发;
  5. 所有 Java 网络组件(Kafka、Netty),底层都离不开操作系统 Socket。

关联文档

  • TCP 粘包拆包 — TCP 粘包/拆包问题
  • 20-areas/cloud-native/programming/java/Java IO 模型 — BIO/NIO 模型
  • HTTP 协议 — 应用层协议