android(客户端)+Apache MINA(服务器端)通信的实现 智能家居动起来!

来源:互联网 发布:百度引擎是人工智能 编辑:程序博客网 时间:2024/05/11 04:49

         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架构图

简单地来讲,就分为三层:

  1. I/O Service :负责处理I/O。
  2. I/O Filter Chain :负责编码处理,字节到数据结构或数据结构到字节的转换等,即非业务逻辑的操作。
  3. I/O Handler :负责处理业务逻辑。

客户端的通信过程:

  1. 通过SocketConnector同服务器端建立连接。
  2. 链接建立之后I/O的读写交给了I/O Processor线程,I/O Processor是多线程的。
  3. 通过I/O Processor读取的数据经过IoFilterChain里所有配置的IoFilter,IoFilter进行消息的过滤,格式的转换,在这个层面可以制定一些自定义的协议。
  4. 最后IoFilter将数据交给Handler进行业务处理,完成了整个读取的过程。
  5. 写入过程也是类似,只是刚好倒过来,通过IoSession.write写出数据,然后Handler进行写入的业务处理,处理完成后交给IoFilterChain,进行消息过滤和协议的转换,最后通过I/O Processor将数据写出到socket通道。

IoFilterChain作为消息过滤链

  1. 读取的时候是从低级协议到高级协议的过程,一般来说从byte字节逐渐转换成业务对象的过程。
  2. 写入的时候一般是从业务对象到字节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.ConsoleAppenderlog4j.appender.stdout.layout=org.apache.log4j.PatternLayoutlog4j.appender.stdout.layout.ConversionPattern=[QC] %p [%t] %C.%M(%L) | %m%n   log4j.appender.R=org.apache.log4j.DailyRollingFileAppenderlog4j.appender.R.File=D:\\Tomcat 5.5\\logs\\qc.loglog4j.appender.R.layout=org.apache.log4j.PatternLayout1log4j.appender.R.layout.ConversionPattern=%d-[TS] %p %t %c - %m%n  log4j.logger.com.neusoft=DEBUGlog4j.logger.com.opensymphony.oscache=ERRORlog4j.logger.net.sf.navigator=ERRORlog4j.logger.org.apache.commons=ERRORlog4j.logger.org.apache.struts=WARNlog4j.logger.org.displaytag=ERRORlog4j.logger.org.springframework=DEBUGlog4j.logger.com.ibatis.db=WARNlog4j.logger.org.apache.velocity=FATAL  log4j.logger.com.canoo.webtest=WARN  log4j.logger.org.hibernate.ps.PreparedStatementCache=WARNlog4j.logger.org.hibernate=DEBUGlog4j.logger.org.logicalcobwebs=WARN  log4j.rootCategory=INFO, stdout , Rlog4j.appender.stdout=org.apache.log4j.ConsoleAppenderlog4j.appender.stdout.layout=org.apache.log4j.PatternLayoutlog4j.appender.stdout.layout.ConversionPattern=[QC] %p [%t] %C.%M(%L) | %m%n log4j.appender.R=org.apache.log4j.DailyRollingFileAppenderlog4j.appender.R.File=D:\\Tomcat 5.5\\logs\\qc.loglog4j.appender.R.layout=org.apache.log4j.PatternLayout1log4j.appender.R.layout.ConversionPattern=%d-[TS] %p %t %c - %m%nlog4j.logger.com.neusoft=DEBUGlog4j.logger.com.opensymphony.oscache=ERRORlog4j.logger.net.sf.navigator=ERRORlog4j.logger.org.apache.commons=ERRORlog4j.logger.org.apache.struts=WARNlog4j.logger.org.displaytag=ERRORlog4j.logger.org.springframework=DEBUGlog4j.logger.com.ibatis.db=WARNlog4j.logger.org.apache.velocity=FATALlog4j.logger.com.canoo.webtest=WARNlog4j.logger.org.hibernate.ps.PreparedStatementCache=WARNlog4j.logger.org.hibernate=DEBUGlog4j.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{@Overridepublic 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{@Overridepublic void exceptionCaught(IoSession session, Throwable cause)throws Exception {super.exceptionCaught(session, cause);}@Overridepublic void messageReceived(IoSession session, Object message)throws Exception {// TODO Auto-generated method stubSystem.out.println(message.toString()+"=====");super.messageReceived(session, message);}@Overridepublic void messageSent(IoSession session, Object message) throws Exception {// TODO Auto-generated method stubSystem.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;@Overrideprotected 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);}@Overridepublic void onClick(View v) {String message = text.getText().toString();//启动线程new MinaThread(message).start();}}


布局文件很简单,就一个文本输入框和一个按钮。就不贴了。

 

本文到这里就结束了。

 

本项目源码:http://download.csdn.net/detail/u013598660/8790993

0 0