• 【转】ByteBuffer 到底怎么用?网络编程中一点总结!--不错


    原文网址:http://cuisuqiang.iteye.com/blog/1443212

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

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

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

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

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

    如何建立缓冲?

    Java代码  收藏代码
    1. /** 
    2.  * 全局MVB数据缓冲区 占用 1M 内存 
    3.  */  
    4. private static ByteBuffer bbuf = ByteBuffer.allocate(10240);  
    5.   
    6. /** 
    7.  * 线程安全的取得缓冲变量 
    8.  */  
    9. public static synchronized ByteBuffer getByteBuffer() {  
    10.     return bbuf;  
    11. }  

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

    Java代码  收藏代码
    1. public void run() {  
    2.     Socket socket = GlobalClientKeep.mvbSocket;  
    3.     if (null != socket) {  
    4.         try {  
    5.             // 获得mvb连接引用  
    6.             OutputStream ops = socket.getOutputStream();  
    7.             InputStream ips = socket.getInputStream();  
    8.             while (true) {  
    9.                 if (null != ops && null != ips) {  
    10.                     // 接收返回信息  
    11.                     byte[] bt = StreamTool.inputStreamToByte(ips);  
    12.                     ByteBuffer bbuf = GlobalCommonObjectKeep.getByteBuffer();  
    13.                     // 设置到缓冲区中  
    14.                     bbuf.put(bt);  
    15.                     // ////////////////////////////////////////////////////////////////////////  
    16.                     // 拆包解析方法  
    17.                     splitByte(ops);  
    18.                     ops.flush();  
    19.                 }  
    20.             }  
    21.         } catch (Exception e) {  
    22.             e.printStackTrace();  
    23.         }  
    24.     } else {  
    25.         // 如果连接存在问题,则必须重新建立  
    26.         GlobalClientKeep.initMvbSocket();  
    27.     }  
    28. }  

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

    Java代码  收藏代码
    1. byte[] bt = StreamTool.inputStreamToByte(ips);  

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

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

    Java代码  收藏代码
    1. /** 
    2.  * @说明 拆包解析方法 
    3.  */  
    4. public static void splitByte(OutputStream ops) {  
    5.     try {  
    6.         ByteBuffer bbuf = GlobalCommonObjectKeep.getByteBuffer();  
    7.         int p = bbuf.position();  
    8.         int l = bbuf.limit();  
    9.         // 回绕缓冲区 一是将 curPointer 移到 0, 二是将 endPointer 移到有效数据结尾  
    10.         bbuf.flip();  
    11.         byte[] byten = new byte[bbuf.limit()]; // 可用的字节数量  
    12.         bbuf.get(byten, bbuf.position(), bbuf.limit()); // 得到目前为止缓冲区所有的数据  
    13.         // 进行基本检查,保证已经包含了一组数据  
    14.         if (checkByte(byten)) {  
    15.             byte[] len = new byte[4];  
    16.             // 数组源,数组源拷贝的开始位子,目标,目标填写的开始位子,拷贝的长度  
    17.             System.arraycopy(byten, 0, len, 0, 4);  
    18.             int length = StreamTool.bytesToInt(len); // 每个字节流的最开始肯定是定义本条数据的长度  
    19.             byte[] deco = new byte[length]; // deco 就是这条数据体  
    20.             System.arraycopy(byten, 0, deco, 0, length);  
    21.             // 判断消息类型,这个应该是从 deco 中解析了,但是下面具体的解析内容不再啰嗦  
    22.             int type = 0;  
    23.             // 判断类型分类操作  
    24.             if (type == 1) {  
    25.                   
    26.             } else if (type == 2) {  
    27.                   
    28.             } else if (type == 3) {  
    29.                   
    30.             } else {  
    31.                 System.out.println("未知的消息类型,解析结束!");  
    32.                 // 清空缓存  
    33.                 bbuf.clear();  
    34.             }  
    35.             // 如果字节流是多余一组数据则递归  
    36.             if (byten.length > length) {  
    37.                 byte[] temp = new byte[bbuf.limit() - length];  
    38.                 // 数组源,数组源拷贝的开始位子,目标,目标填写的开始位子,拷贝的长度  
    39.                 System.arraycopy(byten, length, temp, 0, bbuf.limit() - length);  
    40.                 // 情况缓存  
    41.                 bbuf.clear();  
    42.                 // 重新定义缓存  
    43.                 bbuf.put(temp);  
    44.                 // 递归回调  
    45.                 splitByte(ops);  
    46.             }else if(byten.length == length){ // 如果只有一条数据,则直接重置缓冲就可以了  
    47.                 // 清空缓存  
    48.                 bbuf.clear();  
    49.             }  
    50.         } else {  
    51.             // 如果没有符合格式包含数据,则还原缓冲变量属性  
    52.             bbuf.position(p);  
    53.             bbuf.limit(l);  
    54.         }  
    55.     } catch (Exception e) {  
    56.         e.printStackTrace();  
    57.     }  
    58. }  

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

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

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

    Java代码  收藏代码
    1.    /** 
    2.     * Flips this buffer.  The limit is set to the current position and then 
    3.     * the position is set to zero.  If the mark is defined then it is 
    4.     * discarded. 
    5.     * 
    6.     * <p> After a sequence of channel-read or <i>put</i> operations, invoke 
    7.     * this method to prepare for a sequence of channel-write or relative 
    8.     * <i>get</i> operations.  For example: 
    9.     * 
    10.     * <blockquote><pre> 
    11.     * buf.put(magic);    // Prepend header 
    12.     * in.read(buf);      // Read data into rest of buffer 
    13.     * buf.flip();        // Flip buffer 
    14.     * out.write(buf);    // Write header + data to channel</pre></blockquote> 
    15.     * 
    16.     * <p> This method is often used in conjunction with the {@link 
    17.     * java.nio.ByteBuffer#compact compact} method when transferring data from 
    18.     * one place to another.  </p> 
    19.     * 
    20.     * @return  This buffer 
    21.     */  
    22.    public final Buffer flip() {  
    23. limit = position;  
    24. position = 0;  
    25. mark = -1;  
    26. return this;  
    27.    }  

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

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

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

    请您到ITEYE看我的原创:http://cuisuqiang.iteye.com

    或支持我的个人博客,地址:http://www.javacui.com

     
  • 相关阅读:
    怎样提高开发效率
    ASP.NET/C#获取文章中图片的地址
    listBox的用法
    ASP.NET中的asp:label和asp:literal
    ID,ClientID,UniqueID的区别
    asp.net中的属性
    数据库中的值为空的判断 ,并赋初值
    属性器,转换从数据库中读取的状态
    Collections.emptyList() and Collections.EMPTY_LIST
    InnoDB
  • 原文地址:https://www.cnblogs.com/wi100sh/p/4365631.html
Copyright © 2020-2023  润新知