NIO框架之MINA源码解析(四):粘包与断包处理及编码与解码
1、粘包与段包
粘包:指TCP协议中,发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
造成的可能原因:
发送端需要等缓冲区满才发送出去,造成粘包
接收方不及时接收缓冲区的包,造成多个包接收
断包:也就是数据不全,比如包太大,就把包分解成多个小包,多次发送,导致每次接收数据都不全。
2、消息传输的格式
消息长度+消息头+消息体 即前N个字节用于存储消息的长度,用于判断当前消息什么时候结束。
消息头+消息体 即固定长度的消息,前几个字节为消息头,后面的是消息头。
在MINA中用的是
消息长度+消息体 即前4个字节用于存储消息的长度,用于判断当前消息什么时候结束。
3、编码与解码
在网络中,信息的传输都是通过字节的形式传输的,而我们在编写自己的代码时,则都是具体的对象,那么要想我们的对象能够在网络中传输,就需要编码与解码。
编码:即把我们的消息编码成二进制形式,能以字节的形式在网络中传输。
解码:即把我们收到的字节解码成我们代码中的对象。
在MINA中对象的编码与解码用的都是JDK提供的ObjectOutputStream来实现的。
4、MINA中消息的处理实现
消息的接受处理,我们常用的是TCP协议,而TCP协议会分片的,在下面的代码中,具体功能就是循环从通道里面读取数据,直到没有数据可读,或者buffer满了,然后就把接受到的数据发给解码工厂进行处理。
4.1、消息的接收
- //class AbstractPollingIoProcessor
- private void read(S session) {
- IoSessionConfig config = session.getConfig();
- int bufferSize = config.getReadBufferSize();
- IoBuffer buf = IoBuffer.allocate(bufferSize);
- final boolean hasFragmentation = session.getTransportMetadata().hasFragmentation();
- try {
- int readBytes = 0;
- int ret;
- try {
- //是否有分片 tcp传输会有分片,即把大消息分片成多个小消息再传输
- if (hasFragmentation) {
- //read方法非阻塞,没有读到数据的时候返回0
- while ((ret = read(session, buf)) > 0) {
- readBytes += ret;
- //buffer 满了
- if (!buf.hasRemaining()) {
- break;
- }
- }
- } else {
- ret = read(session, buf);
- if (ret > 0) {
- readBytes = ret;
- }
- }
- } finally {
- buf.flip();
- }
- if (readBytes > 0) {
- IoFilterChain filterChain = session.getFilterChain();
- //处理消息
- filterChain.fireMessageReceived(buf);
- buf = null;
- if (hasFragmentation) {
- if (readBytes << 1 < config.getReadBufferSize()) {
- session.decreaseReadBufferSize();
- } else if (readBytes == config.getReadBufferSize()) {
- session.increaseReadBufferSize();
- }
- }
- }
- if (ret < 0) {
- scheduleRemove(session);
- }
- } catch (Throwable e) {
- if (e instanceof IOException) {
- if (!(e instanceof PortUnreachableException)
- || !AbstractDatagramSessionConfig.class.isAssignableFrom(config.getClass())
- || ((AbstractDatagramSessionConfig) config).isCloseOnPortUnreachable()) {
- scheduleRemove(session);
- }
- }
- IoFilterChain filterChain = session.getFilterChain();
- filterChain.fireExceptionCaught(e);
- }
- }
4.2、解码与编码
- //class AbstractIoBuffer
- public Object getObject(final ClassLoader classLoader) throws ClassNotFoundException {
- //首先判断当前buffer中消息长度是否完整,不完整的话直接返回
- if (!prefixedDataAvailable(4)) {
- throw new BufferUnderflowException();
- }
- //消息长度
- int length = getInt();
- if (length <= 4) {
- throw new BufferDataException("Object length should be greater than 4: " + length);
- }
- int oldLimit = limit();
- //limit到消息结尾处
- limit(position() + length);
- try {
- ObjectInputStream in = new ObjectInputStream(asInputStream()) {
- @Override
- protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
- int type = read();
- if (type < 0) {
- throw new EOFException();
- }
- switch (type) {
- case 0: // NON-Serializable class or Primitive types
- return super.readClassDescriptor();
- case 1: // Serializable class
- String className = readUTF();
- Class<?> clazz = Class.forName(className, true, classLoader);
- return ObjectStreamClass.lookup(clazz);
- default:
- throw new StreamCorruptedException("Unexpected class descriptor type: " + type);
- }
- }
- @Override
- protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
- String name = desc.getName();
- try {
- return Class.forName(name, false, classLoader);
- } catch (ClassNotFoundException ex) {
- return super.resolveClass(desc);
- }
- }
- };
- return in.readObject();
- } catch (IOException e) {
- throw new BufferDataException(e);
- } finally {
- limit(oldLimit);
- }
- }
- //判断当前消息是否完整
- public boolean prefixedDataAvailable(int prefixLength, int maxDataLength) {
- if (remaining() < prefixLength) {
- return false;
- }
- int dataLength;
- switch (prefixLength) {
- case 1:
- dataLength = getUnsigned(position());
- break;
- case 2:
- dataLength = getUnsignedShort(position());
- break;
- case 4:
- dataLength = getInt(position());
- break;
- default:
- throw new IllegalArgumentException("prefixLength: " + prefixLength);
- }
- if (dataLength < 0 || dataLength > maxDataLength) {
- throw new BufferDataException("dataLength: " + dataLength);
- }
- //判断当前消息是否完整
- return remaining() - prefixLength >= dataLength;
- }
- //编码
- public IoBuffer putObject(Object o) {
- int oldPos = position();
- skip(4); // Make a room for the length field.预留4个字节用于存储消息长度
- try {
- ObjectOutputStream out = new ObjectOutputStream(asOutputStream()) {
- @Override
- protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException {
- try {
- Class<?> clz = Class.forName(desc.getName());
- if (!Serializable.class.isAssignableFrom(clz)) { // NON-Serializable class
- write(0);
- super.writeClassDescriptor(desc);
- } else { // Serializable class
- write(1);
- writeUTF(desc.getName());
- }
- } catch (ClassNotFoundException ex) { // Primitive types
- write(0);
- super.writeClassDescriptor(desc);
- }
- }
- };
- out.writeObject(o);
- out.flush();
- } catch (IOException e) {
- throw new BufferDataException(e);
- }
- // Fill the length field
- int newPos = position();
- position(oldPos);
- //存储消息长度
- putInt(newPos - oldPos - 4);
- position(newPos);
- return this;
- }
4.3、断包与粘包处理
- // class CumulativeProtocolDecoder
- public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
- //是否有分片,tcp 有分片
- if (!session.getTransportMetadata().hasFragmentation()) {
- while (in.hasRemaining()) {
- if (!doDecode(session, in, out)) {
- break;
- }
- }
- return;
- }
- // 1、断包处理
- // 2、处理粘包
- boolean usingSessionBuffer = true;
- //session中是否有断包情况(上次处理后),断包保存在session中
- IoBuffer buf = (IoBuffer) session.getAttribute(BUFFER);
- // If we have a session buffer, append data to that; otherwise
- // use the buffer read from the network directly.
- if (buf != null) {//有断包,则把当前包拼接到断包里面
- boolean appended = false;
- // Make sure that the buffer is auto-expanded.
- if (buf.isAutoExpand()) {
- try {
- buf.put(in);
- appended = true;
- } catch (IllegalStateException e) {
- // A user called derivation method (e.g. slice()),
- // which disables auto-expansion of the parent buffer.
- } catch (IndexOutOfBoundsException e) {
- // A user disabled auto-expansion.
- }
- }
- if (appended) {
- buf.flip();
- } else {
- // Reallocate the buffer if append operation failed due to
- // derivation or disabled auto-expansion.
- buf.flip();
- IoBuffer newBuf = IoBuffer.allocate(buf.remaining() + in.remaining()).setAutoExpand(true);
- newBuf.order(buf.order());
- newBuf.put(buf);
- newBuf.put(in);
- newBuf.flip();
- buf = newBuf;
- // Update the session attribute.
- session.setAttribute(BUFFER, buf);
- }
- } else {
- buf = in;
- usingSessionBuffer = false;
- }
- //2 粘包处理,可能buffer中有多个消息,需要多次处理(解码)每个消息,直到消息处理完,或者剩下的消息不是一个完整的消息或者buffer没有数据了
- for (;;) {
- int oldPos = buf.position();
- boolean decoded = doDecode(session, buf, out);
- if (decoded) {//解码 成功
- if (buf.position() == oldPos) {
- throw new IllegalStateException("doDecode() can't return true when buffer is not consumed.");
- }
- //buffer空了
- if (!buf.hasRemaining()) {//buffer没有数据了
- break;
- }
- } else {//剩下的消息不是一个完整的消息,断包出现了
- break;
- }
- }
- // if there is any data left that cannot be decoded, we store
- // it in a buffer in the session and next time this decoder is
- // invoked the session buffer gets appended to
- if (buf.hasRemaining()) {//剩下的消息不是一个完整的消息,断包出现了
- //如果断包已经保存在session中,则更新buffer,没有的话,就把剩下的断包保存在session中
- if (usingSessionBuffer && buf.isAutoExpand()) {
- buf.compact();
- } else {
- storeRemainingInSession(buf, session);
- }
- } else {
- if (usingSessionBuffer) {
- removeSessionBuffer(session);
- }
- }
- }
- //class ObjectSerializationDecoder
- protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
- //首先判断当前buffer中消息长度是否完整,不完整的话直接返回
- if (!in.prefixedDataAvailable(4, maxObjectSize)) {
- return false;
- }
- out.write(in.getObject(classLoader));
- return true;
- }
NIO框架之MINA源码解析(四):粘包与断包处理及编码与解码的更多相关文章
- NIO框架之MINA源码解析(五):NIO超级陷阱和使用同步IO与MINA通信
1.NIO超级陷阱 之所以说NIO超级陷阱,就是因为我在本系列开头的那句话,因为使用缺陷导致客户业务系统瘫痪.当然,我对这个问题进行了很深的追踪,包括对MINA源码的深入了解,但其实之所以会出现这个问 ...
- NIO框架之MINA源码解析(转)
http://blog.csdn.net/column/details/nio-mina-source.html http://blog.csdn.net/chaofanwei/article/det ...
- Mybatis源码解析(四) —— SqlSession是如何实现数据库操作的?
Mybatis源码解析(四) -- SqlSession是如何实现数据库操作的? 如果拿一次数据库请求操作做比喻,那么前面3篇文章就是在做请求准备,真正执行操作的是本篇文章要讲述的内容.正如标题一 ...
- Sentinel源码解析四(流控策略和流控效果)
引言 在分析Sentinel的上一篇文章中,我们知道了它是基于滑动窗口做的流量统计,那么在当我们能够根据流量统计算法拿到流量的实时数据后,下一步要做的事情自然就是基于这些数据做流控.在介绍Sentin ...
- Dubbo 源码解析四 —— 负载均衡LoadBalance
欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 Dubbo 入门之二 --- 项目结构解析 Dubbo 源码分析系列之三 -- 架构原 ...
- iOS即时通讯之CocoaAsyncSocket源码解析四
原文 前言: 本文为CocoaAsyncSocket源码系列中第二篇:Read篇,将重点涉及该框架是如何利用缓冲区对数据进行读取.以及各种情况下的数据包处理,其中还包括普通的.和基于TLS的不同读取操 ...
- React的React.createContext()源码解析(四)
一.产生context原因 从父组件直接传值到孙子组件,而不必一层一层的通过props进行传值,相比较以前的那种传值更加的方便.简介. 二.context的两种实现方式 1.老版本(React16.x ...
- Mybaits 源码解析 (十)----- 全网最详细,没有之一:Spring-Mybatis框架使用与源码解析
在前面几篇文章中我们主要分析了Mybatis的单独使用,在实际在常规项目开发中,大部分都会使用mybatis与Spring结合起来使用,毕竟现在不用Spring开发的项目实在太少了.本篇文章便来介绍下 ...
- AFNetworking2.0源码解析<四>
结构 AFURLResponseSerialization负责解析网络返回数据,检查数据是否合法,把NSData数据转成相应的对象,内置的转换器有json,xml,plist,image,用户可以很方 ...
随机推荐
- 限定某个目录禁止解析php 、限制user_agent 、php的配制文件、PHP的动态扩展模块
1. 限定某个目录禁止解析php(有些目录用户可以上传文件或图片,可能会被恶意者上传其它文件):编辑:/usr/local/apache2.4/conf/extra/httpd-vhosts.conf ...
- 一台机器上安装两个tomcat
1.使用压缩版的tomcat不能使用安装版的. 2.第一个tomcat的配置不变. 3.增加环境变量CATALINA_HOME2,值为新的tomcat的地址:增加环境变量CATALINA_BASE ...
- Python学习手册
基础 概念 源码编译为字节码,解释器解释字节码 CPython是python标准实现方式,Jython将源码编译为java字节码,运行在JVM上 优点:快速开发,灵活的核心数据类型,优美的缩进语法,垃 ...
- crontab 例子
一个简单的 crontab 示例 0,20,40 22-23 * 7 fri-sat /home/ian/mycrontest.sh 在这个示例中,我们的命令在 7 月的每个星期五和星期六晚上 10 ...
- PADS Logic Ref Des Start Value
PADS Logic Ref Des Start Value 曾经以为 Mentor 把 PADS Logic 放弃了,没想到还加了一个非常实用的功能. 这个的好处是让元件号可以设置起始编号,方便每一 ...
- Lambda 表达式 是 个 好东东
Lambda 表达式 是 个 好东东 首先,通过 Lambda 表达式 + 动态语言特性 dynamic , C# 已经 可以 实现 函数式 编程 了 其次, 利用 Lambda, 可以 实现 AOP ...
- html5 视频播放插件
HTML5中加入了浏览器非常友好的标签 <video> ,这个标签非常的厉害,它可以不依靠falsh播放器,在网页中播放视频,目前W3C提供的video只支持mp4,ogg,webm三种视 ...
- webpack 4 知识点
相应Github地址:https://github.com/cag2050/webpack4_demo css-loader 让我们能在javascript代码中导入css文件,但这还不能让css起作 ...
- Jenkins进阶-部署Web项目到远程tomcat(7)
之前讲到的是如何构建一个项目,并且将代码进行编译.打包,那么打包完成最后的结果就需要发布到应用服务器,将项目部署成功.在之前的项目中我们采用的shell脚本来部署,下面讲解通过Jenkins部署web ...
- bzoj 4349 最小树形图——朱刘算法
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4349. 学习博客:http://www.cnblogs.com/xzxl/p/7243466 ...