mina编解码(摘录)
一、Mina对编解码的支持
我们知道网络通讯过程实际是对二进制数据进行处理的过程,二进制数据是计算机认识的数据。对于接收到的二进制数据我们需要将其转换成我们所熟悉的数据格式,此过程称为解码(decode);对于所要发送的数据,我们需要转换为计算机所能处理的二进制数据,此过程称为编码(encode)。
Mina对数据的编解码提供了良好的支持,它提供了过滤器ProtocolCodecFilter支持编码和解码过程,可以查看包org.apache.mina.filter.codec下的代码。
看下此过滤器的调用,代码很简单:
- // 加入编解码过滤器
- chain.addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory()));
实现原理
ProtocolCodecFilter包含一个编码器和解码器工厂:
- //编码器和解码器工厂
- private final ProtocolCodecFactory factory;
此工厂可以通过构造方法传入,具体构造方法可以具体看源码,比较简单,此处不做详细介绍。
主要看下解码和编码过程,解码应该是消息接收到,我们程序对消息进行处理时进行的,此时我们想到ProtocolCodecFilter应该覆盖messageReceived方法。编码应该是发送消息时,需要将我们的业务数据结构转换为二进制数据,此时我们想到ProtocolCodecFilter应该覆盖filterWrite方法。
解码过程
前面已经说了,解码过程就是将二进制数据转换为我们可以识别的数据结构,所以messageReceived方法一开始就有个判断:
- //对于解码,消息类型必须是IoBuffer类型的,如果不是,转向下个filter
- if (!(message instanceof IoBuffer)) {
- nextFilter.messageReceived(session, message);
- return;
- }
解码的核心操作:
- //处理消息,如果buffer中还有数据,就处理数据
- while (in.hasRemaining()) {
- int oldPos = in.position();
- try {
- synchronized (decoderOut) {
- //进行解码操作。
- decoder.decode(session, in, decoderOut);
- }
- …………
- }
我们需要实现解码器ProtocolDecoder接口,主要实现解码方法decode。可以参考TextLineDecoder类的实现,下面代码是本人实际项目中的实现:
- public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
- final int packHeadLength = 2;
- // 先获取上次的处理上下文,其中可能有未处理完的数据
- Context ctx = getContext(session);
- // 先把当前buffer中的数据追加到Context的buffer当中
- ctx.append(in);
- // 把position指向0位置,把limit指向原来的position位置
- IoBuffer buf = ctx.getBuffer();
- buf.flip();
- // 当前剩余长度大于2
- while (buf.remaining() >= packHeadLength) {
- buf.mark();
- if (ByteConvertUtil.toHex(buf.get()).equalsIgnoreCase("eb")) {
- if (ByteConvertUtil.toHex(buf.get()).equalsIgnoreCase("93")) {
- buf.reset();
- if(buf.remaining()<11){
- break;
- }
- byte[] dataArray = new byte[11];
- buf.get(dataArray, 0, 11);
- if (SensorData.checkData(dataArray)) {
- SensorData data = new SensorData(dataArray);
- out.write(data);
- // 回应客户端
- byte[] b = new byte[2];
- b[0] = ByteConvertUtil.uniteBytes("eb");
- b[1] = ByteConvertUtil.uniteBytes("93");
- session.write(IoBuffer.wrap(b));
- }
- } else {
- continue;
- }
- } else {
- continue;
- }
- }
- //断包处理,将剩余数据放入CONTEXT中
- if (buf.hasRemaining()) {
- IoBuffer temp = IoBuffer.allocate(maxPackLength).setAutoExpand(true);
- temp.put(buf);
- temp.flip();
- buf.clear();
- buf.put(temp);
- } else {
- buf.clear();
- }
- }
顺便说下,我们最好要把我们的的数据包的格式提前定义好,了解了数据包的格式我们才能更好的进行数据的编解码。定义好数据包格式一方面方便编解码,另一方面可以解决下面要说的粘包和断包的问题。
数据包的定义有很多种方式,这里说下我所用过的两种方式:
1.固定消息长度,消息头+消息体+校验码。此方式相对简单,表示的内容也比较少。
2.不定消息长度,消息头+消息长度+消息体。此方式可以无限消息长度,比较灵活。
解码出一个消息体后,需要将数据通过ProtocolDecoderOutput的write方法写入到队列(queue)里面去:
- public void write(Object message) {
- if (message == null) {
- throw new IllegalArgumentException("message");
- }
- //将消息写入队列
- messageQueue.add(message);
- }
真正执行消息向下传递是通过flush方法:
- public void flush(NextFilter nextFilter, IoSession session) {
- Queue<Object> messageQueue = getMessageQueue();
- // 取出队列里面的消息向下传递
- while (!messageQueue.isEmpty()) {
- nextFilter.messageReceived(session, messageQueue.poll());
- }
- }
编码过程
看了上面的解码过程,编码过程就不难理解了,编码过程只不过是解码过程的逆向过程,同样在filterWrite方法里有消息类型的判断:
- //消息如果已经是IoBuffer,就不需要再进行编码
- if ((message instanceof IoBuffer) || (message instanceof FileRegion)) {
- nextFilter.filterWrite(session, writeRequest);
- return;
- }
编码:
- // 进行数据编码
- encoder.encode(session, message, encoderOut);
此处编码实现可以参考TextLineEncoder的编码实现,比较简单,此处就不多做解释了。
同样编码也是通过write到一个队列中,然后通过flush写入到后面的过滤器中的。
二、Mina对粘包和断包的处理
上面说了mina对编解码的支持,在解码过程中,不得不面对的一个问题就是TCP的粘包和断包,先说下什么是粘包和断包。
TCP通讯是面向数据流的通讯,我们将数据流理解为一支竹竿,数据包就相当于竹竿中的每一节,那么我们的解码过程就相当于对竹竿进行分解的过程。竹竿就是多个数据包的“粘包”,断包就是指竹节中间断开,我们需要将它拼接成为一个完整的竹节,如果不能拼接起来就要废弃这部分。
粘包:
断包:
对粘包的处理相对比较简单,只需要依据数据包的格式进行数据流的分割即可;对于断包的处理我们需要将断包的数据保存起来,等待接收下次的数据进行拼接。
通常情况下我们要考虑粘包和断包同时出现的情况下的解码代码编写。有两种实现方式:
1.继承CumulativeProtocolDecoder类,实现doDecode方法。
2.实现ProtocolDecoder接口,自己解决粘包和断包的问题。
先看下CumulativeProtocolDecoder的实现。
它有一个成员变量BUFFER:
- //存放断包数据
- private final AttributeKey BUFFER = new AttributeKey(getClass(), "buffer");
doDecode方法一方面判断数据包是否符合解码要求(数据包可能过短,数据包格式不合要求都可能不能通过解码要求),不符合刚返回false;另一方面对于符合解码要求的数据进行数据解码,并返回true。可以参考ImageRequestDecoder类的实现。
看下它的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();// 如果是拼接的数据,将buf置为读模式
- } else {
- // Reallocate the buffer if append operation failed due to
- // derivation or disabled auto-expansion.
- //如果buf不是可自动扩展的buffer,刚通过数据拷贝的方式将断包数据和当前数据进行拼接
- 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) {
- // 如果符合解码要求并进行了解码操作,
- // 则当前position和解码前的position不可能一样
- 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 {
- //如果还有没处理完的数据(一般为断包),刚将此数据存入session中,以便和下次数据进行拼接。
- storeRemainingInSession(buf, session);
- }
- } else {
- if (usingSessionBuffer) {
- removeSessionBuffer(session);
- }
- }
- }
上面的处理过程可以这样理解:
1.取得断包数据,如果有断包数据,就和当前数据拼接。
2.进行数据解码操作。
3.将可以进行解码操作的数据解码完成后,如果还有数据,则将剩余数据存入session中,等待下次数据到来,从步骤1开始再次执行。
通过继承ProtocolDecoder,实现decode方法,自己处理粘包和断包的方式其实和CumulativeProtocolDecoder类的实现原理是类似的,此处实现可以参考类TextLineDecoder,内部类Context保存了上下文信息,同样是保存在了sesion中的,具体实现方式大家可以仔细阅读代码。
三、总结
基于TCP的通讯协议才有可能产生粘包和断包的情况,粘包和断包的产生有多种原因,处理好粘包和断包的问题是网络编程必然面对的情况,对于这块的处理,大家如果有什么好的想法可以一起讨论。
每天进步一点点,不做无为的码农。。。。。
mina编解码(摘录)的更多相关文章
- 【MINA】用protobuf做编解码协议
SOCKET协议 支持java serial 与 AMF3的混合协议,目前没有基于xml 与 json的实现. 协议说明: * 9个字节协议头+协议体. * * 协议头1-4字节表示协议长度 =协议体 ...
- Netty 编解码技术 数据通信和心跳监控案例
Netty 编解码技术 数据通信和心跳监控案例 多台服务器之间在进行跨进程服务调用时,需要使用特定的编解码技术,对需要进行网络传输的对象做编码和解码操作,以便完成远程调用.Netty提供了完善,易扩展 ...
- 【转】Netty系列之Netty编解码框架分析
http://www.infoq.com/cn/articles/netty-codec-framework-analyse/ 1. 背景 1.1. 编解码技术 通常我们也习惯将编码(Encode)称 ...
- iOS8系统H264视频硬件编解码说明
公司项目原因,接触了一下视频流H264的编解码知识,之前项目使用的是FFMpeg多媒体库,利用CPU做视频的编码和解码,俗称为软编软解.该方法比较通用,但是占用CPU资源,编解码效率不高.一般系统都会 ...
- IOS和Android支持的音频编解码
1.IOS编码 参考文档地址:https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/Multimedi ...
- java编解码技术,netty nio
对于java提供的对象输入输出流ObjectInputStream与ObjectOutputStream,可以直接把java对象作为可存储 的字节数组写入文件,也可以传输到网络上去.对与java开放人 ...
- 编解码-marshalling
JBoss的Marshalling序列化框架,它是JBoss内部使用的序列化框架,Netty提供了Marshalling编码和解码器,方便用户在Netty中使用Marshalling. JBoss M ...
- 编解码-protobuf
Google的Protobuf在业界非常流行,很多商业项目选择Protobuf作为编解码框架,Protobuf的优点. (1)在谷歌内部长期使用,产品成熟度高: (2)跨语言,支持多种语言,包括C++ ...
- 编解码-java序列化
大多数Java程序员接触到的第一种序列化或者编解码技术就是Java的默认序列化,只需要序列化的POJO对象实现java.io.Serializable接口,根据实际情况生成序列ID,这个类就能够通过j ...
随机推荐
- 偶尔转帖:AI会议的总结(by南大周志华)
偶尔转帖:AI会议的总结(by南大周志华) 说明: 纯属个人看法, 仅供参考. tier-1的列得较全, tier-2的不太全, tier-3的很不全. 同分的按字母序排列. 不很严谨地说, tier ...
- Java运算符(一)equals方法与“==”
超类Object的equals只是比较两者之间的引用对象是否相同,这一点跟操作符“==”是一样的. 在基本数据类型中,“==”用于比较两者之间的值(内容)是否相等. 在引用类型中,“==”用于比较两者 ...
- 【20161030la 】总结
就写个题解 1. 生成树(Tree) 有一种图形叫做五角形圈.一个五角形圈的中心有1个由n个顶点和n条边组成的圈.在中心的这个n边圈的每一条边同时也是某一个五角形的一条边,一共有n个不同的五角形.这些 ...
- QQ协议的TEA加解密算法
QQ通讯协议里的加解密算法. #include <stdio.h> #include <stdlib.h> #include <memory.h> #include ...
- Perl,Python,Ruby,Javascript 四种脚本语言比较
Perl 为了选择一个合适的脚本语言学习,今天查了不少有关Perl,Python,Ruby,Javascript的东西,可是发现各大阵营的人都在吹捧自己喜欢的语言,不过最没有争议的应该是Javascr ...
- 带控制端的逻辑运算电路_分别完成正整数的平方、立方和阶乘的运算verilog语言
练习:设计一个带控制端的逻辑运算电路,分别完成正整数的平方.立方和阶乘的运算. //--------------myfunction---------- modulemyfunction(clk,n, ...
- 【CF】222 Div.1 B Preparing for the Contest
这样类似的题目不少,很多都是一堆优化条件求最优解,这个题的策略就是二分+贪心.对时间二分, 对费用采用贪心. /* 377B */ #include <iostream> #include ...
- bzoj1899
显然如果只有一个窗口,是一道贪心的题目,直接让吃饭慢的排在前面即可 两个窗口的话,我们还是根据这个原则 先对吃饭时间降序排序,然后这是一个dp 假如设当前处理到第i个人,当在窗口1的打饭时间确定了,窗 ...
- 【转】EditText获取焦点不自动弹出键盘设置--失去焦点的方法,不错
原文网址:http://zouhuajian01.blog.163.com/blog/static/1176987720121128115813176/ 当我们在activity中加入EditText ...
- Devexpress 之gridControl双击行事件
MouseDown事件 protected internal void gridControl1_MouseDown(object sender, MouseEventArgs e) { DevExp ...