NIO之bytebuffer黏包和半包

 作者简介:大家好,我是码炫码哥,前中兴通讯、美团架构师,现任某互联网公司CTO,兼职码炫课堂主讲源码系列专题


代表作:《jdk源码&多线程&高并发》,《深入tomcat源码解析》,《深入netty源码解析》,《深入dubbo源码解析》,《深入springboot源码解析》,《深入spring源码解析》,《深入redis源码解析》等


联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬。码炫课堂的个人空间-码炫课堂个人主页-哔哩哔哩视频

一、示例

网络上有多条数据发送给服务端,数据之间使用 \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 mx
     * 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!.          |
    +--------+-------------------------------------------------+----------------+
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值