在 Java NIO(基于 SocketChannel / ByteBuffer)中,本身不会自动处理“粘包/拆包”,TCP 是流式协议,发送的数据可能被合并(粘包)或被拆分(拆包)。
避免/正确处理粘包的核心思想是:在应用层定义“消息边界”。
下面按常见方案从简单到复杂说明。
write() 被 TCP 合并成一次 read()write() 被 TCP 拆成多次 read()NIO 中你通常会这样读:
ByteBuffer buffer = ByteBuffer.allocate(1024);
socketChannel.read(buffer);
如果你直接按 buffer 解析,就容易出现问题。
每条消息长度固定
ByteBuffer buffer = ByteBuffer.allocate(100);
while (buffer.hasRemaining()) {
socketChannel.read(buffer);
}
buffer.flip();
// 解析完整消息
✅ 优点:实现简单
❌ 缺点:浪费带宽,不灵活
用特殊字符标明消息结束
HELLO\n
WORLD\n
ByteBuffer buffer = ByteBuffer.allocate(1024);
socketChannel.read(buffer);
buffer.flip();
// 按 \n 拆分
String data = new String(buffer.array(), 0, buffer.limit());
String[] messages = data.split("\n");
✅ 优点:直观
❌ 缺点:内容中不能出现分隔符
消息 = 长度(4字节) + 内容
| int length | byte[] body |
ByteBuffer header = ByteBuffer.allocate(4);
socketChannel.read(header);
header.flip();
int length = header.getInt();
ByteBuffer body = ByteBuffer.allocate(length);
while (body.hasRemaining()) {
socketChannel.read(body);
}
body.flip();
✅ 优点:
✅ 这是 NIO 最推荐方式
NIO 中一定要 累积 ByteBuffer
ByteBuffer accumulate = ByteBuffer.allocate(8192);
// 每次 read 后
socketChannel.read(accumulate);
accumulate.flip();
while (accumulate.remaining() >= 4) {
accumulate.mark();
int len = accumulate.getInt();
if (accumulate.remaining() < len) {
accumulate.reset();
break;
}
byte[] data = new byte[len];
accumulate.get(data);
// 处理消息
}
accumulate.compact();
如果你在用 Java NIO,99% 情况应该用 Netty
Netty 已内置:
LengthFieldBasedFrameDecoder(长度字段)LineBasedFrameDecoder(换行)DelimiterBasedFrameDecoderpipeline.addLast(new LengthFieldBasedFrameDecoder(
1024 * 1024, 0, 4, 0, 4));
✅ 不用自己处理粘包
✅ 性能高
✅ 社区成熟
| 方案 | 是否推荐 | 说明 |
|---|---|---|
| 定长 | ⭐⭐ | 简单但不灵活 |
| 分隔符 | ⭐⭐⭐ | 适合文本 |
| 长度字段 | ⭐⭐⭐⭐⭐ | 最通用 |
| Netty | ⭐⭐⭐⭐⭐ | 强烈推荐 |
如果你愿意,我可以:
你现在是 自己写 NIO,还是打算用 Netty?
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。