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,用户可以很方 ...
随机推荐
- Hive错误:User root is not allowed to impersonate anonymous
修改hadoop 配置文件 etc/hadoop/core-site.xml,加入如下配置项: <property> <name>hadoop.proxyuser.root.h ...
- 使用JQuery提交表单的两种方式选择
有一个表单,如果使用JQuery提交的话,可以使用下面2中方式,但他们的区别却是根据实际需求需要进行选择的. 第一种:表单按照action路径提交后,页面会刷新. $("#id") ...
- Go Example--通道选择器
package main import ( "fmt" "time" ) func main() { c1 := make(chan string) c2 := ...
- day 05JVM和深入理解java虚拟机
-----------------Java 虚拟机发展史 PS: Sun公司有 HotSpot, BEA公司有JRockit,IBM有 J9 这三个是高性能VM 在Oracle收购Sun和BEA这两 ...
- Hibernate(二)
性能分析 抓取策略 研究对象 研究怎么样提取集合的,该策略应该作用与set元素上 研究从一的一方加载多的一方 案例 查询cid为1的班级的所有的学生 明:通过一条sql语句:左外链接,把classes ...
- hive 分隔符替换
Hive建表的时候虽然可以指定字段分隔符,不过用insert overwrite local directory这种方式导出文件时,字段的分割符会被默认置为\001,一般都需要将字段分隔符转换为其它字 ...
- Linux内核Inotify机制学习笔记
一.Inotify简介: Inotify是一种文件变化通知机制,Linux内核从2.6.13开始引入.它是一个内核用于通知用户空间程序文件系统变化的机制.开源社区提出用户态需要内核提供一些机制,以便用 ...
- HDU2220 Eddy's AC难题
版权声明:长风原创 https://blog.csdn.net/u012846486/article/details/27853287 Eddy's AC难题 Time Limit: 3000/100 ...
- Unity API 解析 (陈泉宏著)
1 Application类 2 Camera类 3 GameObject类 4 HideFlags类 5 Mathf类 6 Matrix4x4类 7 Object类 8 Quaternion类 9 ...
- Python time & datetime模块
time 模块 时间分为三种格式: 时间戳:表示1970年1月1日之后的秒 结构化时间:元组包含了:年.日.星期等... 格式化字符串:格式可以自定义 时间戳: import time time_st ...