项目中需要利用socket与其它部件通信,最初的版本中利用的原始的socket,并且使用短连接,阻塞形式的交互数据。此种交互方式对性能影响比较大,尤其是在交互非常频繁的情况下,瓶颈就凸显出来了。因此打算将此部分通信换成NIO的方式,对于NIO,一般的理解即是非阻塞式IO,也称New IO。Apache MINA便是基于NIO的一个通信框架。
MINA的官网:http://mina.apache.org/
个人认为MINA更适合于做异步的回调式的消息处理,因为框架提供了很好的回调机制:IoHandler,IoServiceListener。但是由于项目中的socket连接是给http请求使用的,暂时没有想到好的办法来用异步框架处理http请求,好像Spring 3.2以后就开始支持异步http请求了,引用一篇Spring 异步http请求的博文:http://13shu.iteye.com/blog/2021652,但是暂时不想使用异步http,所以将MINA的通信强制做成同步的,主要是要进行如下设置:
connector.getSessionConfig().setUseReadOperation(true);
官方的解释是:
public void setUseReadOperation(boolean useReadOperation)
IoSession.read()
operation. If enabled, all received messages are stored in an internal
BlockingQueue
so you can read received messages in more convenient way for client applications. Enabling this option is not useful to server applications and can cause unintended memory leak, and therefore it's disabled by default.
ReadFuture rf = session.read();
rf.awaitUninterruptibly();
if(rf.isDone() && rf.isRead() && null == rf.getException())
{
return rf.getMessage();
}
第二个值得注意的点是MINA的编码和解码器,MINA允许应用程序自定义通信编码和解码器,这样上层应用在调用IoSession发送数据时可以传送对象,接收数据也会返回解码后的对象。我们需要做的就是要为连接指定编码解码工厂(最后是一个简单的从socket读取一个以<EOF>结尾的字符串的示例解码器):
connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new StringCodecFactory()));
对于大量数据的传送,我们需要继承CumulativeProtocolDecoder解码,此类可以在一个buffer中没有接收到全部数据的情况下反复解码,直到解码出我们的完整报文才往上层发送messageReceived事件。
使用MINA的另外一个优势就是方便的利用MINA的心跳机制,这里引用一篇MINA心跳的介绍:http://my.oschina.net/yjwxh/blog/174633
使用过程中发现,用MINA长连接通信,确实比原先的原来每次new socket效率要高很多,并且由于加入了心跳机制,连接更稳定了。之前如果用原始socket做长连接不发送心跳的话,连接很容易断掉,出现pipe broken错误。
本篇主要是介绍一下MINA的基本使用方法,下一篇介绍一下apache pool2的对象池,并将pool2和MINA结合,构造一个长连接池。
package com.wen.rec;
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;
/**
* 与socket通信的协议解码类,socket每个报文都是以<EOF>结尾
* @author wendellpeng
*
*/
public class MyProtocolDecoder extends CumulativeProtocolDecoder {
private byte[] EOF = "<EOF>".getBytes();
@Override
protected boolean doDecode(IoSession session, IoBuffer in,
ProtocolDecoderOutput out) throws Exception {
int start = in.position();
// Now find the first <EOF>'s position in the buffer.
int eofPos = KMP_Index2(in,EOF);
//<EOF> exists
if(-1 == eofPos)
{
return false;
}else
{
int position = eofPos+EOF.length;// message ends in eofPos + target.length;
int limit = in.limit() ;//- eofPos - target.length;
try {
in.position(start);
in.limit(position);
// The bytes between in.position() and in.limit()
// now contain a full CRLF terminated line.
out.write(parseCommand(in.slice()));
// Decoded one line; CumulativeProtocolDecoder will
// call me again until I return false. So just
// return true until there are no more lines in the
// buffer.
return true;
} finally {
// Set the position to point right after the
// detected line and set the limit to the old
// one.
in.position(position);
in.limit(limit);
}
}
}
private Object parseCommand(IoBuffer slice) {
String str = new String(slice.array(),slice.position(),slice.limit()-EOF.length);
return str;
}
public static int[] next(byte[] t)
{
int[] next = new int[t.length];
next[0] = -1;
int i = 0;
int j = -1;
while (i < t.length - 1) {
if (j == -1 || t[i] == t[j]) {
i++;
j++;
if (t[i] != t[j]) {
next[i] = j;
} else {
next[i] = next[j];
}
} else {
j = next[j];
}
}
return next;
}
/**
* KMP匹配字符串
*
* @param buf 从buf的position位置开始找
*
* @param t
* 模式串
* @return 若匹配成功,返回模式开始的position,否则返回-1
*/
public static int KMP_Index2(IoBuffer buf, byte[] t) {
int position = buf.position();
int i = buf.position();
int j = 0;
try
{
int[] next = next(t);
while (buf.hasRemaining() && j <= t.length - 1) {
if (j == -1 || buf.get() == t[j]) {
i++;
j++;
} else {
j = next[j];
}
}
}finally
{
buf.position(position);
}
if (j < t.length) {
return -1;
} else
return i - t.length; // 返回模式串在主串中的头下标
}
}