本站文章均为 李华明Himi 原创,转载务必在明显处注明:
转载自【黑米GameDev街区】 原文链接: http://www.himigame.com/apache-mina/831.html

          ☞ 点击订阅 ☜ 本博客最新动态!及时将最新博文通知您!

在上一篇博文中已经简单介绍过“过滤器”的概念,那么在Mina 中的协议编解码器通过过滤器 ProtocolCodecFilter 构造,这个过滤器的构造方法需 要一个 ProtocolCodecFactory,这从前面注册 TextLineCodecFactory 的代码就可以看出来。 ProtocolCodecFactory 中有如下两个方法:

public interface ProtocolCodecFactory {

ProtocolEncoder getEncoder(IoSession session) throws Exception;

ProtocolDecoder getDecoder(IoSession session) throws Exception;

}

因此,构建一个 ProtocolCodecFactory 需要 ProtocolEncoder、ProtocolDecoder 两个实例。你可能要问 JAVA 对象和二进制数据之间如何转换呢?这个要依据具体的通信协议,也就是 Server 端要和 Client 端约定网络传输的数据是什么样的格式,譬如:第一个字节表示数据 长度,第二个字节是数据类型,后面的就是真正的数据(有可能是文字、有可能是图片等等), 然后你可以依据长度从第三个字节向后读,直到读取到指定第一个字节指定长度的数据。

简单的说,HTTP 协议就是一种浏览器与 Web 服务器之间约定好的通信协议,双方按照指定 的协议编解码数据。我们再直观一点儿说,前面一直使用的 TextLine 编解码器就是在读取 网络上传递过来的数据时,只要发现哪个字节里存放的是 ASCII 的 10、13 字符(\r、\n), 就认为之前的字节就是一个字符串(默认使用 UTF-8 编码)。

以上所说的就是各种协议实际上就是网络七层结构中的应用层协议,它位于网络层(IP)、 传输层(TCP)之上,Mina 的协议编解码器就是让你实现一套自己的应用层协议栈。

首先我们创建一个传递的对象类:

  1. package com.entity;
  2. import javax.persistence.Column;
  3. import javax.persistence.Entity;
  4. import javax.persistence.GeneratedValue;
  5. import javax.persistence.GenerationType;
  6. import javax.persistence.Id;
  7. import javax.persistence.Table;
  8. import org.hibernate.annotations.Index;
  9. /**
  10. * @author Himi
  11. */
  12. @Entity
  13. @Table(name = "playerAccount")
  14. public class PlayerAccount_Entity {
  15. private int id;
  16. private String name;
  17. private String emailAdress;
  18. private int sex;// 0=man 1=woman
  19. @Id
  20. @Column(name = "playerAccountID")
  21. @GeneratedValue(strategy = GenerationType.AUTO)
  22. public int getId() {
  23. return id;
  24. }
  25. public void setId(int id) {
  26. this.id = id;
  27. }
  28. @Index(name="nameIndex")
  29. public String getName() {
  30. return name;
  31. }
  32. public void setName(String name) {
  33. this.name = name;
  34. }
  35. public String getEmailAdress() {
  36. return emailAdress;
  37. }
  38. public void setEmailAdress(String emailAdress) {
  39. this.emailAdress = emailAdress;
  40. }
  41. public int getSex() {
  42. return sex;
  43. }
  44. public void setSex(int sex) {
  45. this.sex = sex;
  46. }
  47. }

2. 创建一个编码类:

  1. package com.protocol;
  2. /**
  3. * @author Himi
  4. */
  5. import java.nio.charset.Charset;
  6. import java.nio.charset.CharsetEncoder;
  7. import org.apache.mina.core.buffer.IoBuffer;
  8. import org.apache.mina.core.session.IoSession;
  9. import org.apache.mina.filter.codec.ProtocolEncoderAdapter;
  10. import org.apache.mina.filter.codec.ProtocolEncoderOutput;
  11. import com.entity.PlayerAccount_Entity;
  12. public class HEncoder extends ProtocolEncoderAdapter {
  13. private final Charset charset;
  14. public HEncoder(Charset charset) {
  15. this.charset = charset;
  16. }
  17. @Override
  18. public void encode(IoSession arg0, Object arg1, ProtocolEncoderOutput arg2)
  19. throws Exception {
  20. CharsetEncoder ce = charset.newEncoder();
  21. PlayerAccount_Entity paEntity = (PlayerAccount_Entity) arg1;
  22. String name = paEntity.getName();
  23. IoBuffer buffer = IoBuffer.allocate(100).setAutoExpand(true);
  24. buffer.putString(name, ce);
  25. buffer.flip();
  26. arg2.write(buffer);
  27. }
  28. }

在 Mina 中编写编码器可以实现 ProtocolEncoder,其中有 encode()、dispose()两个方法需 要实现。这里的 dispose()方法用于在销毁编码器时释放关联的资源,由于这个方法一般我 们并不关心,所以通常我们直接继承适配器 ProtocolEncoderAdapter。

3.创建一个解码类:

  1. package com.protocol;
  2. /**
  3. * @author Himi
  4. */
  5. import java.nio.charset.Charset;
  6. import java.nio.charset.CharsetDecoder;
  7. import org.apache.mina.core.buffer.IoBuffer;
  8. import org.apache.mina.core.session.IoSession;
  9. import org.apache.mina.filter.codec.CumulativeProtocolDecoder;
  10. import org.apache.mina.filter.codec.ProtocolDecoderOutput;
  11. import com.entity.PlayerAccount_Entity;
  12. public class HDecoder extends CumulativeProtocolDecoder {
  13. private final Charset charset;
  14. public HDecoder(Charset charset) {
  15. this.charset = charset;
  16. }
  17. @Override
  18. protected boolean doDecode(IoSession arg0, IoBuffer arg1,
  19. ProtocolDecoderOutput arg2) throws Exception {
  20. CharsetDecoder cd = charset.newDecoder();
  21. String name = arg1.getString(cd);
  22. PlayerAccount_Entity paEntity = new PlayerAccount_Entity();
  23. paEntity.setName(name);
  24. arg2.write(paEntity);
  25. return true;
  26. }
  27. }

在 Mina 中编写解码器,可以实现 ProtocolDecoder 接口,其中有 decode()、finishDecode()、 dispose()三个方法。这里的 finishDecode()方法可以用于处理在 IoSession 关闭时剩余的 读取数据,一般这个方法并不会被使用到,除非协议中未定义任何标识数据什么时候截止 的约定,譬如:Http 响应的 Content-Length 未设定,那么在你认为读取完数据后,关闭 TCP 连接(IoSession 的关闭)后,就可以调用这个方法处理剩余的数据,当然你也可以忽略调 剩余的数据。同样的,一般情况下,我们只需要继承适配器 ProtocolDecoderAdapter,关 注 decode()方法即可。

但前面说过解码器相对编码器来说,最麻烦的是数据发送过来的规模,以聊天室为例,一个 TCP 连接建立之后,那么隔一段时间就会有聊天内容发送过来,也就是 decode()方法会被往 复调用,这样处理起来就会非常麻烦。那么 Mina 中幸好提供了 CumulativeProtocolDecoder 类,从名字上可以看出累积性的协议解码器,也就是说只要有数据发送过来,这个类就会去 读取数据,然后累积到内部的 IoBuffer 缓冲区,但是具体的拆包(把累积到缓冲区的数据 解码为 JAVA 对象)交由子类的 doDecode()方法完成,实际上 CumulativeProtocolDecoder 就是在 decode()反复的调用暴漏给子类实现的 doDecode()方法。

具体执行过程如下所示:

A. 你的 doDecode()方法返回 true 时,CumulativeProtocolDecoder 的 decode()方法会首先判断你是否在 doDecode()方法中从内部的 IoBuffer 缓冲区读取了数据,如果没有,ce); buffer.putString(smsContent, ce);buffer.flip();则会抛出非法的状态异常,也就是你的 doDecode()方法返回 true 就表示你已经消费了 本次数据(相当于聊天室中一个完整的消息已经读取完毕),进一步说,也就是此时你 必须已经消费过内部的 IoBuffer 缓冲区的数据(哪怕是消费了一个字节的数据)。如果 验证过通过,那么 CumulativeProtocolDecoder 会检查缓冲区内是否还有数据未读取, 如果有就继续调用 doDecode()方法,没有就停止对 doDecode()方法的调用,直到有新 的数据被缓冲。

B. 当你的 doDecode()方法返回 false 时,CumulativeProtocolDecoder 会停止对 doDecode() 方法的调用,但此时如果本次数据还有未读取完的,就将含有剩余数据的 IoBuffer 缓 冲区保存到 IoSession 中,以便下一次数据到来时可以从 IoSession 中提取合并。如果 发现本次数据全都读取完毕,则清空 IoBuffer 缓冲区。简而言之,当你认为读取到的数据已经够解码了,那么就返回 true,否则就返回 false。这 个 CumulativeProtocolDecoder 其实最重要的工作就是帮你完成了数据的累积,因为这个工 作是很烦琐的。

4.创建一个编解码工厂类:

  1. package com.protocol;
  2. import java.nio.charset.Charset;
  3. import org.apache.mina.core.session.IoSession;
  4. import org.apache.mina.filter.codec.ProtocolCodecFactory;
  5. import org.apache.mina.filter.codec.ProtocolDecoder;
  6. import org.apache.mina.filter.codec.ProtocolEncoder;
  7. /**
  8. *
  9. * @author Himi
  10. *
  11. */
  12. public class HCoderFactory implements ProtocolCodecFactory {
  13. private final HEncoder encoder;
  14. private final HDecoder decoder;
  15. public HCoderFactory() {
  16. this(Charset.defaultCharset());
  17. }
  18. public HCoderFactory(Charset charSet) {
  19. this.encoder = new HEncoder(charSet);
  20. this.decoder = new HDecoder(charSet);
  21. }
  22. @Override
  23. public ProtocolDecoder getDecoder(IoSession arg0) throws Exception {
  24. // TODO Auto-generated method stub
  25. return decoder;
  26. }
  27. @Override
  28. public ProtocolEncoder getEncoder(IoSession arg0) throws Exception {
  29. // TODO Auto-generated method stub
  30. return encoder;
  31. }
  32. }

这个工厂类就是包装了编码器、解码器,通过接口中的 getEncoder()、getDecoder() 方法向 ProtocolCodecFilter 过滤器返回编解码器实例,以便在过滤器中对数据进行编解码 处理。

    5. 以上3个编解码有关的类在Server与Client读需要有,那么同时我们创建好了自定义的编解码有关的类后,我们设置Server和Client的编码工厂为我们自定义的编码工厂类:

  1. DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();      chain.addLast("mycoder", new ProtocolCodecFilter(new HCoderFactory(             Charset.forName("UTF-8"))));

6.书写测试的消息处理器类Client和Server端;

Client端消息处理器: 

  1. /**
  2. * @author Himi
  3. */
  4. import org.apache.mina.core.service.IoHandlerAdapter;
  5. import org.apache.mina.core.session.IoSession;
  6. import com.protocol.PlayerAccount_Entity;
  7. public class ClientMainHanlder extends IoHandlerAdapter {
  8. // 当一个客端端连结到服务器后
  9. @Override
  10. public void sessionOpened(IoSession session) throws Exception {
  11. PlayerAccount_Entity ho = new PlayerAccount_Entity();
  12. ho.setName("李华明 xiaominghimi@gmail.com");
  13. session.write(ho);
  14. }
  15. // 当一个客户端关闭时
  16. @Override
  17. public void sessionClosed(IoSession session) {
  18. System.out.println("I'm Client &&  I closed!");
  19. }
  20. // 当服务器端发送的消息到达时:
  21. @Override
  22. public void messageReceived(IoSession session, Object message)
  23. throws Exception {
  24. PlayerAccount_Entity ho = (PlayerAccount_Entity) message;
  25. System.out.println("Server Say:name:" + ho.getName());
  26. }
  27. }

Server端消息处理器:

  1. /**
  2. * @author Himi
  3. */
  4. import org.apache.mina.core.service.IoHandlerAdapter;
  5. import org.apache.mina.core.session.IdleStatus;
  6. import org.apache.mina.core.session.IoSession;
  7. import com.entity.PlayerAccount_Entity;
  8. import com.sessionUtilities.HibernateUtil;
  9. public class MainHanlder extends IoHandlerAdapter {
  10. private int count = 0;
  11. // 当一个新客户端连接后触发此方法.
  12. /*
  13. * 这个方法当一个 Session 对象被创建的时候被调用。对于 TCP 连接来说,连接被接受的时候 调用,但要注意此时 TCP
  14. * 连接并未建立,此方法仅代表字面含义,也就是连接的对象 IoSession 被创建完毕的时候,回调这个方法。 对于 UDP
  15. * 来说,当有数据包收到的时候回调这个方法,因为 UDP 是无连接的。
  16. */
  17. public void sessionCreated(IoSession session) {
  18. System.out.println("新客户端连接");
  19. }
  20. // 当一个客端端连结进入时 @Override
  21. /*
  22. * 这个方法在连接被打开时调用,它总是在 sessionCreated()方法之后被调用。对于 TCP 来
  23. * 说,它是在连接被建立之后调用,你可以在这里执行一些认证操作、发送数据等。 对于 UDP 来说,这个方法与
  24. * sessionCreated()没什么区别,但是紧跟其后执行。如果你每 隔一段时间,发送一些数据,那么
  25. * sessionCreated()方法只会在第一次调用,但是 sessionOpened()方法每次都会调用。
  26. */
  27. public void sessionOpened(IoSession session) throws Exception {
  28. count++;
  29. System.out.println("第 " + count + " 个 client 登陆!address: : "
  30. + session.getRemoteAddress());
  31. }
  32. // 当客户端发送的消息到达时:
  33. /*
  34. * 对于 TCP 来说,连接被关闭时,调用这个方法。 对于 UDP 来说,IoSession 的 close()方法被调用时才会毁掉这个方法。
  35. */
  36. @Override
  37. public void messageReceived(IoSession session, Object message)
  38. throws Exception {
  39. // // 我们己设定了服务器解析消息的规则是一行一行读取,这里就可转为String:
  40. // String s = (String) message;
  41. // // Write the received data back to remote peer
  42. // System.out.println("收到客户机发来的消息: " + s);
  43. // // 测试将消息回送给客户端 session.write(s+count); count++;
  44. PlayerAccount_Entity ho = (PlayerAccount_Entity) message;
  45. System.out.println("Client Say:" + ho.getName());
  46. ho.setName("Himi  317426208@qq.com");
  47. session.write(ho);
  48. }
  49. // 当信息已经传送给客户端后触发此方法.
  50. /*
  51. * 当发送消息成功时调用这个方法,注意这里的措辞,发送成功之后,也就是说发送消息是不 能用这个方法的。
  52. */
  53. @Override
  54. public void messageSent(IoSession session, Object message) {
  55. System.out.println("信息已经传送给客户端");
  56. }
  57. // 当一个客户端关闭时
  58. /*
  59. * 对于 TCP 来说,连接被关闭时,调用这个方法。 对于 UDP 来说,IoSession 的 close()方法被调用时才会毁掉这个方法。
  60. */
  61. @Override
  62. public void sessionClosed(IoSession session) {
  63. System.out.println("one Clinet Disconnect !");
  64. }
  65. // 当连接空闲时触发此方法.
  66. /*
  67. * 这个方法在 IoSession 的通道进入空闲状态时调用,对于 UDP 协议来说,这个方法始终不会 被调用。
  68. */
  69. @Override
  70. public void sessionIdle(IoSession session, IdleStatus status) {
  71. //      System.out.println("连接空闲");
  72. }
  73. // 当接口中其他方法抛出异常未被捕获时触发此方法
  74. /*
  75. * 这个方法在你的程序、Mina 自身出现异常时回调,一般这里是关闭 IoSession。
  76. */
  77. @Override
  78. public void exceptionCaught(IoSession session, Throwable cause) {
  79. System.out.println("其他方法抛出异常");
  80. }
  81. }

OK,首先启动Server端,然后运行Client端,观察控制台:

【Apache Mina2.0开发之二】自定义实现Server/Client端的编解码工厂(自定义编码与解码器)!...相关推荐

  1. 【Apache Mina2.0开发之一】搭建Apache Mina框架并实现Server与Client端消息传递

    Hibernate系列学习阶段到此结束了,那么紧接着进入Apache Mina的开发学习,很多童鞋在微薄和QQ中疑问Himi为什么突然脱离游戏开发了,嘿嘿,其实可能更多的童鞋已经看出来了,Himi在偏 ...

  2. 【Apache Mina2.0开发之一】搭建Apache Mina框架并实现Server与Client端的简单消息传递!

    Hibernate系列学习阶段到此结束了,那么紧接着进入Apache Mina的开发学习,很多童鞋在微薄和QQ中疑问Himi为什么突然脱离游戏开发了,嘿嘿,其实可能更多的童鞋已经看出来了,Himi在偏 ...

  3. 即时通讯音视频开发(十八):详解音频编解码的原理、演进和应用选型

    1.引言 大家好,我是刘华平,从毕业到现在我一直在从事音视频领域相关工作,也有一些自己的创业项目,曾为早期Google Android SDK多媒体架构的构建作出贡献. 就音频而言,无论是算法多样性, ...

  4. ext 6.0开发实例二

    由于Ext JS 6将原来的Ext JS和Sencha Touch合并为一个框架,因而在使用CMD来创建应用程序前,需要考虑清楚你是要创建一个通用应用程序,还是仅仅只是针对桌面或移动设备的应用程序. ...

  5. NDK OpenGL ES 3.0 开发(二十):3D 模型

    该原创文章首发于微信公众号:字节流动 OpenGLES 3D 模型 OpenGLES 3D 模型本质上是由一系列三角形在 3D 空间(OpenGL 坐标系)中构建而成,另外还包含了用于描述三角形表面的 ...

  6. NDK OpenGL ES 3.0 开发(二十二):PBO

    该原创文章首发于微信公众号:字节流动 PBO 是什么 OpenGL PBO(Pixel Buffer Object),被称为像素缓冲区对象,主要被用于异步像素传输操作.PBO 仅用于执行像素传输,不连 ...

  7. 网易互动直播2.0 开发 十二 视频设备管理

    序 官方给出的例子是理想情况下的示例,理想情况下,预览视频,只需要如下调用就可以 void *hWnd = GetHWND();m_engine->setupLocalVideo(hWnd); ...

  8. XenApp6.0 部署之 二 配置XenApp Server

    接着开始配置XenApp了,主要要配置3组件--XenApp.Web Interface.License Server.我这里通俗点给大家解释下这三个组件:XenApp就相当于是XenApp Serv ...

  9. 基于Android O8.1的ffmpeg NDK 开发 - 2 - APP显示ffmpeg所支持协议,编解码,过滤器,格式,配置等信息

    我们今天的目标是仿照雷神的博客(https://blog.csdn.net/leixiaohua1020/article/details/47008825),把这个APP写出来,先上图: APP中有5 ...

最新文章

  1. 58. Length of Last Word
  2. Python编程基础:第三十节 文件检测File Detection
  3. python中map函数字典映射_python Chainmap函数(19)
  4. mysql权限表_MySQL 数据库赋予用户权限操作表
  5. windows环境下C语言socket编程
  6. 【UVALive】3029 City Game(悬线法)
  7. Flutter Swiper制作轮播效果
  8. 街霸5 android,MD街头霸王5免安装版
  9. Android Studio AVD中文输入法安装
  10. 【空间分析-文章学习笔记】2 北京各行业的空间分布分析
  11. Alpine linux硬盘安装
  12. [zz]P2P直播软件设计的技术原理和改进
  13. 微信小程序背景图片 模拟器正常显示 真机无法显示
  14. GIS地图点汇聚及空间搜索算法Java实现样例
  15. idea2020.1.1 窗口显示 Outdated version. 完美解决
  16. WIN32 获取programdata等指定的系统目录的路径
  17. 大学高数常微分方程思维导图_高等数学各章节知识点框架 常微分方程.pdf
  18. JAVA实现打字小游戏
  19. 【XSY2753】LCM
  20. C语言:int型数据向char型数据的强制类型转换原理

热门文章

  1. VTK:Rendering之StringToImageDemo
  2. C++求LCM的和算法(附完整源码)
  3. C语言实现克拉茨Collatz序列(附完整源码)
  4. C++尽量在operater=中处理“自我赋值”
  5. QT实现在图表顶部绘制一个附加元素(标注)
  6. QT的QGraphicsItem类的使用
  7. php输入的值不得小于,PHP代码不允许多个输入值通过我的表单发送
  8. php redis zset 延迟队列_PHP + Redis 实现简单消息队列
  9. Linux vi常用指令使用案例汇总
  10. Mysql 8二进制包多实例绿色解压安装Windows配置