Mina2简介
MINA2是一个网络通信应用框架,它主要用于基于TCP/IP、UDP/IP协议栈的通信框架,也可以提供 Java 对象的序列化服务、虚拟机管道通信服务等。MINA2可以帮助我们快速开发高性能、高扩展性的网络通信应用。MINA2 提供了事件驱动、异步(MINA2 的异步 IO 默认使用的是 Java NIO 作为底层支持)操作的编程模型。
Mina2在网络通信中的作用图
可见 MINA2 的 API 将真正的网络通信与我们的应用程序隔离开来,你只需要关心你要发送、接收的数据以及你的业务逻辑即可。同样的,无论是哪端,MINA2 的执行流程如下所示:
Mina2执行流程图
或如下图:
MINA2 通信原理
异步 IO 模型
异步 I/O 模型大体上可以分为两种,反应式 (Reactive) 模型和前摄式 (Proactive) 模型:
1. 传统的 select / epoll / kqueue 模型,以及 Java NIO 模型,都是典型的反应式模型,即应用代码对 I/O 描述符进行注册,然后等待 I/O 事件。当某个或某些 I/O 描述符所对应的 I/O 设备上产生 I/O 事件(可读、可写、异常等)时,系统将发出通知,于是应用便有机会进行 I/O 操作并避免阻塞。由于在反应式模型中应用代码需要根据相应的事件类型采取不同的动作,最常见的结构便是嵌套的 if {...} else {...} 或 switch ,并常常需要结合状态机来完成复杂的逻辑。
2. 前摄式模型则不同。在前摄式模型中,应用代码主动地投递异步操作而不管 I/O 设备当前是否可读或可写。投递的异步 I/O 操作被系统接管,应用代码也并不阻塞在该操作上,而是指定一个回调函数并继续自己的应用逻辑。当该异步操作完成时,系统将发起通知并调用应用代码指定的回调函数。在前摄式模型中,程序逻辑由各个回调函数串联起来:异步操作 A 的回调发起异步操作 B ,B 的回调再发起异步操作 C ,以此往复。 MINA2 便是一个前摄式的异步 I/O 框架。
MINA2 基本概念
I/O服务:I/O 服务用来执行实际的 I/O 操作。MINA2 已经提供了一系列支持不同协议的 I/O 服务,如 TCP/IP、UDP/IP、串口和虚拟机内部的管道等。开发人员也可以实现自己的 I/O 服务。由于 I/O 服务执行的是输入和输出两种操作,实际上有两种具体的子类型。一种称为“I/O 接受器(I/O acceptor)”,用来接受连接,一般用在服务器的实现中;另外一种称为“I/O 连接器(I/O connector)”,用来发起连接,一般用在客户端的实现中。对应在 MINA2 中的实现,org.apache.mina.core.service.IoService 是 I/O 服务的接口,而继承自它的接口 org.apache.mina.core.service.IoAcceptor 和 org.apache.mina.core.service.IoConnector 则分别表示 I/O 接受器和 I/O 连接器。
I/O 接受器:I/O 接受器用来接受连接,与对等体(客户端)进行通讯,并发出相应的 I/O 事件交给 I/O 处理器来处理。使用 I/O 接受器的时候,只需要调用 bind 方法并指定要监听的套接字地址。当不再接受连接的时候,调用 unbind 停止监听即可。
I/O 连接器:I/O 连接器用来发起连接,与对等体(服务器)进行通讯,并发出相应的 I/O 事件交给 I/O 处理器来处理。使用 I/O 连接器的时候,只需要调用 connect 方法连接指定的套接字地址。另外可以通过 setConnectTimeoutMillis 设置连接超时时间(毫秒数)。
I/O 会话:I/O 会话表示一个活动的网络连接,与所使用的传输方式无关。I/O 会话可以用来存储用户自定义的与应用相关的属性。这些属性通常用来保存应用的状态信息,还可以用来在 I/O 过滤器和 I/O 处理器之间交换数据。I/O 会话在作用上类似于 Servlet 规范中的 HTTP 会话。
I/O过滤器:I/O 服务能够传输的是字节流,而上层应用需要的是特定的对象与数据结构。I/O 过滤器用来完成这两者之间的转换。I/O 过滤器的另外一个重要作用是对输入输出的数据进行处理,满足横切的需求。多个 I/O 过滤器串联起来,形成 I/O 过滤器链。Apache MINA 中的过滤器与 Servlet 规范中的过滤器是类似的。过滤器可以在很多情况下使用,比如记录日志、性能分析、访问控制、负载均衡和消息转换等。每个过滤器都可对通过的数据进行任意的操作,包括增加、删除、更新、类型转换等。先装上的过滤器更靠近远程端点 ( 客户端),后装上的更靠近本地端点 ( 服务器)。
I/O处理器:I/O 事件通过过滤器链之后会到达 I/O 处理器。I/O 处理器中与 I/O 事件对应的方法会被调用。MINA2 中 org.apache.mina.core.service.IoHandler 是 I/O 处理器要实现的接口,一般情况下,只需要继承自 org.apache.mina.core.service.IoHandlerAdapter 并覆写所需方法即可。
MINA2 就是用来充当消息中间件解决各子系统之间通信的问题。在每个子系统增加 MINA2 的客户端和服务端,负责接收和发送 Mina 消息,调用其他子系统的业务功能和数据。
在Mina2的源码中IoAcceptor及IoConnector都是IO服务IoService接口的实现,而此过程中的实现还包括了其他大量的接口及抽象类。I/O处理器、I/O过滤器及编码器都使用了适配器方式,分别是继承IoHandlerAdapter、IoFilter-Adapter、ProtocolEncoderAdapter,覆写所需方法。
Mina2提供的常见I/O过滤器有LoggingFilter(日志记录)、ProtocolCodecFilter(消息数据编解码)、BlacklistFilter("黑名单",阻止来自特定地址的连接)等。编码器需要实现ProtocolEncoder接口,一般继承ProtocolEncoderAdapter覆写所需方法即可,解码器一般继承CumulativeProtocolDecoder。
使用状态机
在 I/O 处理器中实现业务逻辑的时候,对于简单的情况,一般只需要在 messageReceived 方法中对传入的消息进行处理。如果需要写回数据到对等体的话,用 IoSession.write 方法即可。在另外的一些情况下,客户端和服务器端的通信协议比较复杂,客户端其实是有状态变迁的。这个时候可以用 Apache MINA 提供的状态机实现,可以使得 I/O 处理器的实现更加简单。
状态机中两个重要的元素是状态以及状态之间的迁移。
使用标注@state声明一个状态,使用标注@IoHandlerTransition声明一个状态迁移。
每个状态迁移可以有四个属性:on、in、next和 weight,其中属性 in是必须的,其余是可选的。属性 on表示触发此状态迁移的事件名称,如果省略该属性的话,则默认为匹配所有事件的通配符。该属性的值可以是表中给出的 I/O 处理器中能处理的七种事件类型。属性 in表示状态迁移的起始状态。属性 next表示状态迁移的结束状态,如果省略该属性的话,则默认为表示当前状态 的 _self_。属性 weight用来指明状态迁移的权重。一个状态的所有迁移是按照其权重升序排列的。对于当前状态,如果有多个可能的迁移,排序靠前的迁移将会发生。
如:@IoHandlerTransition(on = MESSAGE_RECEIVED, in = NOT_CONNECTED, next = IDLE)
使用了 MINA 提供的状态机之后,创建 I/O 处理器的方式也发生了变化。I/O 处理器的实例由状态机来创建:
private static IoHandler createIoHandler() { StateMachine sm = StateMachineFactory.getInstance( IoHandlerTransition.class).create(ServerHandler.NOT_CONNECTED, new ServerHandler()); return new StateMachineProxyBuilder().setStateContextLookup( new IoSessionStateContextLookup(new StateContextFactory() { public StateContext create() { return new ServerHandler.TetrisServerContext(); } })).create(IoHandler.class, sm); }
异步操作
MINA 中的很多操作都是异步的,比如连接的建立、连接的关闭、还有数据的发送等。在编写网络应用的时候,需要考虑这一点。比如 IoConnector的connect方法,其返回值是 org.apache.mina.core.future.ConnectFuture类的对象。通过此对象,可以查询连接操作的状态。另外一个常用的是发送数据时使用的 org.apache.mina.core.future.WriteFuture。
IoConnector connector = new NioSocketConnector(); connector.setConnectTimeoutMillis(3000); connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("utf-8")))); connector.getFilterChain().addLast("logger", new LoggingFilter()); connector.setHandler(new TimeClientHandler()); ConnectFuture future = connector.connect(new InetSocketAddress("127.0.0.1", PORT)); future.awaitUninterruptibly(); future.isConnected(); IoSession session = future.getSession(); WriteFuture writeFuture = session.write("what time is it now?"); writeFuture.awaitUninterruptibly(); // 等待发送数据操作完成 if(writeFuture.isWritten()){ // 数据已经被成功发送 } else { // 数据发送失败 }
JMX集成
MINA 可以集成 JMX 来对网络应用进行管理和监测。下面通过一简单示例来说明如何集成 JMX。
IoAcceptor acceptor = new NioSocketAcceptor(); // Mina与JMX的集成 MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); IoServiceMBean acceptorMBean = new IoServiceMBean(acceptor); ObjectName acceptorName = new ObjectName(acceptor.getClass().getPackage().getName() + ":type=acceptor,name=" + acceptor.getClass().getSimpleName()); mBeanServer.registerMBean(acceptorMBean, acceptorName); acceptor.getFilterChain().addLast("logger", new LoggingFilter()); acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8")))); acceptor.setHandler(new CalculatorHandler()); acceptor.bind(new InetSocketAddress(PORT));
首先获取平台提供的受控 bean 的服务器,接着创建受控 bean(MBean)来包装想要管理和监测的对象,这里使用的是 I/O 接收器对象。最后把创建出来的受控 bean 注册到服务器即可。
在启动计算器服务应用的时候,添加下面的启动参数:-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8084 -Dcom.sun.management.
jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false,就启用了 JMX。接着通过 JVM 提供的“Java 监视和管理控制台”(运行 jconsole)就可以连接到此应用进行管理和监测了。
Spring集成
MINA 可以和流行的开源框架 Spring 进行集成,由 Spring 来管理 MINA 中的对象。与 Spring 集成的方式也比较简单,只需要编写相应的 Spring 配置文件即可。
<?xml version="1.0" encoding="UTF-8"?> <beans> <bean id="calculatorHandler" class="org.apache.mina.demo02.server.CalculatorHandler" /> <bean id="loggingFilter" class="org.apache.mina.filter.logging.LoggingFilter" /> <bean id="calculatorCodecFilter" class="org.apache.mina.filter. codec.ProtocolCodecFilter"> <constructor-arg> <bean class="org.apache.mina.filter.codec.textline.TextLineCodecFactory" /> </constructor-arg> </bean> <bean id="filterChainBuilder" class="org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder"> <property name="filters"> <map> <entry key="loggingFilter" value-ref="loggingFilter" /> <entry key="codecFilter" value-ref="calculatorCodecFilter" /> </map> </property> </bean> <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="customEditors"> <map> <entry key="java.net.SocketAddress"> <bean class="org.apache.mina.integration.beans.InetSocketAddressEditor"/> </entry> </map> </property> </bean> <bean id="ioAcceptor" class="org.apache.mina.transport.socket.nio.NioSocketAcceptor" init-method="bind" destroy-method="unbind"> <property name="defaultLocalAddress" value=":10010" /> <property name="handler" ref="calculatorHandler" /> <property name="filterChainBuilder" ref="filterChainBuilder" /> </bean> </beans>
上面创建 I/O 处理器和 I/O 过滤器的方式很直接。由于不能直接从 I/O 接受器获取过滤器链,这里创建了一个
org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder类的 bean,用来构建过滤器链。由 Apache MINA
提供的网络地址编辑器 org.apache.mina.integration.beans.InetSocketAddressEditor允许以“主机名 : 端口”的形式
指定网络地址。在声明 I/O 接受器的时候,通过 init-method指明了当 I/O 接受器创建成功之后,调用其 bind方法来
接受连接;通过 destroy-method声明了当其被销毁的时候,调用其 unbind来停止监听。
更多Mina详细讲述及示例:
http://my.oschina.net/ielts0909/blog/92716
http://www.ibm.com/developerworks/cn/web/1108_sumeng_mina2/index.html
http://www.ibm.com/developerworks/cn/java/j-lo-mina2/index.html