做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不是我们平常的取值和设值一样,他会操纵一些属性变化。
请您到ITEYE看我的原创:http://cuisuqiang.iteye.com
或支持我的个人博客,地址:http://www.javacui.com
相关推荐
ios-byteBuffer [![CI状态]( Lee / ios-byteBuffer.svg?style = flat)]( Lee / ios-byteBuffer ) 用法 #分配 ByteBuffer *buffer = [ByteBuffer initWithOrder: ByteOrderLittleEndian]; #输入数据 - ( ...
Android
近百节视频详细讲解,...4. 网络编程 5. NIO vs BIO 二. Netty 入门 1. 概述 2. Hello World 3. 组件 4. 双向通信 三. Netty 进阶 1. 粘包与半包 2. 协议设计与解析 3. 聊天室案例 四. 优化与源码 1. 优化 2. 源码分析
仿安卓ByteBuffer 完美组包、拆包
使用nio byteBuffer 实现按行读取文件(大文件) 在window/linux/macOS上均测试通过 对于中文乱码也已处理成功 完整注释,可随需求更改 有问题请邮件:mly610865580@126.com
jdk api-ServerSocketChannel、Selector、ByteBuffer结合实现网络报文间的通讯
主要介绍了Android在JNI中使用ByteBuffer的方法,涉及Android中缓冲区的相关使用技巧,需要的朋友可以参考下
主要解决从流中获取数据,缓存,拆解,可用于TCP粘包问题
易语言汇编版ByteBuffer源码。主要用于各种网络协议的组包 具体用法可以点上面的网址 功能和jAVA的一样。@10371178。Tags:易语言汇编版ByteBuffer源码。
【IT十八掌徐培成】Java基础第26天-05.ByteBuffer-mark-pos-limit-cap-flip.zip
├─(5) 第1章_05_bytebuffer-基本使用.mp4 ├─(6) 第1章_06_bytebuffer-内部结构.mp4 ├─(7) 第1章_07_bytebuffer-方法演示1.mp4 ├─(8) 第1章_08_bytebuffer-方法演示2.mp4 ├─(9) 第1章_09_bytebuffer-方法...
易语言汇编版ByteBuffer源码主要用于各种网络协议的组包 具体用法可以点上面的网址 功能和jAVA的一样
java api之ByteBuffer基础、应用场景、实战讲解 文档中有丰富的例子代码实现
protobuf+long+bytebuffer,利用protobuf.js实现编解码 所需的三个js库
* <p>Title: Socekt编程学习 * * <p>Description: 文件传输接收端 * * <p>Copyright: Copyright (c) 2009 * * <p>Company: 酷猫科技</a></p> * * @author 贺翔 * @version 1.0 */ public class ...
java中的中文乱码(其中介绍了中国近现代的汉字革命) java中为什么会产生中文乱码 如何解决java中的中文乱码
dena-bytebuffer
声明:这些内容是逐步总结过来的,所以可能有当时的理解不正确,只希望大家能做个参考: 内容如下: 目录 一句话总结汇总: Copy project into workspace 和add project into work set 的含义 数字签名总结 JNI ...