«

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

时间:2024-3-2 19:18     作者:韩俊     分类: Android


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>

标签: android

热门推荐