MINA框架中的编码解码以及对粘包断包的处理

        我们都知道MINA中是使用责任链的方式来实现将二进制字节流数据转换为java对象,或者将java对象转换为二进制字节流数据的,那么这个转换过程到底是怎么进行的呢?这就涉及到MINA中的编码与解码问题了;
        我们先来看看解码过程:

        当服务端读取客户端发送过来的消息时,会执行AbstractPollingIoProcessor里面的read方法,因为之前在我们对MINA的源码分析中就已经知道了对于每个Session的请求来说,实际上的执行者是IoProcessor对象,在read方法中会获取到当前IoSession所对应的IoFilter责任链,接着就会调用责任链的fireMessageReceived方法,而在fireMessageReceived里面又会调用callNextMessageReceived方法,在callNextMessageReceived方法里面就会执行IoFilter的messageReceived方法了,这个方法的真正实现是在ProtocolCodecFilter里面的,我们来看看他的具体实现:

        ProtocolCodecFilter$messageReceived()

public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception {
        LOGGER.debug("Processing a MESSAGE_RECEIVED for session {}", session.getId());

        if (!(message instanceof IoBuffer)) {
            nextFilter.messageReceived(session, message);
            return;
        }

        IoBuffer in = (IoBuffer) message;
        ProtocolDecoder decoder = factory.getDecoder(session);
        ProtocolDecoderOutput decoderOut = getDecoderOut(session, nextFilter);

        // Loop until we don't have anymore byte in the buffer,
        // or until the decoder throws an unrecoverable exception or
        // can't decoder a message, because there are not enough
        // data in the buffer
        while (in.hasRemaining()) {
            int oldPos = in.position();
            try {
                synchronized (session) {
                    // Call the decoder with the read bytes
                    decoder.decode(session, in, decoderOut);
                }
                // Finish decoding if no exception was thrown.
                decoderOut.flush(nextFilter, session);
            } catch (Exception e) {
                ProtocolDecoderException pde;
                if (e instanceof ProtocolDecoderException) {
                    pde = (ProtocolDecoderException) e;
                } else {
                    pde = new ProtocolDecoderException(e);
                }
                if (pde.getHexdump() == null) {
                    // Generate a message hex dump
                    int curPos = in.position();
                    in.position(oldPos);
                    pde.setHexdump(in.getHexDump());
                    in.position(curPos);
                }
                // Fire the exceptionCaught event.
                decoderOut.flush(nextFilter, session);
                nextFilter.exceptionCaught(session, pde);
                // Retry only if the type of the caught exception is
                // recoverable and the buffer position has changed.
                // We check buffer position additionally to prevent an
                // infinite loop.
                if (!(e instanceof RecoverableProtocolDecoderException) || (in.position() == oldPos)) {
                    break;
                }
            }
        }
    }

        这个方法第4行首先判断当前的message是不是IoBuffer,如果不是的话,则会调用nextFilter的messageReceived,由下一个IoFilter进行处理,同时结束当前IoFilter处理,这里的nextFilter是EntryImpl里面的一个对象,他是DefaultIoFilterChain里面的一个内部类,我们来看看他里面的messageReceived方法:

        DefaultIoFilterChain#EntryImpl

public void messageReceived(IoSession session, Object message) {
                    Entry nextEntry = EntryImpl.this.nextEntry;
                    callNextMessageReceived(nextEntry, session, message);
                }
        可以看到它实际上调用的是 callNextMessageReceived方法,而 callNextMessageReceived方法又会调用 IoFilter的messageReceived方法,继续判断当前的message对象是不是IoBuffer,直到是为止,相当于是一个间接的递归操作了;

        如果当前message是IoBuffer的话,则在第10行通过编解码器工厂获取到我们的解码器ProtocolDecoder,我们可以通过实现ProtocolDecoder接口来实现自己的解码器,第17行判断当前IoBuffer中是否存在需要被解码的内容,存在的话执行第22行解码器的decode方法,如果解码器是我们自己实现的话,那么这个方法将是我们自己实现的,并且在我们自己实现的decode方法将该消息解码出来之后,会调用ProtocolDecoderOutput的write方法,将解码出来的信息添加到队列中去:

        AbstractProtocolDecoderOutput$write()

public void write(Object message) {
        if (message == null) {
            throw new IllegalArgumentException("message");
        }

        messageQueue.add(message);
    }
        在执行完write方法之后 ProtocolCodecFilter$messageReceived()的25行执行了flush方法,这个方法会将当前已经解析好的消息传递下去,由下面的IoFilter来进行处理,flush方法的具体实现是在ProtocolCodecFilter的静态内部类ProtocolDecoderOutputImpl里面的:

        ProtocolDecoderOutputImpl$flush

public void flush(NextFilter nextFilter, IoSession session) {
            Queue<Object> messageQueue = getMessageQueue();

            while (!messageQueue.isEmpty()) {
                nextFilter.messageReceived(session, messageQueue.poll());
            }
        }
        其实执行的还是nextFilter的messageReceived方法,这样一直执行下去,直到到达责任链的尾部TailFilter,执行TailFilter的
messageReceived,实际上就是执行IoHandler的 messageReceived方法了,在这个方法里面进行我们业务消息的处理就可以了;

        这就是解码过程,那么编码过程是什么样子的呢?实际上就是逆向的解码过程了:

        我们知道,客户端想要通过MINA向服务端传递数据的话,首先需要获得IoSession对象,执行他的write方法,具体实现是在AbstractIoSession里面实现的,在write方法中获得当前IoSession对应的IoFilter链,并且调用IoFilter链的fireFilterWrite方法,实际上执行的是DefaultIoFilterChain里面的fireFilterWrite方法,在fireFilterWrite方法里面会执行callPreviousFilterWrite方法,在callPreviousFilterWrite里面就会执行IoFilter的filterWrite方法了,这个方法就是我们要讲的重点了,他的实现是在ProtocolCodecFilter里面的:

        ProtocolCodecFilter$filterWrite

public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception {
        Object message = writeRequest.getMessage();

        // Bypass the encoding if the message is contained in a IoBuffer,
        // as it has already been encoded before
        if ((message instanceof IoBuffer) || (message instanceof FileRegion)) {
            nextFilter.filterWrite(session, writeRequest);
            return;
        }

        // Get the encoder in the session
        ProtocolEncoder encoder = factory.getEncoder(session);

        ProtocolEncoderOutput encoderOut = getEncoderOut(session, nextFilter, writeRequest);

        if (encoder == null) {
            throw new ProtocolEncoderException("The encoder is null for the session " + session);
        }

        try {
            // Now we can try to encode the response
            encoder.encode(session, message, encoderOut);

            // Send it directly
            Queue<Object> bufferQueue = ((AbstractProtocolEncoderOutput) encoderOut).getMessageQueue();

            // Write all the encoded messages now
            while (!bufferQueue.isEmpty()) {
                Object encodedMessage = bufferQueue.poll();

                if (encodedMessage == null) {
                    break;
                }

                // Flush only when the buffer has remaining.
                if (!(encodedMessage instanceof IoBuffer) || ((IoBuffer) encodedMessage).hasRemaining()) {
                    SocketAddress destination = writeRequest.getDestination();
                    WriteRequest encodedWriteRequest = new EncodedWriteRequest(encodedMessage, null, destination);

                    nextFilter.filterWrite(session, encodedWriteRequest);
                }
            }

            // Call the next filter
            nextFilter.filterWrite(session, new MessageWriteRequest(writeRequest));
        } catch (Exception e) {
            ProtocolEncoderException pee;<pre name="code" class="java">public void filterWrite(IoSession session, WriteRequest writeRequest) {
                    Entry nextEntry = EntryImpl.this.prevEntry;
                    callPreviousFilterWrite(nextEntry, session, writeRequest);
                }
<span style="font-family:Comic Sans MS;">}</span>

         和解码一样,编码过程首先也是判断当前message是否是 
IoBuffer,如果不是的话,则直接调用nextFilter的filterWrite方法,这里的 
filterWrite实际上调用的是 
DefaultIoFilterChain内部类EntryImpl中的filterWrite方法; 

       DefaultIoFilterChain$EntryImpl

public void filterWrite(IoSession session, WriteRequest writeRequest) {
                    Entry nextEntry = EntryImpl.this.prevEntry;
                    callPreviousFilterWrite(nextEntry, session, writeRequest);
                }
        可以看到这个方法实际上执行的是callPreviousFilterWrite方法,而在callPreviousFilterWrite方法里面又会去执行IoFilter的filterWrite方法,这样子形成了间接递归操作,直到当前message是IoBuffer对象为止,继续向下执行第12行获取到实现ProtocolEncoder接口的编码器,第22行调用编码器的encode方法进行编码,这个方法可以是我们自己实现编码器中的encode方法,我们会在自己实现的encode方法里面调用ProtocolEncoderOutput的write方法,将编码结果写到队列里面,最后调用nextFilter的filterWrite方法,将当前结果传递给他的下一个过滤器中,这就是整个编码过程;

        讲完了编解码过程,还有一个问题就是MINA是怎么解决传递数据的过程中出现的粘包和断包现象的呢?

        先来说说粘包的概念:指TCP协议中,发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。造成的可能原因:发送端需要等缓冲区满才发送出去,造成粘包接收方不及时接收缓冲区的包,造成多个包接收;

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

        消息格式有两种,一种是消息长度+消息头+消息体,即前N个字节用于存储消息的长度,用于判断当前消息什么时候结束。一种是消息头+消息体,即固定长度的消息,前几个字节为消息头,后面的是消息头。在MINA中使用的是前者实现的,具体来讲的话就是通过4个字节来存储消息的长度,用于判断当前消息什么时候结束;

        在MINA中解决粘包和断包问题的话,我们的解码器就需要实现CumulativeProtocolDecoder抽象类,我们之前实现解码器的话是实现ProtocolDecoder接口的,单独实现这个接口是不能解决粘包和断包问题的,CumulativeProtocolDecoder抽象类继承了ProtocolDecoderAdapter抽象类,这个类实现了ProtocolDecoder接口,在CumulativeProtocolDecoder中为我们增添了一个叫doDecode的方法,这个方法是我们在实现CumulativeProtocolDecoder抽象类的时候自己实现的,这个方法是由返回值的,正是因为这个返回值的存在,我们才能解决断包问题,返回true的话,表示已经是一个完整的包了,我们可以进行解码了,返回false的话表示是断包,我们需要将其缓存下来,等到是组成完整的包之后对其进行解码;那么doDecode是谁调用的呢?当然是decode啦,具体我们看看decode里面做了些什么事情:

 public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
        if (!session.getTransportMetadata().hasFragmentation()) {
            while (in.hasRemaining()) {
                if (!doDecode(session, in, out)) {
                    break;
                }
            }

            return;
        }

        boolean usingSessionBuffer = true;
        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;
        }

        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.");
                }

                if (!buf.hasRemaining()) {
                    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()) {
            if (usingSessionBuffer && buf.isAutoExpand()) {
                buf.compact();
            } else {
                storeRemainingInSession(buf, session);
            }
        } else {
            if (usingSessionBuffer) {
                removeSessionBuffer(session);
            }
        }
    }
        查看CumulativeProtocolDecoder会发现它里面存在一个AttributeKey类型的属性对象BUFFER,这个BUFFER实际上就是用于存放断包数据的;

private final AttributeKey BUFFER = new AttributeKey(getClass(), "buffer");
        首先第13行获取到上次的断包数据,第16行判断断包数据是否存在,如果存在的话进入if语句块,接着第19行判断当前IoBuffer是否允许扩充,允许的话进入if语句块中,在第21行将断包数据和当前数据进行拼接,同时修改appended标志,接着第31行判断appended的值,如果等于true的话,执行第31行,将IoBuffer置为读模式,否则的话表示当前的IoBuffer是不允许扩充的,那么我们需要在第37行创建一个新的IoBuffer对象对断包数据和当前输入的数据进行拼接,最后在第42行将拼接完成的IoBuffer赋给原先的IoBuffer,随后进入52行的死循环,在第54行调用我们实现抽象类CumulativeProtocolDecoder的doDecode方法进行解码并且获取他的返回值,如果返回值为false的话,则直接退出循环,表示这次是断包,我们需要存储这个断包里面的内容,随后的第71到81行就是将断包内容存储到IoSession,下次在调用decode方法的时候,我们便可以获取到这次存储到IoSession里面的断包内容了,这就是断包处理过程了;

        那么粘包是怎么处理的呢?上面我们对52行到66行的讲解有点粗糙,其实这部分就是对粘包的处理,你想想为什么使用死循环就明白了,粘包的意思是有若干的数据在接收方粘成了一块,那么我们就需要多次处理这个消息了,因为我们自己实现的doDecode方法是由解码规则的,粘包中完整数据可能不止一个,我们当然需要循环处理了,直到剩下的消息数据不再是完整的消息或者IoBuffer中不存在数据为止,也就是在第61行和64行我们看到的两个break退出循环;

        以上就是MINA中编解码以及粘包、断包的处理啦!




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值