[TOC]
一、示例
网络上有多条数据发送给服务端,数据之间使用 \n 进行分隔 > 但由于某种原因这些数据在接收时,被进行了重新组合,例如原始数据有3条为
- Hello,world\n
- I'm zhangsan\n
- How are you?\n
变成了下面的两个 byteBuffer (黏包,半包)
- Hello,world\nI'm zhangsan\nHo
- w are you?\n
分析
本来分别将上方三条数据发送给服务端,但是发送到服务的却是两条
- 第一条数据中的第二行和第三行中的Ho当作一条数据发送给了服务端,产生了 黏包 。
- 第二条数据,因为服务端并未接收到完整的第三条数据,所以产生了 半包 。
二、黏包和半包出现原因
- 黏包
发送方在发送数据时,并不是一条一条地发送数据,而是 将数据整合在一起 ,当数据达到一定的数量后再一起发送。这就会导致多条信息被放在一个缓冲区中被一起发送出去
- 半包
接收方的缓冲区的大小是有限的,当接收方的缓冲区满了以后,就需要 将信息截断 ,等缓冲区空了以后再继续放入数据。这就会发生一段完整的数据最后被截断的现象
三、代码示例
解决方案
-
通过get(index)方法遍历ByteBuffer,遇到分隔符时进行处理。注意:get(index)不会改变position的值
- 记录该段数据长度,以便于申请对应大小的缓冲区
- 将缓冲区的数据通过get()方法写入到target中
-
调用 compact方法 切换模式,因为缓冲区中可能还有未读的数据
import java.nio.ByteBuffer;
import static com.lilinchao.nio.bytebuffer_2.ByteBufferUtil.debugAll;
/**
* Created by lilinchao
* Date 2022/5/26
* Description 黏包和半包 Demo
*/
public class TestByteBufferExam {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(32);
// 模拟黏包
buffer.put("Hello,world\nI'm zhangsan\nHo".getBytes());
split(buffer);
//模拟半包
buffer.put("w are you?\nhaha!\n".getBytes());
split(buffer);
}
private static void split(ByteBuffer buffer){
//切换到读模式 才能从buffer中读取数据
buffer.flip();
int oldLimit = buffer.limit();
for (int i=0;i<oldLimit;i++){
// 遍历寻找分隔符
// get(i)不会移动position
if(buffer.get(i) == '\n'){
System.out.println(i);
// 缓冲区长度
int length = i+1-buffer.position();
ByteBuffer target = ByteBuffer.allocate(length);
// 0 ~ limit
buffer.limit(i + 1);
target.put(buffer); //从buffer 读,向 target 写
debugAll(target);
buffer.limit(oldLimit);
}
}
//切换到写模式,但是缓冲区可能未读完,这里需要使用compact
buffer.compact();
}
}
运行结果
11
+--------+-------------------- all ------------------------+----------------+
position: [12], limit: [12]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 48 65 6c 6c 6f 2c 77 6f 72 6c 64 0a |Hello,world. |
+--------+-------------------------------------------------+----------------+
24
+--------+-------------------- all ------------------------+----------------+
position: [13], limit: [13]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 49 27 6d 20 7a 68 61 6e 67 73 61 6e 0a |I'm zhangsan. |
+--------+-------------------------------------------------+----------------+
12
+--------+-------------------- all ------------------------+----------------+
position: [13], limit: [13]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 48 6f 77 20 61 72 65 20 79 6f 75 3f 0a |How are you?. |
+--------+-------------------------------------------------+----------------+
18
+--------+-------------------- all ------------------------+----------------+
position: [6], limit: [6]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 68 61 68 61 21 0a |haha!. |
+--------+-------------------------------------------------+----------------+
附参考文章:
《黑马程序员Netty实战》
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。