Mina框架与源码的简单理解
一.Mina架构图:
其框架实现如下图所示:(图来自于网上:)
二.Mina 一个请求的主要实现流程:
服务器启动时,构造NioSocketAcceptor,服务器同时也会构造NioProcessor。
client请求->NioSocketAcceptor建立连接,在bind监听端口后,调用startupAcceptor()方法->接收线程Acceptor启动->processHandles()方法调用->初始化session,并把session加入到NioProcessor待处理session队列中。
当始化session时,NioProcessor已经在通道中注册可读事件,同时add(T session)方法调用后->启动startupProcessor()方法->处理线程Processor启动->然后读事件读取数据时会在通道中执行过滤器连。
如果有相应的业务处理,则调用相应的IoHandler(),然后组织返回给client的数据,经过数据经过滤连,数据发出。
一般我们自己写nio服务器端时,只有一个selector,但mina框架使用了两个选择器selector,其中一个用来建立连接,其核心为NioSocketAcceptor
另一个负责读写事件的,其核心为NioProcessor
三.NioSocketAcceptor相关说明
1.NioSocketAcceptor的父类为AbstractPollingIoAcceptor,很多实现是在父类中实现的。在构造此类的时候,同时也构造了NioProcessor类,代码如下:
protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig,
Class<? extends IoProcessor<T>> processorClass) {
this(sessionConfig, null, new SimpleIoProcessorPool<T>(processorClass),
true);
}
并且NioProcessor的个数为cpu+1,
private static final int DEFAULT_SIZE = Runtime.getRuntime()
.availableProcessors() + 1;
2.init方法,打开selector通道;aceeptor()方法处理接收连接,把SocektChannel绑定到NioSession中;open()方法是打开serversocketchannel,然后配制socket的一些基本属性,并注册此事件是可连接的事件。
socket.bind(localAddress, getBacklog());
// Register the channel within the selector for ACCEPT event
channel.register(selector, SelectionKey.OP_ACCEPT);
3.父类中有内部线程类Acceptor,主要负责轮询处理注册过连接事件的请求建立起连接。此线程类是在startupAcceptor()方法中调用,代码如下:
private void startupAcceptor() {
// If the acceptor is not ready, clear the queues
// TODO : they should already be clean : do we have to do that ?
if (!selectable) {
registerQueue.clear();
cancelQueue.clear();
}
// start the acceptor if not already started
synchronized (lock) {
if (acceptor == null) {
acceptor = new Acceptor();
executeWorker(acceptor);
}
}
}
四.NioProcessor相关说明:
1.NioProcessor 在接收连接之后,另开了一个selector,用来处理读写事件。其大部分功能,在父类AbstractPollingIoProcessor中实现
2.Mina中用NioSession来封装了channel对应的读写操作等功能.
3.在NioProcessor类中init方法为:
protected void init(NioSession session) throws Exception {
SelectableChannel ch = (SelectableChannel) session.getChannel();
ch.configureBlocking(false);
session.setSelectionKey(ch.register(selector, SelectionKey.OP_READ, session));
}
很明显注册了读事件,并与相应的NioSession对应起来。
4.在NioProcessor类中表面上看代码好像没有注册过写事件,而且实际应用中,读写事件是来回切换的。但是实际上NioProcessor用另外的方法实现了:
setInterestedInRead()
setInterestedInWrite()
这两个方法通过key.interestOps(newInterestOps)来实现事件切换,从而达到了注册读写事件的作用。
要明白读写注册事件,首先得明白SocketChannel注册事件的原理,一个通道每次注册时只对应一个事件,而每次注册不同事件时,只是对此通道的事件进行了切换而已。所以通过key.interestOps(ops)方法是实现了最原始的操作,即切换通道事件。
5.NioSession中read与write方法同Buffer中一样。
6.IoSessionIterator<NioSession> an encapsulating iterator 。是NioProcess一个内部类
实现了iterator<NioSession> 接口的类,主要是重写了其方法public NioSession next(),目的是把迭代key时,把NioSession取出。
public NioSession next() {
SelectionKey key = iterator.next();
NioSession nioSession = (NioSession) key.attachment();
return nioSession;
}
五.NioProcessor 父类AbstractPollingIoProcessor的相关实现:
1.Processor,为父类的内部线程类,在startProcessor()方法调用,通过executor启动此线程。一般请求在NioSocketAcceptor上建立连接后,然后启动NioProcessor中的工作线程Processor。一般也不是马上企动的,需要在调用addNew方法后,有新的请求后才会调用。
2.此类中有四个ConcurrentLinkedQueue分别用来存储创建,删除,操作中的session。其中值的说明的是flushingSessions是用来写session的
3.executor为NioProcessor工作线程,默认实现为:Executors.newCachedThreadPool()来定义。
4.startupProcessor()方法,开始工作线程,此处把processor内部线程类放入线程池中运行。如果select()出现阻塞,才马上唤醒,在processor线程每次select时间为一秒
private void startupProcessor() {
synchronized (lock) {
if (processor == null) {
processor = new Processor();
executor.execute(new NamePreservingRunnable(processor,
threadName));
}
}
// Just stop the select() and start it again, so that the processor
// can be activated immediately.
wakeup();
}
5.handNewSessions(T session) 从新session队列中取出所有待处理的session.然后通过addNow(T Session)给session init(),buildfilterchain过滤这些等。最后返回处理了session的数量。另外也有对应的removeSession()方法
6.read(T session)是读取session里面的数据。如果读取数据的容易超过初始时设定的buffer大小时buffer会自动增加。写数据是通过writeBuffer()方法实现,往外写数据buffer大小也自动增加。
在此处要说明的,在此调用的read与writebuffer方法并不是最上层的方法,而是通过firechain连,调用fireMessageReceived(buf),以及fireMessageSent(session,buf)来读写。
请求到达时实例IoProcessor在会话通道中会执行过滤器链。
另外,如果此请求还有逻辑处理的话,接着也会执行相应的IoHandler。
六.Mina中IoProcessor处理方式
Mina 默认的线程工作方式为一个IoProcessor 负责执行一个会话上的所有过滤器、IoHandler,也就是对于IO 读写操作来说,是单线程工作方式(就是按照顺序逐个执行)。假如你想让某个事件方法(譬如:sessionIdle()、sessionOpened()等)在单独的线程中运行(也就是非IoProcessor 所在的线程),那么这里就需要用到一个ExecutorFilter 的过滤器。
增加一个ExecutorFilter代码如下:
acceptor.getFilterChain().addLast("exceutor", new ExecutorFilter()); 对于ExecutorFilter默认线程实现方式也可以修改。