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,用户可以很方 ...
随机推荐
- rsync命令 续集 、linux系统日志、screen工具
1.rsync 通过服务进行监听同步: 开启服务:rsync --daemon (默认开启873端口) 需要编辑配制文件:/etc/rsyncd.conf port=873log file=/var ...
- Thread_run()方法
cas 1: package threadTest; public class ThreadTest { public static void main(String[] args) { Thread ...
- Socket远程桌面
自建Socket转发,使用远程桌面(mstsc)连接家中电脑 网络结构图如下: 开题先放图,一切全靠编哈哈. 进入正题! 如图所示,我们需要一个公网服务器,利用公网服务器将内网的数据进行转发,从而 ...
- generator插件配置方式使用
generator插件配置方式使用 <build> <plugins> <plugin> <groupId>org.mybatis.generator& ...
- hello1分析
1:选择hello1文件夹并单击“打开项目”.展开“Web页”节点,然后双击该index.xhtml文件以在编辑器中查看它. 该index.xhtml文件是Facelets应用程序的默认登录页面.在典 ...
- python实现单链表的翻转
#!/usr/bin/env python #coding = utf-8 class Node: def __init__(self,data=None,next = None): ...
- 从 Godaddy 转移域名到 Namesilo
域名本来是在 Godaddy 上注册的,首付很便宜,但是续费时发现是个坑,续费一年是 102 元,再加上隐私保护 60元/年,总共一年需要 160 元,续费贵而且一点优惠也没. 对比下其他商家一年只要 ...
- 使用k8s && minio 进行 postgres 数据库自动备份
通过k8s 的定时任务job,我们可以方便的进行定时任务应用的开发,通过minio s3 兼容的cloud native 存储 我们可以方便的通过http 请求进行数据文件的备份,以下简单演示下如 ...
- 为什么 PCB 生产时推荐出 Gerber 给工厂?
为什么 PCB 生产时推荐出 Gerber 给工厂? 事情是这样的,有一天电工王工,画了一块 PCB,发给 PCB 板厂. 过了几天 PCB 回来了,一看不对呀,这里的丝印怎么少了,那里怎么多了几条线 ...
- .Net Core 应用方向 图谱
.Net Core 应用方向 图谱, 如下图 : 大规模并行计算 是 大数据 和 人工智能 的 基础, 是 未来 大计算能力 的 基础, 网格计算 是 未来 大计算能力 的 一个 分支 . 所以, ...