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

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

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

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

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

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

如何建立缓冲?

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

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

  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. }

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

  1. byte[] bt = StreamTool.inputStreamToByte(ips);

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

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

  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. } else if (type == 2) {
  26. } else if (type == 3) {
  27. } else {
  28. System.out.println("未知的消息类型,解析结束!");
  29. // 清空缓存
  30. bbuf.clear();
  31. }
  32. // 如果字节流是多余一组数据则递归
  33. if (byten.length > length) {
  34. byte[] temp = new byte[bbuf.limit() - length];
  35. // 数组源,数组源拷贝的开始位子,目标,目标填写的开始位子,拷贝的长度
  36. System.arraycopy(byten, length, temp, 0, bbuf.limit() - length);
  37. // 情况缓存
  38. bbuf.clear();
  39. // 重新定义缓存
  40. bbuf.put(temp);
  41. // 递归回调
  42. splitByte(ops);
  43. }else if(byten.length == length){ // 如果只有一条数据,则直接重置缓冲就可以了
  44. // 清空缓存
  45. bbuf.clear();
  46. }
  47. } else {
  48. // 如果没有符合格式包含数据,则还原缓冲变量属性
  49. bbuf.position(p);
  50. bbuf.limit(l);
  51. }
  52. } catch (Exception e) {
  53. e.printStackTrace();
  54. }
  55. }

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

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

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

  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

 

【转】ByteBuffer 到底怎么用?网络编程中一点总结!--不错的更多相关文章

  1. Java网络编程中异步编程的理解

    目录 前言 一.异步,同步,阻塞和非阻塞的理解 二.异步编程从用户层面和框架层面不同角度的理解 用户角度的理解 框架角度的理解 三.为什么使用异步 四.理解这些能在实际中的应用 六.困惑 参考文章 前 ...

  2. [转帖]关于网络编程中MTU、TCP、UDP优化配置的一些总结

    关于网络编程中MTU.TCP.UDP优化配置的一些总结 https://www.cnblogs.com/maowang1991/archive/2013/04/15/3022955.html 感谢原作 ...

  3. 浅谈TCP/IP网络编程中socket的行为

    我认为,想要熟练掌握Linux下的TCP/IP网络编程,至少有三个层面的知识需要熟悉: 1. TCP/IP协议(如连接的建立和终止.重传和确认.滑动窗口和拥塞控制等等) 2. Socket I/O系统 ...

  4. socket网络编程中read与recv区别

    socket网络编程中read与recv区别 1.read 与 recv 区别 read 原则: 数据在不超过指定的长度的时候有多少读多少,没有数据则会一直等待.所以一般情况下:我们读取数据都需要采用 ...

  5. VC++学习之网络编程中的套接字

    VC++学习之网络编程中的套接字 套接字,简单的说就是通信双方的一种约定,用套接字中的相关函数来完成通信过程.应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问 ...

  6. 【Linux网络编程】TCP网络编程中connect()、listen()和accept()三者之间的关系

    [Linux网络编程]TCP网络编程中connect().listen()和accept()三者之间的关系 基于 TCP 的网络编程开发分为服务器端和客户端两部分,常见的核心步骤和流程如下: conn ...

  7. 网络编程中select模型和poll模型学习(linux)

    一.概述 并发的网络编程中不管是阻塞式IO还是非阻塞式IO,都不能很好的解决同时处理多个socket的问题.操作系统提供了复用IO模型:select和poll,帮助我们解决了这个问题.这两个函数都能够 ...

  8. 网络编程中的read,write函数

    关于TCP/IP协议,建议参考Richard Stevens的<TCP/IP Illustrated,vol1>(TCP/IP详解卷1). 关于第二层面,依然建议Richard Steve ...

  9. Unix网络编程中的五种I/O模型_转

    转自:Unix网络编程中的的五种I/O模型 下面主要是把unp第六章介绍的五种I/O模型. 1. 阻塞I/O模型 例如UDP函数recvfrom的内核到应用层.应用层到内核的调用过程是这样的:首先把描 ...

随机推荐

  1. SharePoint Dialog 使用

    SharePoint中弹出模态窗口对体验提高太大了 方法为: 父页面中调用子页面: function showDialog() {        var options = {             ...

  2. jQuery Ajax 实例 全解析

     jQuery确实是一个挺好的轻量级的JS框架,能帮助我们快速的开发JS应用,并在一定程度上改变了我们写JavaScript代码的习惯. 废话少说,直接进入正题,我们先来看一些简单的方法,这些方法都是 ...

  3. IXListView的自我分析一

    XListView是一个很不错的用来刷新和加载的控件,下拉刷新和上拉加载.目前这个控件已经没有更新,这个不重要,重要的是它确实还不错,之后可能一直有人在用. android没有提供原生的这类控件,需要 ...

  4. 1.redis.3.2 下载,安装、配置、使用 - 1

    1.下载:   2.使用: 挤压之后,使用cmd执行,如下图 redis-server--service-installredis.windows.conf,执行安装 提示成功之后,剩下就好办了, 这 ...

  5. CSS 边框

    CSS 边框属性 CSS边框属性允许你指定一个元素边框的样式和颜色. 边框样式 边框样式属性指定要显示什么样的边界.  border-style属性用来定义边框的样式 border-style 值: ...

  6. 关于for循环中的闭包问题

    还是昨天的那个简单的小项目,已经花了一天的时间了 - - .从&&的用法,到CSStext,到今天马上要谈的闭包(closure),通过一个小东西,真真发现了自己的各方面不足.昨天发完 ...

  7. 洛谷 U2878 小R的分数比赛(fraction)

    题目提供者 2015c07 标签 数论(数学相关) 高精度 难度 尚无评定 通过/提交 0/29 提交该题 记录 题目背景 P5难度系数:★★★☆☆ 小R再次挑战你. 这次的挑战又会是什么呢? 题目描 ...

  8. Angularjs简介

    很久没有系统学习一个新技术了,angularjs将会比较系统的讲解这个技术的语法.应用.次类型的博客将会持续更新,博主也是一个初学者,如果有问题欢迎留言讨论. angularjs简介. angular ...

  9. 获取Ip 的地域等信息接口-实例

    今天项目要用到 查询访问网站用户的IP 然后网上查询了 资料还很多 不过有些已经失效了 在这总结下 腾讯,pconline 的API已经失效 不能使用 淘宝的IP接口地址: http://ip.tao ...

  10. thinkphp路径引用问题

    查看ThinkPHP\Library\Behavior\ContentReplaceBehavior.class文件,常量定义如下定义: '__ROOT__'      =>  __ROOT__ ...