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

         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时最主要关注的地方。

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

                                                                                                                                        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.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);
    }
		
}


服务器端这边程序已经写完了,下面用网络调试助手来测试。

 

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

          这里需要注意的是发送消息的是采用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

你可能感兴趣的:(apache,spring,安卓,session,网络应用)