Socket 工作原理
1. 核心前置:TCP 三次握手(连接建立)
Socket 基于 TCP 时,底层会自动完成三次握手,这是通信的前提:
- 客户端 → 服务端:我要建立连接(SYN)
- 服务端 → 客户端:收到请求,我也准备好(SYN+ACK)
- 客户端 → 服务端:确认收到,连接正式建立(ACK)
握手完成,TCP 通道打通,双方可以收发数据。 连接断开则是四次挥手,Socket 关闭时内核自动处理。
2. 传统 BIO 下 Socket 完整工作流程(最经典、最好理解)
分 服务端 和 客户端 两条链路,按执行顺序拆解,附简易 Java 伪代码。
3. (一)服务端流程(被动等待连接)
3.1. 步骤1:创建 ServerSocket(监听端口)
- 应用程序调用系统接口,在内核中创建一个监听型 Socket;
- 绑定本机 IP + 端口(例如 0.0.0.0:9092);
- 开始监听,等待客户端接入。 Java 代码:
// 绑定 9092 端口,开始监听
ServerSocket serverSocket = new ServerSocket(9092);
3.2. 步骤2:阻塞等待客户端连接 accept()
调用 serverSocket.accept(),线程进入阻塞状态:
- 此时线程卡住,什么都不做,一直等到有客户端发起连接;
- 客户端连上后,内核完成 TCP 三次握手,然后返回一个新的普通 Socket 对象。
重点:
ServerSocket只负责「监听、接连接」,不负责收发数据;
每一个客户端连接,都会生成一个全新独立 Socket。
// 阻塞等待连接,有客户端接入才会往下执行
Socket clientSocket = serverSocket.accept();
3.3. 步骤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());
3.4. 步骤4:关闭资源
通信结束,依次关闭流、Socket,释放操作系统端口和文件句柄。
in.close();
out.close();
clientSocket.close();
serverSocket.close();
3.5. BIO 服务端整体运行逻辑
创建ServerSocket → 绑定端口 → accept()阻塞等连接
↓
客户端接入 → 生成专属Socket → 新开线程处理读写(一连接一线程)
↓
读写阻塞 → 通信完成 → 关闭Socket
4. (二)客户端流程(主动发起连接)
客户端不需要 ServerSocket,直接创建普通 Socket 主动去连服务端。
4.1. 步骤1:创建 Socket,发起连接
指定服务端IP + 端口,内核自动发起 TCP 三次握手,建立连接。
// 连接 127.0.0.1:9092 服务端
Socket socket = new Socket("127.0.0.1", 9092);
4.2. 步骤2:读写数据
和服务端对称,用输入输出流收发数据:
// 发数据给服务端
OutputStream out = socket.getOutputStream();
out.write("hello server".getBytes());
// 读取服务端返回数据(阻塞)
InputStream in = socket.getInputStream();
byte[] buf = new byte[1024];
in.read(buf);
4.3. 步骤3:关闭连接
in.close();
out.close();
socket.close();
5. 关键特性:为什么 BIO 的 Socket 会阻塞?
这是理解瓶颈的核心:
accept()阻塞:没有客户端连接,线程一直等;read()阻塞:连接已建立,但对方没发数据,线程一直等。 结合之前饭店比喻:
accept()= 前台等客人上门;read()= 服务员等客人说话;
全程卡死,线程无法做其他事。 这就是 BIO 无法支撑高并发的根源。
6. NIO 中 Socket 的变化(过渡到 Channel)
前面说过,Java NIO 不再用传统 Socket/ServerSocket,而是改用:
ServerSocketChannel(替代 ServerSocket)SocketChannel(替代 Socket) 底层依然是操作系统 Socket,只是 Java 层做了改造:
- 可以设置 非阻塞模式:
accept()没有连接 → 直接返回,不卡线程;read()没有数据 → 直接返回,不卡线程。
- 配合
Selector多路复用:一个线程轮询上千个 Channel(底层Socket)。 工作简化流程:
ServerSocketChannel(非阻塞) → 注册到Selector
Selector轮询事件 → 有新连接 → 生成SocketChannel
SocketChannel注册到Selector → 轮询读写事件 → 非阻塞读写
本质没变:底层还是操作系统 Socket,只是调用方式、阻塞行为、线程模型改了。
7. Socket 必懂的补充知识点
8. 端口占用问题
- 一个端口同一时间只能被一个进程监听;
- Socket 正常关闭后,内核会进入
TIME_WAIT状态(默认几十秒),端口不会立刻释放,短时间内无法重启服务。
9. 文件句柄限制
操作系统中,每一个 Socket 连接都会占用一个文件句柄。
- 连接数越多,占用句柄越多;
- 系统有最大句柄上限,连接超限会直接报错:
Too many open files。
这也是高并发服务必须优化系统参数的原因。
10. 全双工通信
Socket 建立的 TCP 连接是全双工:
双方可以同时收发数据,互不干扰(输入流、输出流独立工作)。
11. 两种关闭方式
- 主动 close():正常断开连接,内核走四次挥手;
- 进程意外退出/断网:连接异常断开,内核回收资源。
12. 结合 Kafka 串联总结
- Kafka 对外监听 9092 端口,底层本质也是操作系统 Socket;
- 但它不用 Java BIO 的 Socket,而是用 NIO 的
SocketChannel; - Acceptor 线程负责接收新连接(对应
ServerSocketChannel.accept()); - Processor 线程通过 Selector 管理大量
SocketChannel,非阻塞读写; - 最终所有网络通信,落脚点都是操作系统内核的 Socket 套接字。
13. 一句话极简总结
- Socket 是操作系统提供的网络通信端点,基于 IP+端口定位进程;
- TCP 通信需要客户端Socket + 服务端Socket 成对,依靠三次握手建立连接;
- 传统 BIO 中 Socket 默认阻塞,一连接一线程,并发能力弱;
- NIO 的 Channel 是 Java 对底层 Socket 的新封装,支持非阻塞、多路复用,适配高并发;
- 所有 Java 网络组件(Kafka、Netty),底层都离不开操作系统 Socket。
关联文档
- TCP 粘包拆包:TCP 粘包/拆包问题
- Java IO 模型:BIO/NIO 模型
- HTTP 状态码详解:应用层协议
