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. rsync命令 续集 、linux系统日志、screen工具

    1.rsync 通过服务进行监听同步: 开启服务:rsync --daemon  (默认开启873端口) 需要编辑配制文件:/etc/rsyncd.conf port=873log file=/var ...

  2. Thread_run()方法

    cas 1: package threadTest; public class ThreadTest { public static void main(String[] args) { Thread ...

  3. Socket远程桌面

    自建Socket转发,使用远程桌面(mstsc)连接家中电脑   网络结构图如下: 开题先放图,一切全靠编哈哈. 进入正题! 如图所示,我们需要一个公网服务器,利用公网服务器将内网的数据进行转发,从而 ...

  4. generator插件配置方式使用

    generator插件配置方式使用 <build> <plugins> <plugin> <groupId>org.mybatis.generator& ...

  5. hello1分析

    1:选择hello1文件夹并单击“打开项目”.展开“Web页”节点,然后双击该index.xhtml文件以在编辑器中查看它. 该index.xhtml文件是Facelets应用程序的默认登录页面.在典 ...

  6. python实现单链表的翻转

    #!/usr/bin/env python #coding = utf-8 class Node:     def __init__(self,data=None,next = None):      ...

  7. 从 Godaddy 转移域名到 Namesilo

    域名本来是在 Godaddy 上注册的,首付很便宜,但是续费时发现是个坑,续费一年是 102 元,再加上隐私保护 60元/年,总共一年需要 160 元,续费贵而且一点优惠也没. 对比下其他商家一年只要 ...

  8. 使用k8s && minio 进行 postgres 数据库自动备份

      通过k8s 的定时任务job,我们可以方便的进行定时任务应用的开发,通过minio s3 兼容的cloud native 存储 我们可以方便的通过http 请求进行数据文件的备份,以下简单演示下如 ...

  9. 为什么 PCB 生产时推荐出 Gerber 给工厂?

    为什么 PCB 生产时推荐出 Gerber 给工厂? 事情是这样的,有一天电工王工,画了一块 PCB,发给 PCB 板厂. 过了几天 PCB 回来了,一看不对呀,这里的丝印怎么少了,那里怎么多了几条线 ...

  10. .Net Core 应用方向 图谱

    .Net Core 应用方向 图谱,  如下图 : 大规模并行计算 是 大数据 和 人工智能 的 基础, 是 未来 大计算能力 的 基础, 网格计算 是 未来 大计算能力 的 一个 分支 . 所以, ...