android推送,androidpn项目分析及完善学习笔记(二) Mina

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/specialshoot/article/details/50684912
上回说道,socket是基本的网络通讯方式,然而,真正的工程考虑到高并发性、高可靠性等要求,基本的socket是肯定无法用到项目上的。好在Apache开发出了一套高性能和高可用性的网络应用程序框架,这个框架就是大名鼎鼎的Mina框架。
废话不多说,首先Mina的jar包大家可以从http://mina.apache.org/downloads-mina.html上下载最新版本,我用的是2.0.9版本,截止到我写这篇博客的时候,最新的已经是2.0.12版本了。Binaries是不带源码的版本,普通项目用的话,下载这个就行,如果大家要看下mina的源码,可以下载sources带源码的版本。下载完后解压,找到dist文件夹,mina-core-版本号.jar这个jar包是我们需要包含到项目中去的。此外,lib文件夹下的slf4j的jar包也是我们这个代码所需要的(用于日志)。至于jar包怎么放到项目中相信不需要我说大家已经了如指掌了,这里就不细说了。

服务器端

使用Mina最基本的功能仅需要四步:
1.创建NioSocketAcceptor对象。
2.设置Handler,传入一个专门处理消息收发的handler,这个handler要继承IoHandlerAdapter。
3.设置过滤器,可以是自定义过滤器,也可以是默认的,这里我们设置默认的文本过滤器,因为我们现在仅仅是传递文本,至于自定义过滤器的写法,后面我会说道。
4.绑定端口号
NioSocketAcceptor对象也可以设置一些属性比如设置服务器空闲时长等,多用于心跳机制,做推送的时候会用到这个。
服务器端代码:
MinaServer.java:
package com.imooc.server;

import java.io.IOException;
import java.net.InetSocketAddress;

import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;

public class MinaServer {

	public static void main(String[] args) {
		try {
			NioSocketAcceptor acceptor = new NioSocketAcceptor();
			acceptor.setHandler(new MyServerHandler());
			//添加mina过滤器,getFilterChain()可以得到所有过滤器
			//addlast添加一个拦截器,第一个参数是拦截器名字,自己起一个,第二个参数是拦截器
			//ProtocolCodecFilter是用于二进制数据与对象转换,参数是ProtocolCodecFactory
			//可以传入默认的参数比如TextLineFactory,内置好的,专门读取文本的factory
			acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory()));
			//这里我们传入自定义factory
			//acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new MyTextLineFactory()));
			//setIdleTime(IdleStatus,arg1)设置服务器空闲时长
			//IdelStatus可选三种参数,BOTH_IDEL,READER_IDLE,WRITER_IDLE,分别代表发送接收均超过时长没有响应,接收超过时长没有响应,发送超过时长没有响应
			//第二个是时长,单位是秒
			acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 5);
			acceptor.bind(new InetSocketAddress(9898));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}
IoHandlerAdapter是用于处理消息收发的,其中有一些方法需要复写
messageReceived:当收到消息后进入此方法
messageSent:发送消息时进入此方法
sessionClosed:客户端与服务器端断开连接进入此方法
sessionCreated: 客户端与服务器端建立连接进入此方法
sessionIdle:会话进入空闲时进入此方法
sessionOpened:会话打开时进入此方法(这个方法会在sessionCreated执行后执行)
下面代码中,我们仅仅在收到消息后,把消息再发送给客户端,用write方法。
MyServerHandler.java(继承IoHandlerAdapter):
package com.imooc.server;

import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;

public class MyServerHandler extends IoHandlerAdapter {

	//网络连接出现异常
	@Override
	public void exceptionCaught(IoSession session, Throwable cause)
			throws Exception {
		System.out.println("exceptionCaught");
	}

	//收到消息进入此方法
	@Override
	public void messageReceived(IoSession session, Object message)
			throws Exception {
		String s = (String) message;
		System.out.println("messageReceived: " + s);
		session.write("server reply: " + s);	//返回消息给客户端
	}

	//发送消息会进入此方法
	@Override
	public void messageSent(IoSession session, Object message) throws Exception {
		System.out.println("messageSent");
	}

	//客户端与服务器会话断开连接时进入此方法
	@Override
	public void sessionClosed(IoSession session) throws Exception {
		System.out.println("sessionClosed");
	}

	//客户端与服务器会话创建时进入此方法
	@Override
	public void sessionCreated(IoSession session) throws Exception {
		System.out.println("sessionCreated");
	}

	//会话进入空闲状态进入此方法
	@Override
	public void sessionIdle(IoSession session, IdleStatus status)
			throws Exception {
		System.out.println("sessionIdle");
	}

	//会话打开进入此方法
	@Override
	public void sessionOpened(IoSession session) throws Exception {
		System.out.println("sessionOpened");
	}
	
}
为了了解过滤器的原理及方便以后自定义比如解析xml,json等文件,下面我们自己写一个自定义过滤器
我们需要创建加解密的对象,MyTextLineDecoder和MyTextLineEncoder,至于MyTextLineCumulativeDecoder是另外一种解码器,我们稍后再说。
继承ProtocolCodecFactory,我们需要实现两个方法,getDecoder和getEncoder分别得到解码器和编码器
我们创建一个MyTextLineFactory.java
package com.imooc.server;

import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFactory;
import org.apache.mina.filter.codec.ProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolEncoder;

public class MyTextLineFactory implements ProtocolCodecFactory {
	
	private MyTextLineDecoder mDecoder;
	
	private MyTextLineCumulativeDecoder mCumulativeDecoder;
	
	private MyTextLineEncoder mEncoder;
	
	public MyTextLineFactory () {
		mDecoder = new MyTextLineDecoder();
		mEncoder = new MyTextLineEncoder();
		mCumulativeDecoder = new MyTextLineCumulativeDecoder();
	}

	//解密
	@Override
	public ProtocolDecoder getDecoder(IoSession arg0) throws Exception {
		//return mCumulativeDecoder;
		return mDecoder;
	}

	//加密
	@Override
	public ProtocolEncoder getEncoder(IoSession arg0) throws Exception {
		return mEncoder;
	}

}
MyTextLineDecoder类继承ProtocolDecoder.
decode方法是解码方法,dispose方法用于在销毁编码器时释放关联资源,finishDecode用于处理在IoSession关闭时剩余的读取数据。这里我们尽在decode中添加我们的代码。详细的ProtocolDecoder讲解参看博客http://www.himigame.com/apache-mina/839.html
MyTextLineDecoder.java:
package com.imooc.server;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;

public class MyTextLineDecoder implements ProtocolDecoder {

	@Override
	public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out)
			throws Exception {
		int startPosition = in.position();	//开始读取的位置
		while (in.hasRemaining()) {	//当iobuffer中还有字节可以读取时
			byte b = in.get();	//读字节
			if (b == '\n') {
				int currentPositoin = in.position();	//记录当前读取的位置
				int limit = in.limit();	//当前总长度
				in.position(startPosition);	//定向到起始位置
				in.limit(currentPositoin);	//终点为当前position
				IoBuffer buf = in.slice();	//slice截取从起始位置到终点位置的字节
				byte [] dest = new byte[buf.limit()];
				buf.get(dest);
				String str = new String(dest);
				out.write(str);
				in.position(currentPositoin);	//重定向,还原到当前position
				in.limit(limit);	//还原到原来的limit
			}
		}
	}

	@Override
	public void dispose(IoSession arg0) throws Exception {

	}

	@Override
	public void finishDecode(IoSession arg0, ProtocolDecoderOutput arg1)
			throws Exception {

	}

}

关于Mina的IoBuffer的使用,参见 http://blog.csdn.net/u012841509/article/details/17259025http://www.cnblogs.com/sunwei2012/archive/2010/08/27/1810089.html
这里面的操作是将每行的文字提取出来并输出。
Encoder加密就比较简单了,我们发送出去的内容都要encode一下,代码如下:
MyTextLineEncoder.java:
package com.imooc.server;

import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoder;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;

public class MyTextLineEncoder implements ProtocolEncoder {

	@Override
	public void dispose(IoSession arg0) throws Exception {

	}

	@Override
	public void encode(IoSession session, Object message, ProtocolEncoderOutput out)
			throws Exception {
		String s =null;
		if (message instanceof String) {
			//判断是否message是否为字符串
			s = (String) message;
		}
		if (s != null) {
			//转码操作
			CharsetEncoder charsetEndoer = (CharsetEncoder)session.getAttribute("encoder");
			if (charsetEndoer == null) {
				charsetEndoer = Charset.defaultCharset().newEncoder();
				session.setAttribute("encoder", charsetEndoer);
			}
			IoBuffer ioBuffer = IoBuffer.allocate(s.length());	//开辟内存
			ioBuffer.setAutoExpand(true);	//设置内存自动扩展
			ioBuffer.putString(s, charsetEndoer);	//将字符串放入ioBuffer中
			ioBuffer.flip();
			out.write(ioBuffer);
		}
	}
}
这样我们的编解码就写完了。
下面我们来测试服务器端,用上一节课的SocketClient客户端给服务器端发消息。我们SocketClient中上节中提到有如下代码用于测试:
if (count % 2 ==0) {
	writer.write("\n");
}
count++;
这里就可以说明此代码测试的意义,我让客户端先发送一个带换行符的字符串,然后发送不带换行符的字符串,然后再发不带换行符号,运行前注意将MinaServer.java中acceptor.getFilterChain().addLast里面改为自定义的MyTextLineFactory(我在源码中注释掉的,把注释去了然后把用默认Factory的那句给注释上)。让我们来看看运行效果:
客户端发送
客户端发送

服务器接收
服务器接收

大家可以很明显的看出问题,当客户端没有发送换行符的时候,服务器肯定没有响应,因为没有换行符作为结束,这个是没有问题的,然而后面有换行符的时候也就是输入bb后,服务器应该接收的是aabb,但是显示出来的只有bb,这就是数据丢失问题。我们怎么解决它呢?
这时候就用到了我们刚才没有介绍的CumulativeProtocolDecoder,这个类是Mina专门处理包和粘包的。下面看代码:
MyTextLineCumulativeDecoder.java
package com.imooc.server;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.CumulativeProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;

//MyTextLineCumulativeDecoder与MyTextLineDecoder差不多,这里返回一个boolean类型,用此Decoder可以防止数据丢失
public class MyTextLineCumulativeDecoder extends CumulativeProtocolDecoder {

	@Override
	protected boolean doDecode(IoSession session, IoBuffer in,
			ProtocolDecoderOutput out) throws Exception {
		int startPosition = in.position();
		while (in.hasRemaining()) {
			byte b = in.get();
			if (b == '\n') {
				int currentPositoin = in.position();
				int limit = in.limit();
				in.position(startPosition);
				in.limit(currentPositoin);
				IoBuffer buf = in.slice();
				byte [] dest = new byte[buf.limit()];
				buf.get(dest);
				String str = new String(dest);
				out.write(str);
				in.position(currentPositoin);
				in.limit(limit);
				return true;
			}
		}
		in.position(startPosition);	//如果没有\n换行符,保持position在初始位置
		return false;
	}

}
这里仅仅重写doDecode即可,代码整体与MyTextLineDecoder.java基本相同。有换行符的时候返回true,没有换行符的时候将起始位置设置为startPosition然后返回false即可。
下面运行测试,注意把MyTextLineFactory中getDecoder方法返回mCumulativeDecoder。
服务器返回结果
这次是正确的输出了。

客户端

服务器端我们讲解完了,下面讲解Mina客户端,这部分不是androidpn项目必须的,还是说明一下做个记录:
MinaClient.java:
package com.imooc.client;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;

import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.transport.socket.nio.NioSocketConnector;

public class MinaClient {

	public static void main(String[] args) throws Exception{
		NioSocketConnector connector = new NioSocketConnector();
		connector.setHandler(new MyClientHandler());
		connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory()));
		ConnectFuture future = connector.connect(new InetSocketAddress("127.0.0.1", 9898));	//connect方法返回一个ConnectFuture对象
		future.awaitUninterruptibly();	//阻塞住直到连接建立成功
		IoSession session = future.getSession();	//得到session,得到session下面就可以写数据了,session.write
		BufferedReader inputReader = new BufferedReader(new InputStreamReader(System.in));
		String inputContent;
		while (!(inputContent = inputReader.readLine()).equals("bye")) {
			session.write(inputContent);
		}
	}

}
MyClientHandler.java:
package com.imooc.client;

import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;

public class MyClientHandler extends IoHandlerAdapter {

	@Override
	public void exceptionCaught(IoSession session, Throwable cause)
			throws Exception {
		System.out.println("exceptionCaught");
	}

	@Override
	public void messageReceived(IoSession session, Object message)
			throws Exception {
		String s = (String) message;
		System.out.println("messageReceived: " + s);
	}

	@Override
	public void messageSent(IoSession session, Object message) throws Exception {
		System.out.println("messageSent");
	}

	@Override
	public void sessionClosed(IoSession session) throws Exception {
		System.out.println("sessionClosed");
	}

	@Override
	public void sessionCreated(IoSession session) throws Exception {
		System.out.println("sessionCreated");
	}

	@Override
	public void sessionIdle(IoSession session, IdleStatus status)
			throws Exception {
		System.out.println("sessionIdle");
	}

	@Override
	public void sessionOpened(IoSession session) throws Exception {
		System.out.println("sessionOpened");
	}
	
}
下一篇文章记录一下关于xmpp协议的有关内容。

猜你喜欢

转载自blog.csdn.net/specialshoot/article/details/50684912