Apache Mina是一个能够帮助用户开发高性能和高伸缩性网络应用程序的框架。它通过Java nio技术基于TCP/IP和UDP/IP协议提供了抽象的、事件驱动的、异步的API。是 Apache 组织一个较新的项目,它为开发高性能和高可用性的网络应用程序提供了非常便利的框架。当前发行的 MINA 版本支持基于 Java NIO 技术的 TCP/UDP 应用程序开发、串口通讯程序(只在最新的预览版中提供),MINA 所支持的功能也在进一步的扩展中。目前正在使用 MINA 的软件包括有:Apache Directory Project、AsyncWeb、AMQP(Advanced Message Queuing Protocol)、RED5 Server(Macromedia Flash Media RTMP)、ObjectRADIUS、Openfire 等等。
这个是比较官方的说法,在实际开发中,比如游戏开发,智能家居中要求加入及时通信功能,这样就能达到了直接通过手机发送报文信息给这些可爱的电器们。首先我们要介绍一下mina的相关知识,篇幅有点长,如果直接想例子的话,可以跳过这一段。
一、Mina包几个比较重要的接口:
IoServiece :这个接口在一个线程上负责套接字的建立,拥有自己的 Selector,监听是否有连接被建立。
IoProcessor :这个接口在另一个线程上负责检查是否有数据在通道上读写,也就是说它也拥有自己的 Selector,这是与我们使用 JAVA NIO 编码时的一个不同之处,通常在 JAVA NIO 编码中,我们都是使用一个 Selector,也就是不区分 IoService与 IoProcessor 两个功能接口。另外,IoProcessor 负责调用注册在 IoService 上的过滤器,并在过滤器链之后调用 IoHandler。
IoAccepter :相当于网络应用程序中的服务器端
IoConnector :相当于客户端
IoSession :当前客户端到服务器端的一个连接实例
IoHandler :这个接口负责编写业务逻辑,也就是接收、发送数据的地方。这也是实际开发过程中需要用户自己编写的部分代码。
IoFilter :过滤器用于悬接通讯层接口与业务层接口,这个接口定义一组拦截器,这些拦截器可以包括日志输出、黑名单过滤、数据的编码(write 方向)与解码(read 方向)等功能,其中数据的 encode与 decode是最为重要的、也是你在使用 Mina时最主要关注的地方。
MIINA架构图
简单地来讲,就分为三层:
I/O Service :负责处理I/O。
I/O Filter Chain :负责编码处理,字节到数据结构或数据结构到字节的转换等,即非业务逻辑的操作。
I/O Handler :负责处理业务逻辑。
客户端的通信过程:
通过SocketConnector同服务器端建立连接。
链接建立之后I/O的读写交给了I/O Processor线程,I/O Processor是多线程的。
通过I/O Processor读取的数据经过IoFilterChain里所有配置的IoFilter,IoFilter进行消息的过滤,格式的转换,在这个层面可以制定一些自定义的协议。
最后IoFilter将数据交给Handler进行业务处理,完成了整个读取的过程。
写入过程也是类似,只是刚好倒过来,通过IoSession.write写出数据,然后Handler进行写入的业务处理,处理完成后交给IoFilterChain,进行消息过滤和协议的转换,最后通过I/O Processor将数据写出到socket通道。
IoFilterChain作为消息过滤链
读取的时候是从低级协议到高级协议的过程,一般来说从byte字节逐渐转换成业务对象的过程。
写入的时候一般是从业务对象到字节byte的过程。
客户端通信过程 IoSession贯穿整个通信过程的始终
二、通过网络调试助手当成客户端发送消息给服务器端。
这里服务器端采用的Struts2+Spring4+Mybatis3+Mina2,客户端先用网络调试助手,后面用android移动终端。
(1)Apache官方网站:http://mina.apache.org/downloads.html,大家直接下载即可。
(2)服务器端一共要用到四个jar包,包括一个日志包。将他们放在lib中,并加载进去
mina-core-2.0.7.jar slf4j-log4j12-1.7.6.jar slf4j-api-1.7.6.jar log4j-1.2.14.jar (日志管理包)
(3)如果要使用日志的jar包,则要在项目的src目录下新建一个log4j.properties,添加内容如下:
log4j.rootCategory=INFO, stdout , R log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=[QC] %p [%t] %C.%M(%L) | %m%n log4j.appender.R=org.apache.log4j.DailyRollingFileAppender log4j.appender.R.File=D:\Tomcat 5.5\logs\qc.log log4j.appender.R.layout=org.apache.log4j.PatternLayout 1log4j.appender.R.layout.ConversionPattern=%d-[TS] %p %t %c - %m%n log4j.logger.com.neusoft=DEBUG log4j.logger.com.opensymphony.oscache=ERROR log4j.logger.net.sf.navigator=ERROR log4j.logger.org.apache.commons=ERROR log4j.logger.org.apache.struts=WARN log4j.logger.org.displaytag=ERROR log4j.logger.org.springframework=DEBUG log4j.logger.com.ibatis.db=WARN log4j.logger.org.apache.velocity=FATAL log4j.logger.com.canoo.webtest=WARN log4j.logger.org.hibernate.ps.PreparedStatementCache=WARN log4j.logger.org.hibernate=DEBUG log4j.logger.org.logicalcobwebs=WARN log4j.rootCategory=INFO, stdout , R log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=[QC] %p [%t] %C.%M(%L) | %m%n log4j.appender.R=org.apache.log4j.DailyRollingFileAppender log4j.appender.R.File=D:\Tomcat 5.5\logs\qc.log log4j.appender.R.layout=org.apache.log4j.PatternLayout 1log4j.appender.R.layout.ConversionPattern=%d-[TS] %p %t %c - %m%n log4j.logger.com.neusoft=DEBUG log4j.logger.com.opensymphony.oscache=ERROR log4j.logger.net.sf.navigator=ERROR log4j.logger.org.apache.commons=ERROR log4j.logger.org.apache.struts=WARN log4j.logger.org.displaytag=ERROR log4j.logger.org.springframework=DEBUG log4j.logger.com.ibatis.db=WARN log4j.logger.org.apache.velocity=FATAL log4j.logger.com.canoo.webtest=WARN log4j.logger.org.hibernate.ps.PreparedStatementCache=WARN log4j.logger.org.hibernate=DEBUG log4j.logger.org.logicalcobwebs=WARN
(4)服务器端程序
1、mina2与spring整合
<!-- 字符编 码过滤器 不发中文不需要--> <bean id="codecFilter" class="org.apache.mina.filter.codec.ProtocolCodecFilter"> <constructor-arg> <bean class="com.th.ssi.mina.util.TextLineChineseCodecFactory"></bean> </constructor-arg> </bean> <!-- 日志过滤器 --> <bean id="loggingFilter" class="org.apache.mina.filter.logging.LoggingFilter" /> <!-- 过滤器链 --> <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="codecFilter" /> </map> </property> </bean> <!-- 设置 I/O 接受器,并指定接收到请求后交给 myHandler 进行处理 --> <bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="customEditors"> <map> <entry key="java.net.SocketAddress" value="org.apache.mina.integration.beans.InetSocketAddressEditor"/> </map> </property> </bean> <!-- 定义数据处理Bean --> <bean id="myHandler" class="com.th.ssi.mina.handler.HelloWorldServerHandler" /> <!-- IoAccepter,绑定到1234端口 --> <bean id="ioAcceptor" class="org.apache.mina.transport.socket.nio.NioSocketAcceptor" init-method="bind" destroy-method="unbind"> <property name="defaultLocalAddress" value=":1234" /> <property name="handler" ref="myHandler" /> <property name="reuseAddress" value="true" /> <property name="filterChainBuilder" ref="filterChainBuilder" /> </bean>
这里绑定的端口号是1234,handler由HelloWorldServerHandler类处理。字符编码过滤器由TextLineChineseCodecFactory处理。
2、HelloWorldServerHandler类
import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.session.IdleStatus; import org.apache.mina.core.session.IoSession; public class HelloWorldServerHandler extends IoHandlerAdapter{ @Override public void messageReceived(IoSession session, Object message)throws Exception { System.out.println(message.toString()+"====="); } //创建新的连接 @Override public void sessionOpened(IoSession session) throws Exception { System.out.println("Open Session:"+session.getId()+"成功连接了一个"); System.out.println(session.getLastReadTime()); } //关闭连接 @Override public void sessionClosed(IoSession session) throws Exception { System.out.println("Close Session:"+session.getId()); } //输出错误的堆栈信息并关闭会话 @Override public void exceptionCaught( IoSession session, Throwable cause ) throws Exception { //打印异常信息 cause.printStackTrace(); System.out.println("Session id >> "+session.getId()+" << " +cause.getMessage()); } //会话空闲的时间达到设定的时间时被调用 @Override public void sessionIdle( IoSession session, IdleStatus status ) throws Exception { //System.out.println( "IDLE " + session.getIdleCount( status )); } }
TextLineChineseCodecFactory类
public class TextLineChineseCodecFactory implements ProtocolCodecFactory { private final TextLineEncoder encoder; private final TextLineChineseDecoder decoder; /** * Creates a new instance with the current default {@link Charset}. */ public TextLineChineseCodecFactory() { //this(Charset.defaultCharset()); this(Charset.forName( "UTF-8" )); } /** * Creates a new instance with the specified {@link Charset}. The * encoder uses a UNIX {@link LineDelimiter} and the decoder uses * the AUTO {@link LineDelimiter}. * * @param charset * The charset to use in the encoding and decoding */ public TextLineChineseCodecFactory(Charset charset) { encoder = new TextLineEncoder(charset, LineDelimiter.UNIX); decoder = new TextLineChineseDecoder(charset, LineDelimiter.AUTO); } /** * Creates a new instance of TextLineCodecFactory. This constructor * provides more flexibility for the developer. * * @param charset * The charset to use in the encoding and decoding * @param encodingDelimiter * The line delimeter for the encoder * @param decodingDelimiter * The line delimeter for the decoder */ public TextLineChineseCodecFactory(Charset charset, String encodingDelimiter, String decodingDelimiter) { encoder = new TextLineEncoder(charset, encodingDelimiter); decoder = new TextLineChineseDecoder(charset, decodingDelimiter); } /** * Creates a new instance of TextLineCodecFactory. This constructor * provides more flexibility for the developer. * * @param charset * The charset to use in the encoding and decoding * @param encodingDelimiter * The line delimeter for the encoder * @param decodingDelimiter * The line delimeter for the decoder */ public TextLineChineseCodecFactory(Charset charset, LineDelimiter encodingDelimiter, LineDelimiter decodingDelimiter) { encoder = new TextLineEncoder(charset, encodingDelimiter); decoder = new TextLineChineseDecoder(charset, decodingDelimiter); } public ProtocolEncoder getEncoder(IoSession session) { return encoder; } public ProtocolDecoder getDecoder(IoSession session) { return decoder; } /** * Returns the allowed maximum size of the encoded line. * If the size of the encoded line exceeds this value, the encoder * will throw a {@link IllegalArgumentException}. The default value * is {@link Integer#MAX_VALUE}. * <p> * This method does the same job with {@link TextLineEncoder#getMaxLineLength()}. */ public int getEncoderMaxLineLength() { return encoder.getMaxLineLength(); } /** * Sets the allowed maximum size of the encoded line. * If the size of the encoded line exceeds this value, the encoder * will throw a {@link IllegalArgumentException}. The default value * is {@link Integer#MAX_VALUE}. * <p> * This method does the same job with {@link TextLineEncoder#setMaxLineLength(int)}. */ public void setEncoderMaxLineLength(int maxLineLength) { encoder.setMaxLineLength(maxLineLength); } /** * Returns the allowed maximum size of the line to be decoded. * If the size of the line to be decoded exceeds this value, the * decoder will throw a {@link BufferDataException}. The default * value is <tt>1024</tt> (1KB). * <p> * This method does the same job with {@link TextLineDecoder#getMaxLineLength()}. */ public int getDecoderMaxLineLength() { return decoder.getMaxLineLength(); } /** * Sets the allowed maximum size of the line to be decoded. * If the size of the line to be decoded exceeds this value, the * decoder will throw a {@link BufferDataException}. The default * value is <tt>1024</tt> (1KB). * <p> * This method does the same job with {@link TextLineDecoder#setMaxLineLength(int)}. */ public void setDecoderMaxLineLength(int maxLineLength) { decoder.setMaxLineLength(maxLineLength); } }
服务器端这边程序已经写完了,下面用网络调试助手来测试。
这里需要注意的是发送消息的是采用16进制数据在末尾点击 0D 0A 表示换行,网络调试助手才认为你已经输入消息输完了,服务器端才能获取数据。消息如下:
2015-06-10 08:55:42.096 [INFO ] org.apache.mina.filter.logging.LoggingFilter {LoggingFilter.java:157} - RECEIVED: HeapBuffer[pos=0 lim=4 cap=512: 31 23 0D 0A]
1#=====
三、通过安卓客户端发送消息。
需要两个jar包, mina-core-2.0.7.jar slf4j-android-1.6.1-RC1.jar 。百度直接搜索下载即可。
由于接受消息会阻塞Android的进程,所以我把它开在了子线程中。
package com.xby.minatest; import java.net.InetSocketAddress; import java.nio.charset.Charset; import org.apache.mina.core.future.ConnectFuture; import org.apache.mina.core.service.IoConnector; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.filter.codec.textline.LineDelimiter; import org.apache.mina.filter.codec.textline.TextLineCodecFactory; import org.apache.mina.transport.socket.nio.NioSocketConnector; import android.util.Log; public class MinaThread extends Thread{ IoSession session = null; String message ; public MinaThread(String message){ this.message = message; } @Override public void run() { Log.e("TEST", "客户端连接开始……"); IoConnector connector = new NioSocketConnector(); connector.setConnectTimeoutMillis(30000); connector.getFilterChain().addLast("codec", new ProtocolCodecFilter( new TextLineCodecFactory(Charset.forName("UTF-8"), LineDelimiter.WINDOWS.getValue(), LineDelimiter.WINDOWS.getValue()))); connector.setHandler(new MinaClientHandler()); try { ConnectFuture future = connector.connect(new InetSocketAddress(ConstantUtil.WEB_MATCH_PATH,ConstantUtil.WEB_MATCH_PORT)); future.awaitUninterruptibly(); session = future.getSession(); session.write(message); } catch (Exception e) { Log.d("TEST","客户端链接异常..."); } session.getCloseFuture().awaitUninterruptibly(); Log.d("TEST","客户端断开..."); connector.dispose(); super.run(); } }
需要注意的是connector.setHandler()这个方法,发送消息由MinaClientHandler类处理。
MinaClientHandler类
package com.xby.minatest; import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.session.IoSession; public class MinaClientHandler extends IoHandlerAdapter{ @Override public void exceptionCaught(IoSession session, Throwable cause) throws Exception { super.exceptionCaught(session, cause); } @Override public void messageReceived(IoSession session, Object message) throws Exception { // TODO Auto-generated method stub System.out.println(message.toString()+"====="); super.messageReceived(session, message); } @Override public void messageSent(IoSession session, Object message) throws Exception { // TODO Auto-generated method stub System.out.println("====="+message.toString()); super.messageSent(session, message); } }
是不是跟服务器端很相似,就那么几个方法。
接下在MainActivity中调用即可。
package com.xby.minatest; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; public class MainActivity extends Activity implements OnClickListener{ private EditText text; private Button sendBtn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); text = (EditText) findViewById(R.id.edit_text); sendBtn = (Button) findViewById(R.id.sendBtn); sendBtn.setOnClickListener(this); } @Override public void onClick(View v) { String message = text.getText().toString(); //启动线程 new MinaThread(message).start(); } }
布局文件很简单,就一个文本输入框和一个按钮。就不贴了。
本文到这里就结束了。
本项目源码:http://download.csdn.net/detail/u013598660/8790993
<p>版权声明:本文为博主原创文章,未经博主允许不得转载。</p>