ByteBuffer 到底怎么用?网络编程中一点总结!

本文介绍如何在TCP网络编程中处理连续的数据流。通过维护一个全局缓冲区,不断接收并解析网络流数据,实现数据包的正确划分与处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

做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, 04);  
  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

<think>好的,我现在要解决用户的问题:如何将BYTE数组转换为WORD数据。首先,我需要明确用户的需求。用户提到的BYTE数组和WORD数据,可能涉及到低级数据处理,比如在编程中处理二进制数据。WORD通常指的是16位无符号整数,而BYTE是8位的。因此,转换的关键在于将两个连续的BYTE合并成一个WORD。 接下来,我需要回忆相关的知识。在C/C++中,可以通过指针转换或位移操作来实现。例如,将两个字节拼接,高位字节左移8位,然后与低位字节进行或运算。不过用户可能使用的是其他语言,比如Java或Python,所以需要考虑不同语言的实现方式。 然后,查看用户提供的引用内容。引用[1]和[2]提到了Java中将文件转为字节数组,以及字节操作。引用[2]中的代码示例展示了如何从双字中提取高位字节,这可能与处理字节顺序有关。用户的问题可能涉及到类似的位移操作,但需要将两个字节组合成WORD。 需要注意字节的顺序,即大端序(Big-Endian)和小端序(Little-Endian)。高位字节在前还是后会影响转换结果。比如,如果byte数组是[0x12, 0x34],大端序的WORD是0x1234,而小端序则是0x3412。用户的问题中可能需要明确这一点,或者提供两种情况的示例。 接下来,考虑代码示例。在Java中,可以使用ByteBuffer来处理字节数组到其他数据类型的转换。例如,使用ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getShort()来获得short类型的值(相当于WORD)。但Java的short是有符号的,可能需要处理符号问题,或者使用char类型(16位无符号)。 在C#中,可以使用BitConverter.ToUInt16方法,同样需要注意字节顺序。Python的话,可以用struct模块的unpack函数,指定格式如'H'表示16位无符号整数,并设置端序。 用户可能需要具体的示例代码,比如Java、C#或Python的实现。根据引用[3],Aspose.Words有处理字节数组的方法,但用户的问题更基础,不涉及文档处理,而是纯字节转换。 此外,用户提供的引用[4]涉及文件下载和HTTP头设置,可能与当前问题无关,但可能用户后续想将生成的WORD数据作为文件下载,不过当前问题集中在转换本身。 总结步骤:首先确定字节顺序,然后取两个字节,通过位移和或运算合并成WORD。需要处理数组长度是否为偶数,否则最后一个字节可能无法组成WORD。此外,需要注意数据类型是否有符号,可能需要使用无符号类型来正确表示WORD的值。 现在,编写示例代码。以Java为例,假设byte数组是big-endian,可以通过循环遍历数组,每次取两个字节,左移第一个字节8位,然后与第二个字节按位或。例如: byte[] byteArray = {0x12, 0x34, 0x56, 0x78}; int length = byteArray.length; for (int i = 0; i < length; i += 2) { int high = (byteArray[i] & 0xFF) << 8; int low = byteArray[i+1] & 0xFF; int word = high | low; // 存储到WORD数组中,可能需要处理符号扩展 } 但Java中需要处理byte的符号位,因此需要与0xFF进行按位与操作,转换为无符号值。然后组合成int类型的结果,因为Java没有无符号的short类型。如果用户需要的是short数组,可能需要强制转换,但要注意符号问题。 对于C#,可以使用BitConverter.ToUInt16,但需要确保字节顺序。例如: byte[] bytes = {0x12, 0x34}; ushort word = BitConverter.ToUInt16(bytes, 0); // 小端序 // 如果是大端序,可能需要反转数组 if (BitConverter.IsLittleEndian) { Array.Reverse(bytes); } Python中使用struct.unpack: import struct bytes = b'\x12\x34' word = struct.unpack('>H', bytes)[0] # 大端序 # '>'表示大端,'<'小端,'H'是16位无符号整数 此外,需要提醒用户关于数组长度是否为偶数,以及如何处理可能的截断情况。 最后,生成相关问题,可能涉及字节顺序的影响、处理奇数长度数组、不同编程语言的实现差异,以及实际应用场景如网络协议或文件格式解析。</think>要将BYTE数组转换为WORD数据,需理解计算机系统中字节序的存储方式,并通过位移运算实现数据重组。这里以16位无符号整数(WORD)为例说明转换方法,并提供Java和Python的代码示例。 ### 一、转换原理 1. **字节序概念** - **大端序(Big-Endian)**:高位字节存储在低地址 - **小端序(Little-Endian)**:低位字节存储在低地址 2. **数学表达式** 对于两个连续字节$[b_1,b_2]$的转换: $$word = (b_1 \ll 8) \ | \ b_2 \quad (\text{大端序})$$ $$word = (b_2 \ll 8) \ | \ b_1 \quad (\text{小端序})$$ ### 二、Java实现示例 ```java public static short[] bytesToWordsBE(byte[] byteArray) { int length = byteArray.length; short[] words = new short[length/2]; for (int i=0; i<length; i+=2) { int high = (byteArray[i] & 0xFF) << 8; // 消除符号扩展影响[^2] int low = byteArray[i+1] & 0xFF; words[i/2] = (short)(high | low); } return words; } ``` ### 三、Python实现示例 ```python import struct def bytes_to_words(bytes_data, endian='big'): format_str = '>' if endian == 'big' else '<' word_count = len(bytes_data) // 2 return struct.unpack(f'{format_str}{word_count}H', bytes_data[:word_count*2]) ``` ### 四、注意事项 1. **字节对齐**:当数组长度为奇数时,最后一个字节会被丢弃 2. **符号处理**:Java的short类型是有符号的,需注意0x8000-0xFFFF区间的负数表示 3. **内存映射**:直接内存操作效率更高,如Java的ByteBuffer类: ```java ByteBuffer.wrap(byteArray).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get(words); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值