Java ByteBuffer网络编程用法实例解析
作者:cuisuqiang 发布时间:2022-09-17 20:16:22
做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不是我们平常的取值和设值一样,他会操纵一些属性变化。
来源:https://www.iteye.com/blog/cuisuqiang-1443212


猜你喜欢
- ztree生成树状图ztree官网前台导入js和css包下载地址前端页面 ztree.jsp<%@ page contentType=
- 这篇文章是android开发人员的必备知识,是我特别为大家整理和总结的,不求完美,但是有用。1.背景自适应且不失真问题的存在制作自适应背景图
- 需求在前面的文章里使用.NET 6开发TodoList应用之领域实体创建原理和思路,我们留了一个坑还没有填上,就是在数据库保存的时
- 方法一:实现Comparator接口,并重写compare方法实体类代码:import java.util.Comparator;/** *
- Java语言是简单的:Java语言的语法与C语言和C++语言很接近,使得大多数程序员很容易学习和使用。另一方面,Java丢弃了C++中很少使
- 程序是这样的:static void Main(string[] args){ SmtpClient c
- 二叉搜索树的定义它是一颗二叉树任一节点的左子树上的所有节点的值一定小于该节点的值任一节点的右子树上的所有节点的值一定大于该节点的值特点: 二
- 在java中如果我们需要遍历集合并删除其中的某些元素时,例如对于List来说,我们有三种办法。1. 普通的for循环遍历并删除public
- 1. 引入当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象, 只有通过new关键字才会产生出对象,这时系统才会
- 单行文本的输入存在严重的缺陷,也不适合实际的运用,本节通过一个无功能的记事本来介绍可以进行多行输入的JTextArea:JTextArea(
- 前置知识Kotlin协程不是什么空中阁楼,Kotlin源代码会被编译成class字节码文件,最终会运行到虚拟机中。所以从本质上讲,Kotli
- 上一篇讲解了类型,通过类型来开始本篇的学习;int a[10];上述代码中的a是什么类型呢?相信很多人都知道是一个数组类型,具体来说是一个i
- 背景众所周知,所有被打开的系统资源,比如流、文件或者Socket连接等,都需要被开发者手动关闭,否则随着程序的不断运行,资源泄露将会累积成重
- MyBatis-Plus不使用数据库默认值的问题有时候我们在设计数据表时希望某些字段使用默认值,比如create_time、和update_
- 流程引擎对象和其配置对象都是activiti的核心对象一、activiti的简单使用流程activiti在工作时,一般有以下几个步骤:创建一
- 一、获取程序集版本 程序代码 label版本.Text = System.Reflection.Assembly.GetExecutingA
- — 遇到问题今天在IDEA里面运行项目的时候报了一个错,如下图所示:— 找到问题根源其实控制台给出的错误信息提示说的很明显:类加载器加载文件
- JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,采用完全独立于语言的文本格式,是理想的数据交换
- 泛型的概述和优势泛型概述泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。泛型的格式:<数据类型>;
- 一、简介Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新spring应用的初始搭建以及开发过程。该框架使用