1、粘包与段包

粘包:指TCP协议中,发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
造成的可能原因:

发送端需要等缓冲区满才发送出去,造成粘包

接收方不及时接收缓冲区的包,造成多个包接收

断包:也就是数据不全,比如包太大,就把包分解成多个小包,多次发送,导致每次接收数据都不全。

2、消息传输的格式

消息长度+消息头+消息体  即前N个字节用于存储消息的长度,用于判断当前消息什么时候结束。

消息头+消息体    即固定长度的消息,前几个字节为消息头,后面的是消息头。

在MINA中用的是

消息长度+消息体 即前4个字节用于存储消息的长度,用于判断当前消息什么时候结束。

3、编码与解码

在网络中,信息的传输都是通过字节的形式传输的,而我们在编写自己的代码时,则都是具体的对象,那么要想我们的对象能够在网络中传输,就需要编码与解码。

编码:即把我们的消息编码成二进制形式,能以字节的形式在网络中传输。

解码:即把我们收到的字节解码成我们代码中的对象。

在MINA中对象的编码与解码用的都是JDK提供的ObjectOutputStream来实现的。

4、MINA中消息的处理实现

消息的接受处理,我们常用的是TCP协议,而TCP协议会分片的,在下面的代码中,具体功能就是循环从通道里面读取数据,直到没有数据可读,或者buffer满了,然后就把接受到的数据发给解码工厂进行处理。

4.1、消息的接收

  1. //class AbstractPollingIoProcessor
  2. private void read(S session) {
  3. IoSessionConfig config = session.getConfig();
  4. int bufferSize = config.getReadBufferSize();
  5. IoBuffer buf = IoBuffer.allocate(bufferSize);
  6. final boolean hasFragmentation = session.getTransportMetadata().hasFragmentation();
  7. try {
  8. int readBytes = 0;
  9. int ret;
  10. try {
  11. //是否有分片 tcp传输会有分片,即把大消息分片成多个小消息再传输
  12. if (hasFragmentation) {
  13. //read方法非阻塞,没有读到数据的时候返回0
  14. while ((ret = read(session, buf)) > 0) {
  15. readBytes += ret;
  16. //buffer 满了
  17. if (!buf.hasRemaining()) {
  18. break;
  19. }
  20. }
  21. } else {
  22. ret = read(session, buf);
  23. if (ret > 0) {
  24. readBytes = ret;
  25. }
  26. }
  27. } finally {
  28. buf.flip();
  29. }
  30. if (readBytes > 0) {
  31. IoFilterChain filterChain = session.getFilterChain();
  32. //处理消息
  33. filterChain.fireMessageReceived(buf);
  34. buf = null;
  35. if (hasFragmentation) {
  36. if (readBytes << 1 < config.getReadBufferSize()) {
  37. session.decreaseReadBufferSize();
  38. } else if (readBytes == config.getReadBufferSize()) {
  39. session.increaseReadBufferSize();
  40. }
  41. }
  42. }
  43. if (ret < 0) {
  44. scheduleRemove(session);
  45. }
  46. } catch (Throwable e) {
  47. if (e instanceof IOException) {
  48. if (!(e instanceof PortUnreachableException)
  49. || !AbstractDatagramSessionConfig.class.isAssignableFrom(config.getClass())
  50. || ((AbstractDatagramSessionConfig) config).isCloseOnPortUnreachable()) {
  51. scheduleRemove(session);
  52. }
  53. }
  54. IoFilterChain filterChain = session.getFilterChain();
  55. filterChain.fireExceptionCaught(e);
  56. }
  57. }

4.2、解码与编码

  1. //class AbstractIoBuffer
  2. public Object getObject(final ClassLoader classLoader) throws ClassNotFoundException {
  3. //首先判断当前buffer中消息长度是否完整,不完整的话直接返回
  4. if (!prefixedDataAvailable(4)) {
  5. throw new BufferUnderflowException();
  6. }
  7. //消息长度
  8. int length = getInt();
  9. if (length <= 4) {
  10. throw new BufferDataException("Object length should be greater than 4: " + length);
  11. }
  12. int oldLimit = limit();
  13. //limit到消息结尾处
  14. limit(position() + length);
  15. try {
  16. ObjectInputStream in = new ObjectInputStream(asInputStream()) {
  17. @Override
  18. protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
  19. int type = read();
  20. if (type < 0) {
  21. throw new EOFException();
  22. }
  23. switch (type) {
  24. case 0: // NON-Serializable class or Primitive types
  25. return super.readClassDescriptor();
  26. case 1: // Serializable class
  27. String className = readUTF();
  28. Class<?> clazz = Class.forName(className, true, classLoader);
  29. return ObjectStreamClass.lookup(clazz);
  30. default:
  31. throw new StreamCorruptedException("Unexpected class descriptor type: " + type);
  32. }
  33. }
  34. @Override
  35. protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
  36. String name = desc.getName();
  37. try {
  38. return Class.forName(name, false, classLoader);
  39. } catch (ClassNotFoundException ex) {
  40. return super.resolveClass(desc);
  41. }
  42. }
  43. };
  44. return in.readObject();
  45. } catch (IOException e) {
  46. throw new BufferDataException(e);
  47. } finally {
  48. limit(oldLimit);
  49. }
  50. }
  51. //判断当前消息是否完整
  52. public boolean prefixedDataAvailable(int prefixLength, int maxDataLength) {
  53. if (remaining() < prefixLength) {
  54. return false;
  55. }
  56. int dataLength;
  57. switch (prefixLength) {
  58. case 1:
  59. dataLength = getUnsigned(position());
  60. break;
  61. case 2:
  62. dataLength = getUnsignedShort(position());
  63. break;
  64. case 4:
  65. dataLength = getInt(position());
  66. break;
  67. default:
  68. throw new IllegalArgumentException("prefixLength: " + prefixLength);
  69. }
  70. if (dataLength < 0 || dataLength > maxDataLength) {
  71. throw new BufferDataException("dataLength: " + dataLength);
  72. }
  73. //判断当前消息是否完整
  74. return remaining() - prefixLength >= dataLength;
  75. }
  76. //编码
  77. public IoBuffer putObject(Object o) {
  78. int oldPos = position();
  79. skip(4); // Make a room for the length field.预留4个字节用于存储消息长度
  80. try {
  81. ObjectOutputStream out = new ObjectOutputStream(asOutputStream()) {
  82. @Override
  83. protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException {
  84. try {
  85. Class<?> clz = Class.forName(desc.getName());
  86. if (!Serializable.class.isAssignableFrom(clz)) { // NON-Serializable class
  87. write(0);
  88. super.writeClassDescriptor(desc);
  89. } else { // Serializable class
  90. write(1);
  91. writeUTF(desc.getName());
  92. }
  93. } catch (ClassNotFoundException ex) { // Primitive types
  94. write(0);
  95. super.writeClassDescriptor(desc);
  96. }
  97. }
  98. };
  99. out.writeObject(o);
  100. out.flush();
  101. } catch (IOException e) {
  102. throw new BufferDataException(e);
  103. }
  104. // Fill the length field
  105. int newPos = position();
  106. position(oldPos);
  107. //存储消息长度
  108. putInt(newPos - oldPos - 4);
  109. position(newPos);
  110. return this;
  111. }

4.3、断包与粘包处理

  1. // class CumulativeProtocolDecoder
  2. public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
  3. //是否有分片,tcp 有分片
  4. if (!session.getTransportMetadata().hasFragmentation()) {
  5. while (in.hasRemaining()) {
  6. if (!doDecode(session, in, out)) {
  7. break;
  8. }
  9. }
  10. return;
  11. }
  12. // 1、断包处理
  13. // 2、处理粘包
  14. boolean usingSessionBuffer = true;
  15. //session中是否有断包情况(上次处理后),断包保存在session中
  16. IoBuffer buf = (IoBuffer) session.getAttribute(BUFFER);
  17. // If we have a session buffer, append data to that; otherwise
  18. // use the buffer read from the network directly.
  19. if (buf != null) {//有断包,则把当前包拼接到断包里面
  20. boolean appended = false;
  21. // Make sure that the buffer is auto-expanded.
  22. if (buf.isAutoExpand()) {
  23. try {
  24. buf.put(in);
  25. appended = true;
  26. } catch (IllegalStateException e) {
  27. // A user called derivation method (e.g. slice()),
  28. // which disables auto-expansion of the parent buffer.
  29. } catch (IndexOutOfBoundsException e) {
  30. // A user disabled auto-expansion.
  31. }
  32. }
  33. if (appended) {
  34. buf.flip();
  35. } else {
  36. // Reallocate the buffer if append operation failed due to
  37. // derivation or disabled auto-expansion.
  38. buf.flip();
  39. IoBuffer newBuf = IoBuffer.allocate(buf.remaining() + in.remaining()).setAutoExpand(true);
  40. newBuf.order(buf.order());
  41. newBuf.put(buf);
  42. newBuf.put(in);
  43. newBuf.flip();
  44. buf = newBuf;
  45. // Update the session attribute.
  46. session.setAttribute(BUFFER, buf);
  47. }
  48. } else {
  49. buf = in;
  50. usingSessionBuffer = false;
  51. }
  52. //2 粘包处理,可能buffer中有多个消息,需要多次处理(解码)每个消息,直到消息处理完,或者剩下的消息不是一个完整的消息或者buffer没有数据了
  53. for (;;) {
  54. int oldPos = buf.position();
  55. boolean decoded = doDecode(session, buf, out);
  56. if (decoded) {//解码 成功
  57. if (buf.position() == oldPos) {
  58. throw new IllegalStateException("doDecode() can't return true when buffer is not consumed.");
  59. }
  60. //buffer空了
  61. if (!buf.hasRemaining()) {//buffer没有数据了
  62. break;
  63. }
  64. } else {//剩下的消息不是一个完整的消息,断包出现了
  65. break;
  66. }
  67. }
  68. // if there is any data left that cannot be decoded, we store
  69. // it in a buffer in the session and next time this decoder is
  70. // invoked the session buffer gets appended to
  71. if (buf.hasRemaining()) {//剩下的消息不是一个完整的消息,断包出现了
  72. //如果断包已经保存在session中,则更新buffer,没有的话,就把剩下的断包保存在session中
  73. if (usingSessionBuffer && buf.isAutoExpand()) {
  74. buf.compact();
  75. } else {
  76. storeRemainingInSession(buf, session);
  77. }
  78. } else {
  79. if (usingSessionBuffer) {
  80. removeSessionBuffer(session);
  81. }
  82. }
  83. }
    1. //class  ObjectSerializationDecoder
    2. protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
    3. //首先判断当前buffer中消息长度是否完整,不完整的话直接返回
    4. if (!in.prefixedDataAvailable(4, maxObjectSize)) {
    5. return false;
    6. }
    7. out.write(in.getObject(classLoader));
    8. return true;
    9. }

NIO框架之MINA源码解析(四):粘包与断包处理及编码与解码的更多相关文章

  1. NIO框架之MINA源码解析(五):NIO超级陷阱和使用同步IO与MINA通信

    1.NIO超级陷阱 之所以说NIO超级陷阱,就是因为我在本系列开头的那句话,因为使用缺陷导致客户业务系统瘫痪.当然,我对这个问题进行了很深的追踪,包括对MINA源码的深入了解,但其实之所以会出现这个问 ...

  2. NIO框架之MINA源码解析(转)

    http://blog.csdn.net/column/details/nio-mina-source.html http://blog.csdn.net/chaofanwei/article/det ...

  3. Mybatis源码解析(四) —— SqlSession是如何实现数据库操作的?

    Mybatis源码解析(四) -- SqlSession是如何实现数据库操作的?   如果拿一次数据库请求操作做比喻,那么前面3篇文章就是在做请求准备,真正执行操作的是本篇文章要讲述的内容.正如标题一 ...

  4. Sentinel源码解析四(流控策略和流控效果)

    引言 在分析Sentinel的上一篇文章中,我们知道了它是基于滑动窗口做的流量统计,那么在当我们能够根据流量统计算法拿到流量的实时数据后,下一步要做的事情自然就是基于这些数据做流控.在介绍Sentin ...

  5. Dubbo 源码解析四 —— 负载均衡LoadBalance

    欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 Dubbo 入门之二 --- 项目结构解析 Dubbo 源码分析系列之三 -- 架构原 ...

  6. iOS即时通讯之CocoaAsyncSocket源码解析四

    原文 前言: 本文为CocoaAsyncSocket源码系列中第二篇:Read篇,将重点涉及该框架是如何利用缓冲区对数据进行读取.以及各种情况下的数据包处理,其中还包括普通的.和基于TLS的不同读取操 ...

  7. React的React.createContext()源码解析(四)

    一.产生context原因 从父组件直接传值到孙子组件,而不必一层一层的通过props进行传值,相比较以前的那种传值更加的方便.简介. 二.context的两种实现方式 1.老版本(React16.x ...

  8. Mybaits 源码解析 (十)----- 全网最详细,没有之一:Spring-Mybatis框架使用与源码解析

    在前面几篇文章中我们主要分析了Mybatis的单独使用,在实际在常规项目开发中,大部分都会使用mybatis与Spring结合起来使用,毕竟现在不用Spring开发的项目实在太少了.本篇文章便来介绍下 ...

  9. AFNetworking2.0源码解析<四>

    结构 AFURLResponseSerialization负责解析网络返回数据,检查数据是否合法,把NSData数据转成相应的对象,内置的转换器有json,xml,plist,image,用户可以很方 ...

随机推荐

  1. 限定某个目录禁止解析php 、限制user_agent 、php的配制文件、PHP的动态扩展模块

    1. 限定某个目录禁止解析php(有些目录用户可以上传文件或图片,可能会被恶意者上传其它文件):编辑:/usr/local/apache2.4/conf/extra/httpd-vhosts.conf ...

  2. 一台机器上安装两个tomcat

    1.使用压缩版的tomcat不能使用安装版的.  2.第一个tomcat的配置不变.  3.增加环境变量CATALINA_HOME2,值为新的tomcat的地址:增加环境变量CATALINA_BASE ...

  3. Python学习手册

    基础 概念 源码编译为字节码,解释器解释字节码 CPython是python标准实现方式,Jython将源码编译为java字节码,运行在JVM上 优点:快速开发,灵活的核心数据类型,优美的缩进语法,垃 ...

  4. crontab 例子

    一个简单的 crontab 示例 0,20,40 22-23 * 7 fri-sat /home/ian/mycrontest.sh 在这个示例中,我们的命令在 7 月的每个星期五和星期六晚上 10 ...

  5. PADS Logic Ref Des Start Value

    PADS Logic Ref Des Start Value 曾经以为 Mentor 把 PADS Logic 放弃了,没想到还加了一个非常实用的功能. 这个的好处是让元件号可以设置起始编号,方便每一 ...

  6. Lambda 表达式 是 个 好东东

    Lambda 表达式 是 个 好东东 首先,通过 Lambda 表达式 + 动态语言特性 dynamic , C# 已经 可以 实现 函数式 编程 了 其次, 利用 Lambda, 可以 实现 AOP ...

  7. html5 视频播放插件

    HTML5中加入了浏览器非常友好的标签 <video> ,这个标签非常的厉害,它可以不依靠falsh播放器,在网页中播放视频,目前W3C提供的video只支持mp4,ogg,webm三种视 ...

  8. webpack 4 知识点

    相应Github地址:https://github.com/cag2050/webpack4_demo css-loader 让我们能在javascript代码中导入css文件,但这还不能让css起作 ...

  9. Jenkins进阶-部署Web项目到远程tomcat(7)

    之前讲到的是如何构建一个项目,并且将代码进行编译.打包,那么打包完成最后的结果就需要发布到应用服务器,将项目部署成功.在之前的项目中我们采用的shell脚本来部署,下面讲解通过Jenkins部署web ...

  10. bzoj 4349 最小树形图——朱刘算法

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4349. 学习博客:http://www.cnblogs.com/xzxl/p/7243466 ...