Mina使用总结
背景介绍:
十四号的时候,智灵那边来了一位研发人员,带着他们的板子来跟我们对接他们那边的一款网关。这款网关布置在长江里面,集成了监测下端挂载设备的雨量流速水位等功能。在此之前,为了能够与之对接并且分割能效路灯项目中业务与底层网络通讯的部分,我们采用了mina框架。
为了完成梳理总结一下最近的成果的政治任务,同时也为了把我们部门定期分享的这一光荣传统发扬下去,所以现将这一段时间的工作感想形成一篇报告并且结合项目在这里跟大家分享一下。因为我之前对于框架了解的也并不多,临时抱了抱佛脚,所以希望大家能够抱着一个开放和包容的心态来参与到今天的这一场分享会中来。
为什么要使用MINA:
一个大型的应用往往会拆分成多个子系统,就好比之前在欧阳给我们上的培训课“项目框架”上所讲的,仅能效项目就分为三个子系统:BEMS(能效、路灯)、Comet(数据推送)、Schedule(数据采集)。而这些子系统之间并不是完全独立的,它们使用网络编程来相互通信,共同实现一个完整的业务功能。这样的java应用,我们将之称之为分布式java应用。
分布式的Java应用通常有两种方式来实现:(实现方式不是数据传输方式)
1.基于消息方式来实现系统间的通信,系统间通信说白了就是要向外发送信息,消息可以是字节流、字节数组、甚至可以是java对象(序列化之后的)。而消息方式的系统间通信,通常是基于网络协议来实现的。常用的协议有:TCP/IP、UDP/IP。
TCP/IP是一种可靠的网络传输数据协议,它要求通信双方首先建立一个连接,之后再进行数据的传输。TCP/IP负责保证数据传输的可靠性。
UDP/IP是一种不保证数据一定能到达的网络数据传输协议。由于UDP/IP不能保证数据传输的可靠性,因此性能会好一些。
TCP/IP和UDP/IP可用于完成数据传输。但要完成系统间的通信,还需要对数据进行处理。比如读入和写入数据。而按照POSIX标准(可移植操作系统接口),分为:同步IO、异步IO。同步IO中最常用的是BIO(Blocking IO)和NIO(Non-Blocking IO)。
BIO:从程序的角度而言,BIO就是当发起IO的读写操作时,均为阻塞方式,只有当程序读到了流或将流写入操作系统之后,才会将资源释放。
NIO:从程序的角度而言,当发起IO的读写操作时,是非阻塞的;当Socket有流可读或可写入Socket时,操作系统会通知相应的应用程序进行处理,应用再将流读取到缓冲区或写入操作系统。
AIO:为异步IO方式。从程序角度而言,当进行读写操作时,只需直接调用API的read或write方法即可。这两种方法均为异步的。对于操作而言,当有流可读取时,操作系统会将可读取的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。相较于NIO而言,AIO简化了程序的编写,流的读取和写入都由操作系统来代替完成。
2.基于远程调用的方式实现系统间的通信,当系统间通信时,可通过调用本地的一个java接口方法,透明地调用远程的java实现,而其细节由Java或框架来完成。
使用java包来实现基于消息方式的系统间通信比如webService和RMI(java用于实现透明远程调用的重要机制,在远程调用中客户端仅有服务器端提供的接口)还是比较麻烦的。为了让开发人员能够更加关注对于数据进行业务处理,而不是过多的关注纯技术细节。开源界诞生了许多优秀的基于以上各种协议的系统间通信框架,Mina。
Mina是Apache的顶级项目,基于Java NIO构建,同时支持TCP/IP和UDP/IP两种协议。Mina对外屏蔽了Java NIO使用的复杂性,并在性能上做了不少的优化。
远程调用方式就是尽可能地使系统间的通信和系统内一样,让使用者感觉调用远程和调用本地一样。
Mina介绍:
相较于业务功能的开发,网络通讯更偏向于底层。可能大多数程序员在学校里学完以后,在以后的工作中就很难用到。毕竟,Java的好处就是取之不尽的框架,不同的人把焦点集中在不同层面,既避免了重复造轮子也提高了生产效率。
对于阻塞式的IO,线程需要等待;而对于非阻塞式的IO,仍然需要单线程去轮询selector的状态。mina将封装了这些网络层的处理,对用户是透明的。它简化了TCP、UDP、串行通信程序等网络连接的使用;简化了服务端和客户端的使用。在高扩展性、性能和内存使用(据说netty做的更好,不过这两个框架基本思想差不多)方面给用户提供有力的支持。
mina是简单但功能齐全的网络应用程序框架,它的主要特性包括:
- 为各种网络通讯提供统一的API。
-
- 基于Java NIO 的TCP/IP UDP/IP。
- 基于RXTX的串行通信程序。
- 基于VM内部的管道通讯。
- 支持自定义实现。
- 可扩展的过滤器链,类似Serlvet的过滤器模型。
- 底层和抽象层的数据API。
-
- 底层:可基于ByteBuffer使用。
- 抽象层:用户自定义的消息对象和解编码方式。
- 高度可定制的线程模型。
-
- 单线程
- 单线程池
- 多个线程池
- 基于Java 5 SSLEngine进行了封装,支持开箱即用的SSL、TLS、StartTLS等。(?)
- 过载保护和流量控制。
- 可使用mock对象进行方便的单元测试。
- 集成JMX的管理架构
- 支持流IO(Stream-Base I/O)的操作,通过实现StreamIoHandle。
- 可被常用的容器框架集成管理,如Spring。
- 能从Netty平滑的迁移过度到Mina。
mina体系结构:
mina位于用户程序和网络处理之间,将用户从复杂的网络处理中解耦,那我们就可以更加关注业务领域。
图2.1 mina鸟瞰图
让我们再深入进去,mina框架内部的各个组件是怎么协调工作的呢?
图2.2 mina组件结构图
显然,mina框架被分成了主要的3个组件部分:
- I/O Service,具体提供服务的组件。
- I/O Filter Chain,过滤器链,用于支持各种切面服务。
- I/O Handler,用于处理用户的业务逻辑。
相对应的,为了创建一个基于mina的应用程序,我们需要:
- 创建I/O Service :可选择mina提供的Services如(*Acceptor)或实现自己的Service。
- 创建I/O Filter :同样可以选择mina提供的各类filter,也可以实现自己的编解码过滤器等。
- 实现I/O Handler,实现Handler接口,处理各种消息。
服务端结构:
服务端的作用就是开启监听端口,等待请求的到来、处理他们、以及将发送对请求的响应。同时,服务端会为每个连接创建session,在session周期内提供各种精度的服务,比如连接创建时(sessionCreated(IoSession session))、连接等待时(sessionIdle(IoSession session, IdleStatus status))、连接销毁时(sessionClosed(IoSession session))等。mina的api为TCP/UDP提供的一致性Server端操作。
图2.3 Server结构图
- IOAcceptor 监听来自网络的请求。
- 当新的连接建立时,一个新的session会被创建,该session用作对同一IP/端口组合的客户端提供服务。
- 数据包需经过一系列的过滤器,这些过滤器可用来修改数据包的内容(如转换对象、添加或修改信息等),其中将原始字节流转换成POJO对象是非常有用的。当然这需要解编码器提供支持。
- 最后这些数据包或转化后的对象将交由IOHandler处理,我们将实现IOHandler用于处理具体的业务逻辑。
客户端结构:
客户端需要连接服务端,发送请求并处理响应。实际上客户端的结构和服务端极其相似。
图2.4 客户端结构
- 客户端首先需要创建IOConnector对象,绑定服务端的IP和端口。
- 一旦连接成功,一个于本次连接绑定的session对象将被创建。
- 客户端发送给服务端的请求都需要经过一系列的fliter。
- 同样,响应消息的接受也会经过一系列的filter再到IOHandler被处理。
所以整体上,mina提供良好的一致性调用和封装结构。在使用mina创建基于网络的程序应用时,投入的学习成本比较低。
TCP连接的例子:
导入mina所需jar包
在web.xml中添加applicationContext-mina.xml
1.MINA配置项
配置applicationContext-mina.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean
class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="java.net.SocketAddress" value="org.apache.mina.integration.beans.InetSocketAddressEditor" >
</entry>
</map>
</property>
</bean>
<!-- IoAcceptor 绑定在 8888端口 -->
<bean id="ioAcceptor"
class="org.apache.mina.transport.socket.nio.NioSocketAcceptor"
init-method="bind" destroy-method="unbind">
<property name="defaultLocalAddress" value=":8888" />
<property name="handler" ref="bossSampleHandler" />
<property name="filterChainBuilder" ref="filterChainBuilder" />
<property name="reuseAddress" value="true" />
</bean>
<bean id="executorFilter"
class="org.apache.mina.filter.executor.ExecutorFilter" />
<bean id="mdcInjectionFilter"
class="org.apache.mina.filter.logging.MdcInjectionFilter">
<constructor-arg value="remoteAddress" />
</bean>
<bean id="codecFilter"
class="org.apache.mina.filter.codec.ProtocolCodecFilter">
<constructor-arg>
<!--
<bean
class="org.apache.mina.filter.codec.textline.TextLineCodecFactory" />
-->
<bean class="com.isoftstone.connectProtocol.tcp.mima.MyCodeFactory"></bean>
</constructor-arg>
</bean>
<bean id="loggingFilter"
class="org.apache.mina.filter.logging.LoggingFilter" />
<bean id="SampleHandler" class="com.isoftstone.connectProtocol.tcp.mima.HandlerTwo" />
<!--boss server -->
<bean id="bossSampleHandler" class="com.isoftstone.connectProtocol.tcp.mima.HandlerOne" />
<bean id="filterChainBuilder"
class="org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder">
<property name="filters">
<map>
<entry key="executor" value-ref="executorFilter" />
<entry key="mdcInjectionFilter"
value-ref="mdcInjectionFilter" />
<entry key="codecFilter" value-ref="codecFilter" />
<entry key="loggingFilter" value-ref="loggingFilter" />
</map>
</property>
</bean>
</beans>
上面标黄色底的部分,是配置的自已开发的MINA处理客户端请求的服务器端程序。下面标蓝底的配置指明,本服务器端程序起动后的服务器端的端口是19090。NioSocketAcceptor:非阻塞的套接字TCP(Socket) 。IoAcceptor
2.程序实现
- package com.isoftstone.connectProtocol.tcp.mima;
- import java.util.HashMap;
- import java.util.Map;
- import javax.annotation.Resource;
- import org.apache.mina.core.service.IoHandlerAdapter;
- import org.apache.mina.core.session.IdleStatus;
- import org.apache.mina.core.session.IoSession;
- import com.alibaba.fastjson.JSON;
- import com.isoftstone.upInterface.service.UPService;
- import com.isoftstone.upInterface.upConstant.UPConstant;
- public class HandlerOne extends IoHandlerAdapter
- {
- /**
- * 用来存储连接的缓存
- */
- protected static Map<IoSession, String> session_map =
- new HashMap<IoSession, String>();
- @Resource
- private UPService upService;
- /*
- * @Resource private CommunicationGateway com;
- */
- @Override
- public void messageReceived(IoSession session, Object message)
- throws Exception
- {
- System.out.println("message :" + message.toString());
- Map<IoSession, String> all = UPConstant.SESSION_MAP;
- // TODO Auto-generated method stub
- // super.messageReceived(session, message);
- Object succesResponse = JSON.parse(String.valueOf(message));
- Map map = (Map)succesResponse;
- // 心跳为上下线
- if (map.get("message-type").equals(UPConstant.MESSAGE_TYPE_HEART_BEAT))
- {
- upService.OnlineStatusChange(map);
- }
- // 实时数据上报
- if (map.get("message-type")
- .equals(UPConstant.MESSAGE_TYPE_REAL_DATA_REPORT))
- {
- upService.DataReport(map);
- }
- // 设备数据读取
- if (map.get("message-type").equals(UPConstant.MESSAGE_TYPE_READ_DATA))
- {
- upService.readData(map);
- }
- // 设备参数设置
- if (map.get("message-type").equals(UPConstant.MESSAGE_TYPE_READ_DATA))
- {
- upService.setData(map);
- }
- // 保存会话
- if (map != null)
- {
- all.put(session, String.valueOf(map.get("esn")));
- }
- System.out.println("message :" + message.toString());
- }
- @Override
- public void sessionClosed(IoSession session)
- throws Exception
- {
- // TODO Auto-generated method stub
- super.sessionClosed(session);
- Map<String, String> map = new HashMap<String, String>();
- map.put("esn", UPConstant.SESSION_MAP.get(session));
- map.put("message-type", UPConstant.MESSAGE_TYPE_HEART_BEAT);
- map.put("online-status", "offLine");
- upService.OnlineStatusChange(map);
- UPConstant.SESSION_MAP.remove(session);
- //
- }
- @Override
- public void sessionIdle(IoSession session, IdleStatus status)
- throws Exception
- {
- // TODO Auto-generated method stub
- super.sessionIdle(session, status);
- }
- @Override
- public void messageSent(IoSession session, Object message)
- throws Exception
- {
- super.messageSent(session, message);
- }
- @Override
- public void sessionCreated(IoSession session)
- throws Exception
- {
- super.sessionCreated(session);
- }
- @Override
- public void sessionOpened(IoSession session)
- throws Exception
- {
- super.sessionOpened(session);
- }
- }
结语:
今天给大家介绍了mina的组件结构和框架体系,并且用案例简单介绍了下mina的使用方法。在日常的开发中,其实已经足够了。