Java ByteBuffer网络编程用法实例解析

作者:cuisuqiang 时间:2022-09-17 20:16:22 

做tcp网络编程,要解析一批批的数据,可是数据是通过Socket连接的InputStream一次次读取的,读取到的不是需要转换的对象,而是要直接根据字节流和协议来生成自己的数据对象。

按照之前的编程思维,总是请求然后响应,当然Socket也是请求和响应,不过与单纯的请求响应是不同的。

这里Socket连接往往是要保持住的,也就是长连接,然后设置一个缓冲区,网络流不断的追加到缓冲区。然后后台去解析缓冲区的字节流。

Java ByteBuffer网络编程用法实例解析

如图所示,网络的流一直在传递,我们收到也许是完成的数据流,也可能是没有传递完的。这里就需要监视管道,不断读取管道中的流数据,然后向缓冲区追加。程序从头开始解析,如果目前缓冲区包含了数据,则解析,没有则放弃继续读取管道流。

就算管道中包含了数据,也不一定包含了完成的数据。例如,100个字节是一个数据体,可是目前缓冲区内包含了120个字节,这就是说缓冲区包含了一条数据,但是还有没有传递完的字节流。那么就要把前100个字节拿出来解析,然后从缓冲区清除这100个字节。那缓冲区就剩下20个字节了,这些数据可能在下次流中补充完成。

如何建立缓冲?


/**
* 全局MVB数据缓冲区 占用 1M 内存
*/
private static ByteBuffer bbuf = ByteBuffer.allocate(10240);

/**
* 线程安全的取得缓冲变量
*/
public static synchronized ByteBuffer getByteBuffer() {
 return bbuf;
}

写一个Socket客户端,该客户端得到Socket连接,然后读取流,一直向缓冲中追加字节流,每次追加后调用一个方法来解析该流


public void run() {
 Socket socket = GlobalClientKeep.mvbSocket;
 if (null != socket) {
   try {
     // 获得mvb连接引用
     OutputStream ops = socket.getOutputStream();
     InputStream ips = socket.getInputStream();
     while (true) {
       if (null != ops && null != ips) {
         // 接收返回信息
         byte[] bt = StreamTool.inputStreamToByte(ips);
         ByteBuffer bbuf = GlobalCommonObjectKeep.getByteBuffer();
         // 设置到缓冲区中
         bbuf.put(bt);
         // ////////////////////////////////////////////////////////////////////////
         // 拆包解析方法
         splitByte(ops);
         ops.flush();
       }
     }
   } catch (Exception e) {
     e.printStackTrace();
   }
 } else {
   // 如果连接存在问题,则必须重新建立
   GlobalClientKeep.initMvbSocket();
 }
}

关于如何读取流,我有一篇博客专门讲解了所以这里是直接调用方法

byte[] bt = StreamTool.inputStreamToByte(ips);

那么解析方法是如何做的?

解析方法首先获得该缓冲中的所有可用字节,然后判断是否符合一条数据条件,符合就解析。如果符合两条数据条件,则递归调用自己。其中每次解析一条数据以后,要从缓冲区中清除已经读取的字节信息。


/**
* @说明 拆包解析方法
*/
public static void splitByte(OutputStream ops) {
 try {
   ByteBuffer bbuf = GlobalCommonObjectKeep.getByteBuffer();
   int p = bbuf.position();
   int l = bbuf.limit();
   // 回绕缓冲区 一是将 curPointer 移到 0, 二是将 endPointer 移到有效数据结尾
   bbuf.flip();
   byte[] byten = new byte[bbuf.limit()]; // 可用的字节数量
   bbuf.get(byten, bbuf.position(), bbuf.limit()); // 得到目前为止缓冲区所有的数据
   // 进行基本检查,保证已经包含了一组数据
   if (checkByte(byten)) {
     byte[] len = new byte[4];
     // 数组源,数组源拷贝的开始位子,目标,目标填写的开始位子,拷贝的长度
     System.arraycopy(byten, 0, len, 0, 4);
     int length = StreamTool.bytesToInt(len); // 每个字节流的最开始肯定是定义本条数据的长度
     byte[] deco = new byte[length]; // deco 就是这条数据体
     System.arraycopy(byten, 0, deco, 0, length);
     // 判断消息类型,这个应该是从 deco 中解析了,但是下面具体的解析内容不再啰嗦
     int type = 0;
     // 判断类型分类操作
     if (type == 1) {

} else if (type == 2) {

} else if (type == 3) {

} else {
       System.out.println("未知的消息类型,解析结束!");
       // 清空缓存
       bbuf.clear();
     }
     // 如果字节流是多余一组数据则递归
     if (byten.length > length) {
       byte[] temp = new byte[bbuf.limit() - length];
       // 数组源,数组源拷贝的开始位子,目标,目标填写的开始位子,拷贝的长度
       System.arraycopy(byten, length, temp, 0, bbuf.limit() - length);
       // 情况缓存
       bbuf.clear();
       // 重新定义缓存
       bbuf.put(temp);
       // 递归回调
       splitByte(ops);
     }else if(byten.length == length){ // 如果只有一条数据,则直接重置缓冲就可以了
       // 清空缓存
       bbuf.clear();
     }
   } else {
     // 如果没有符合格式包含数据,则还原缓冲变量属性
     bbuf.position(p);
     bbuf.limit(l);
   }
 } catch (Exception e) {
   e.printStackTrace();
 }
}

代码只是一个参考,主要讲解如何分解缓冲区,和取得缓冲区的一条数据,然后清除该数据原来站的空间。

至于缓冲区的属性,如何得到缓冲区的数据,为什么要清空,bbuf.flip();是什么意思。下面来说一下关于ByteBuffer 的一下事情。

ByteBuffer 中有几个属性,其中有两个很重要。limit和 position。position开始在0,填充数据后等于数据的长度,而limit是整个缓冲可用的长度。bbuf.flip();之后,position直接变为0,而limit直接等于position。JDK源码如下:


 /**
  * Flips this buffer. The limit is set to the current position and then
  * the position is set to zero. If the mark is defined then it is
  * discarded.
  *
  * <p> After a sequence of channel-read or <i>put</i> operations, invoke
  * this method to prepare for a sequence of channel-write or relative
  * <i>get</i> operations. For example:
  *
  * <blockquote><pre>
  * buf.put(magic);  // Prepend header
  * in.read(buf);   // Read data into rest of buffer
  * buf.flip();    // Flip buffer
  * out.write(buf);  // Write header + data to channel</pre></blockquote>
  *
  * <p> This method is often used in conjunction with the {@link
  * java.nio.ByteBuffer#compact compact} method when transferring data from
  * one place to another. </p>
  *
  * @return This buffer
  */
 public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
 }

这样,在position和limit之间的数据就是我们要的可用数据。

但是position和limit是ByteBuffer在put和get时需要的属性,所以在使用后要么还原,要么像上面代码一样,清除一些字节信息然后重置。

ByteBuffer 的get和put不是我们平常的取值和设值一样,他会操纵一些属性变化。

来源:https://www.iteye.com/blog/cuisuqiang-1443212

标签:Java,ByteBuffer
0
投稿

猜你喜欢

  • 在C#中使用MSMQ的方法

    2023-04-05 20:28:29
  • Java中stream处理中map与flatMap的比较和使用案例

    2023-11-21 02:27:53
  • 程序员最喜欢的ThreadLocal使用姿势

    2022-10-22 21:14:55
  • 用C#做网络爬虫的步骤教学

    2023-12-24 10:31:52
  • 详解Android系统中的root权限获得原理

    2023-06-19 20:28:25
  • 设置Android设备WIFI在休眠时永不断开的代码实现

    2022-08-26 09:03:00
  • 浅谈Java中ThreadLocal内存泄露的原因及处理方式

    2021-06-12 21:08:37
  • android 仿微信聊天气泡效果实现思路

    2022-11-12 21:11:35
  • Java日期时间类(Date、DateFormat、Calendar)解析

    2022-08-06 18:02:14
  • Springboot整合mybatis开启二级缓存的实现示例

    2023-02-24 13:07:18
  • C#对二进制数据进行base64编码的方法

    2023-09-02 23:26:12
  • OpenCV计算图像的水平和垂直积分投影

    2021-10-09 08:40:45
  • SpringBoot启动访问localhost:8080报错404的解决操作

    2021-07-20 08:44:00
  • Java语法中Lambda表达式无法抛出异常的解决

    2022-10-13 01:04:43
  • java实现纸牌游戏之小猫钓鱼算法

    2021-08-11 22:57:00
  • C#通过链表实现队列的方法

    2023-06-19 15:14:17
  • Android内核源码 在Ubuntu上下载,编译,安装

    2023-02-21 19:43:39
  • Android入门之onTouchEvent触碰事件的示例详解

    2021-08-31 14:27:58
  • Spring的事务机制实例代码

    2021-09-11 07:46:23
  • idea构建web项目的超级详细教程

    2023-09-07 13:21:39
  • asp之家 软件编程 m.aspxhome.com