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

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

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

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

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

就算管道中包含了数据,也不一定包含了完成的数据。例如,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不是我们平常的取值和设值一样,他会操纵一些属性变化。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • java 流与 byte[] 的互转操作

    1. InputStream -> byte[] 引入 apache.commons.is 包 import org.apache.commons.io.IOUtils; byte[] bytes = IOUtils.toByteArray(inputStream); 2. byte[] -> InputStream InputStream inputStream = new ByteArrayInputStream(bytes); 补充知识:byte[]与各种数据类型互相转换示例 在sock

  • Android在JNI中使用ByteBuffer的方法

    本文实例讲述了Android在JNI中使用ByteBuffer的方法.分享给大家供大家参考.具体如下: 一.ByteBuffer 定义 在NIO中,数据的读写操作始终是与缓冲区相关联的(读取时信道(SocketChannel)将数据读入缓冲区,写入时首先要将发送的数据按顺序填入缓冲区) 缓冲区是定长的,基本上它只是一个列表,它的所有元素都是基本数据类型.ByteBuffer是最常用的缓冲区,它提供了读写其他数据类型的方法,且信道的读写方法只接收ByteBuffer. ByteBuffer有以下几

  • node.js中的buffer.Buffer.byteLength方法使用说明

    方法说明: 获取字符串的字节长度. 这个函数与 String.prototype.length 不同点在于,后者返回的是字符串的字符数. 语法: 复制代码 代码如下: Buffer.byteLength(string, [encoding]) 接收参数: string                              字符创 encoding                        字符串编码,默认为 'utf8′ 例子: 复制代码 代码如下: str = '\u00bd + \u

  • 解决redis与Python交互取出来的是bytes类型的问题

    基本代码 from redis import * if __name__ == '__main__': sr = StrictRedis(host='localhost', port=6379, db=0) result=sr.set('name','python') print(result) result1 = sr.get('name') print(result1) 运行结果: True b'python' 这里我们存进去的是字符串类型的数据,取出来却是字节类型的,这是由于python3

  • Java byte数组操纵方式代码实例解析

    字节数组的关键在于它为存储在该部分内存中的每个8位值提供索引(快速),精确的原始访问,并且您可以对这些字节进行操作以控制每个位. 坏处是计算机只将每个条目视为一个独立的8位数 - 这可能是你的程序正在处理的,或者你可能更喜欢一些强大的数据类型,如跟踪自己的长度和增长的字符串 根据需要,或者一个浮点数,让你存储说3.14而不考虑按位表示. 作为数据类型,在长数组的开头附近插入或移除数据是低效的,因为需要对所有后续元素进行混洗以填充或填充创建/需要的间隙. java官方提供了一种操作字节数组的方法-

  • Java任意长度byte数组转换为int数组的方法

    前言 嗯.最近工程上遇到一个byte数组转换为int的问题,解决过程中遇到了几个坑,经过各种查资料终于还是解决了.撒花. Java的位运算以及byte数组与其他类型数据的转换比c/c++感觉麻烦一些.这里简单说明一下byte数组和int的转换吧. 总所周知,int类型数据在Java中占据32 bit,byte占据8 bit,这样的话,正常情况下4个byte可以转换成一个int类型数据. 当然,不正常情况下,3个byte或者2个byte甚至1个byte都可以转换为int数据,只需要高位补上就行.

  • Java将byte[]转图片存储到本地的案例

    Java中,将字节数组转成图片的有很多种方式,今天在这里记录其中一种,方便以后查询,也可以提供给没有接触的童鞋做一个参考. 首先是将图片转成字节数组 import sun.misc.BASE64Encoder; import java.io.*; // 传入图片路径,获取图片 FileInputStream fis = new FileInputStream("/Users/curry/error.png"); BufferedInputStream bis = new Buffere

  • Mybatis在sqlite中无法读写byte[]类问题的解决办法

    开发环境: springboot + mybatis plus 场景:在DAO的bean中有byte[]类时,写入可以成功,但是读取不行.从错误栈中可以看到原因是:sqlite的driver中,JDBC4ResultSet没有实现以下接口: public Blob getBlob(int col) throws SQLException { throw unused(); } public Blob getBlob(String col) throws SQLException { throw

  • 基于JSON实现传输byte数组过程解析

    今晚上在编写udp传输文件的时候发现无法用JSON传输字节数组,试了很多种办法都会报错,最后查资料找到了Base64这个类,这个类可以将字节数组转为字符串,在JSON中传输以后可以再转化为字节数组. 写个小例子如下: package test; import java.util.Base64; public class testStringAndbyte { public static void main(String[] args) { // TODO Auto-generated metho

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

    做tcp网络编程,要解析一批批的数据,可是数据是通过Socket连接的InputStream一次次读取的,读取到的不是需要转换的对象,而是要直接根据字节流和协议来生成自己的数据对象. 按照之前的编程思维,总是请求然后响应,当然Socket也是请求和响应,不过与单纯的请求响应是不同的. 这里Socket连接往往是要保持住的,也就是长连接,然后设置一个缓冲区,网络流不断的追加到缓冲区.然后后台去解析缓冲区的字节流. 如图所示,网络的流一直在传递,我们收到也许是完成的数据流,也可能是没有传递完的.这里

  • java之static关键字用法实例解析

    本文实例讲述了java中static关键字用法,分享给大家供大家参考.具体分析如下: 一.介绍: 1.在类中,用static声明的成员变量为静态成员变量,它为该类的公用变量,在第一次使用时被初始化,对于该类的所有对象来说,static成员变量只有一份. 2.用static声明的方法为静态方法,在调用该方法时,不会将对象的引用传递给它,所以在static方法中不可访问非static成员.(静态方法不再是针对于某个对象调用,所以不能访问非静态成员) 3.可以通过对象引用或类名(不需要实例化)访问静态

  • java之super关键字用法实例解析

    本文实例讲述了java中super关键字的用法.分享给大家供大家参考.具体方法分析如下: super关键字:在java中使用super来引用基类的成分. 程序代码如下: class FatherClass{ public int value; public void f(){ value = 100; System.out.println("FatherClass.value:"+value); } } class ChildClass extends FatherClass{ pri

  • Java编程WeakHashMap实例解析

    简述: <Thinking in Java>第4版 P519 页 WeakHashMap一章读书笔记 WeakHashMap 用来保存WeakReference,这一结构云逊垃圾回收器自动清理键和值 在添加键和值的操作时,映射会自动使用WeakReference包装它们, 见jdk源代码, public V put(K key, V value) { Object k = maskNull(key); int h = hash(k); Entry<K,V>[] tab = getT

  • Java抽象类概念与用法实例分析

    本文实例讲述了Java抽象类概念与用法.分享给大家供大家参考,具体如下: 抽象:就是对一个事物的大概描述 抽象方法:以abstract修饰的方法,这种方法只声明返回数据类型,方法名和所需参数,并没有函数体.如 abstract void study(); 抽象类特点: 1.抽象类中不一定含有抽象方法:但抽象方法一定在抽象类中. 2.抽象类不具备实际功能,只能用于派生子类 3.抽象类中可以包含构造函数,但是构造函数不能被声明成抽象.抽象类中的成员方法包括一般方法和抽象方法 4.抽象方法和抽象类都必

  • Sql Server中Substring函数的用法实例解析

    SQL 中的 substring 函数是用来抓出一个栏位资料中的其中一部分.这个函数的名称在不同的资料库中不完全一样: MySQL: SUBSTR( ), SUBSTRING( ) Oracle: SUBSTR( ) SQL Server: SUBSTRING( ) SQL 中的 substring 函数是用来截取一个栏位资料中的其中一部分. 例如,我们需要将字符串'abdcsef'中的'abd'给提取出来,则可用substring 来实现: select substring('abdcsef'

  • Python元字符的用法实例解析

    反斜杠的作用: 要想将一个元字符^当一个普通字符处理,加反斜杠 例如: >>>import re >>>r=r'\^abc' >>>re.findall(r,'^abc ^abc ^abc') ['^abc','^abc','^abc'] \d匹配任何十进制数,它相当于类[0-9]. \D匹配任何非数字字符,它相当于类[^0-9] \s匹配任何空白字符,他相当于类[\t\n\r\f\v] \S匹配任何非空白字符,它相当于类[^\t\n\r\f\v] \

  • Python numpy线性代数用法实例解析

    这篇文章主要介绍了Python numpy线性代数用法实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 numpy中线性代数用法 矩阵乘法 >>> import numpy as np >>> x=np.array([[1,2,3],[4,5,6]]) >>> y=np.array([[7,8],[-1,7],[8,9]]) >>> x array([[1, 2, 3], [4

  • java new一个对象的过程实例解析

    这篇文章主要介绍了java new一个对象的过程实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 java在new一个对象的时候,会先查看对象所属的类有没有被加载到内存,如果没有的话,就会先通过类的全限定名来加载.加载并初始化类完成后,再进行对象的创建工作. 我们先假设是第一次使用该类,这样的话new一个对象就可以分为两个过程:加载并初始化类和创建对象. 一.类加载过程(第一次使用该类) java是使用双亲委派模型来进行类的加载的,所以在

  • Java lock同步锁使用实例解析

    这篇文章主要介绍了Java lock同步锁使用实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就

随机推荐