Wrayの知识库 Wrayの知识库
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
  • Java章节编写规范
  • Java基础

  • Java集合

  • Java并发

  • Java IO

    • Java IO概述
    • IO基础

    • 常用IO流

    • 文件与目录操作

    • Java NIO

      • NIO概述
      • Buffer缓冲区
      • Channel通道
      • Selector多路复用
      • SocketChannel网络编程
        • 1. ServerSocketChannel
        • 2. SocketChannel
        • 3. 读取数据
        • 4. 写入数据
        • 5. TCP 粘包和拆包
        • 6. 半包写出
        • 7. 生产建议
        • 专家实践清单
        • Tips 快问快答
        • 8. 总结
      • NIO与IO对比
    • 高级主题

    • 实践与性能优化

    • 面试与设计题

    • IO实践速查

  • JVM

目录

SocketChannel网络编程

SocketChannel 和 ServerSocketChannel 是 NIO 中用于 TCP 网络通信的核心类。它们既可以阻塞使用,也可以配置为非阻塞并配合 Selector 使用。

# 1. ServerSocketChannel

服务端使用 ServerSocketChannel 监听端口。

ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress(8080));
server.configureBlocking(false);

非阻塞模式下,accept() 如果没有新连接会立即返回 null。

SocketChannel client = server.accept();
if (client != null) {
    client.configureBlocking(false);
}

# 2. SocketChannel

客户端使用 SocketChannel 连接服务端。

SocketChannel client = SocketChannel.open();
client.configureBlocking(false);
client.connect(new InetSocketAddress("localhost", 8080));

while (!client.finishConnect()) {
    // wait or do something else
}

非阻塞连接可能不会立即完成,需要调用 finishConnect()。

# 3. 读取数据

ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = channel.read(buffer);

if (len == -1) {
    channel.close();
} else if (len > 0) {
    buffer.flip();
    byte[] data = new byte[buffer.remaining()];
    buffer.get(data);
    buffer.clear();
}

返回值含义:

  • > 0:读取到的字节数。
  • 0:非阻塞模式下当前没有数据。
  • -1:对端关闭连接。

# 4. 写入数据

ByteBuffer buffer = StandardCharsets.UTF_8.encode("hello");

while (buffer.hasRemaining()) {
    channel.write(buffer);
}

阻塞模式下,上面通常可以完成写入。非阻塞模式下,write 可能返回 0,也可能只写入部分数据。真实框架需要维护待写队列,不能简单自旋。

# 5. TCP 粘包和拆包

TCP 是字节流协议,不保留应用层消息边界。

一次 write 不等于对端一次 read。

可能出现:

  • 粘包:多条消息被一次读到。
  • 拆包:一条消息被分多次读到。

解决方式需要应用层协议设计:

  • 固定长度消息。
  • 分隔符协议,如行协议。
  • 长度字段 + 消息体。
  • TLV 格式。

例如长度字段协议:

4字节消息长度 + N字节消息体

# 6. 半包写出

非阻塞写入时,不能假设一次写完:

int written = channel.write(buffer);
if (buffer.hasRemaining()) {
    // 保存 buffer,等待下次 OP_WRITE 继续写
}

这也是直接使用原生 NIO 编写网络框架复杂的原因之一。

# 7. 生产建议

  • 业务系统不要轻易手写复杂 NIO 网络框架。
  • 高性能 TCP 服务优先考虑 Netty。
  • IO 线程只做轻量读写和编解码,耗时业务交给业务线程池。
  • 协议必须明确消息边界。
  • 对连接数、读写缓冲、空闲超时、异常关闭做完整治理。

# 专家实践清单

问题 处理方式 风险
连接建立 非阻塞 connect + finishConnect 误判连接完成
半包粘包 协议定义长度或分隔符 消息边界混乱
写不完 维护待发送队列 数据丢失
连接空闲 心跳和超时关闭 死连接占资源
业务耗时 派发到业务线程池 IO 线程被阻塞

SocketChannel 生命周期:

open
  ▼
configureBlocking(false)
  ▼
connect / accept
  ▼
register Selector
  ▼
read / write
  ▼
close

# Tips 快问快答

Q:SocketChannel 主要用于什么? A:NIO TCP 客户端或已连接 Socket 的非阻塞读写。

Q:非阻塞 connect 后为什么要 finishConnect? A:连接过程可能尚未完成,需要在可连接事件中确认。

Q:TCP 为什么会有粘包拆包? A:TCP 是字节流协议,不保留应用层消息边界。

Q:如何解决消息边界问题? A:常用固定长度、长度字段、分隔符或更完整的协议编码。

Q:write 返回 0 代表什么? A:当前无法写出更多数据,需要稍后再写。

Q:为什么 IO 线程不能做慢业务? A:一个 IO 线程可能管理很多连接,阻塞会影响所有连接。

Q:生产中会直接手写 SocketChannel 吗? A:较少,复杂 TCP 服务通常使用 Netty 等成熟框架。

Q:SocketChannel 最常见的坑是什么? A:忽略半包、半写、异常关闭和连接状态管理。

# 8. 总结

SocketChannel 是理解非阻塞网络编程的核心类。它让一个线程处理多个连接成为可能,但也把粘包拆包、半包写出、状态管理等复杂性暴露给开发者。生产环境通常使用成熟框架封装这些细节。

上次更新: 2026/06/25, 14:19:18
Selector多路复用
NIO与IO对比

← Selector多路复用 NIO与IO对比→

Copyright © 2023-2026 Wray | 鄂ICP备2024050235号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式