mina是目前很流行的一个网络应用框架,用以帮助用户构建高性能和高伸缩性的网络应用。称其为网络应用框架,主要是其极强的扩展性,支持包括http,ssh,ftp等在内的多种应用层协议。而它本身封装了底层的TCP, UDP等通信协议,使用也非常方便。本文的重点是分析源码,了解它的多线程模型。源码版本为apache-mina-2.0.7。
为了有助于理解,先简单介绍一下mina的整体架构。如下图所示,其分为三层。在通信层之上,是IOService,提供IO服务,管理连接信息,它包含两个实现,一个是IOAcceptor,位于服务器,一个是IOConnector,处在客户端。在IOService之上,是IOFilterChain,作为一个容器,包含一系列IOFilter,做一些通用的数据处理,像协议的转换,多线程,日志等。最上层是IOHandler,负责执行用户的业务逻辑。
(来源http://mina.apache.org/mina-project/userguide/ch2-basics/application-architecture.html)
下面将按照服务器启动,连接监听,IO处理的顺序来对mina的多线程框架展开分析。
由此看来,mina对多线程的控制是非常精细的,尤其是IO处理线程与连接监听线程的分离,设计相当巧妙,值得学习。
为了有助于理解,先简单介绍一下mina的整体架构。如下图所示,其分为三层。在通信层之上,是IOService,提供IO服务,管理连接信息,它包含两个实现,一个是IOAcceptor,位于服务器,一个是IOConnector,处在客户端。在IOService之上,是IOFilterChain,作为一个容器,包含一系列IOFilter,做一些通用的数据处理,像协议的转换,多线程,日志等。最上层是IOHandler,负责执行用户的业务逻辑。
(来源http://mina.apache.org/mina-project/userguide/ch2-basics/application-architecture.html)
下面将按照服务器启动,连接监听,IO处理的顺序来对mina的多线程框架展开分析。
protected final Set<SocketAddress> bindInternal(List<? extends SocketAddress> localAddresses) throws Exception {
....
// cr eates the Acceptor instance and has the local
// executor kick it off.
startupAcceptor();
.....
}
private void startupAcceptor() throws InterruptedException {
......
// start the acceptor if not already started
Acceptor acceptor = acceptorRef.get();
if (acceptor == null) {
lock.acquire();
acceptor = new Acceptor();
if (acceptorRef.compareAndSet(null, acceptor)) {
executeWorker(acceptor);
} else {
lock.release();
}
}
}
protected final void executeWorker(Runnable worker, String suffix) {
String actualThreadName = threadName;
if (suffix != null) {
actualThreadName = actualThreadName + '-' + suffix;
}
executor.execute(new NamePreservingRunnable(worker, actualThreadName));
}
服务启动时,创建了一个Acceptor实例并在后台运行。Acceptor 主要用来监听用户的连接请求,看看代码:
private class Acceptor implements Runnable {
public void run() {
......
while (selectable) {
try {
// Detect if we have some keys ready to be processed
// The select() will be woke up if some new connection
// have occurred, or if the selector has been explicitly
// woke up
int selected = select();
// this actually sets the selector to OP_ACCEPT,
// and binds to the port on which this class will
// listen on
nHandles += registerHandles();
// Now, if the number of registred handles is 0, we can
// quit the loop: we don't have any socket listening
// for incoming connection.
if (nHandles == 0) {
......
}
if (selected > 0) {
// We have some connection request, let's process
// them here.
processHandles(selectedHandles());
}
// check to see if any cancellation request has been made.
nHandles -= unregisterHandles();
} catch (ClosedSelectorException cse) {
......
}
}
......
}
/**
* This method will process new sessions for the Worker class. All
* keys that have had their status updates as per the Selector.selectedKeys()
* method will be processed here. Only keys that are ready to accept
* connections are handled here.
* <p/>
* Session objects are created by making new instances of SocketSessionImpl
* and passing the session object to the SocketIoProcessor class.
*/
@SuppressWarnings("unchecked")
private void processHandles(Iterator<H> handles) throws Exception {
while (handles.hasNext()) {
H handle = handles.next();
handles.remove();
// Associates a new created connection to a processor,
// and get back a session
S session = accept(processor, handle);
if (session == null) {
continue;
}
initSession(session, null, null);
// add the session to the SocketIoProcessor
session.getProcessor().add(session);
}
}
}
Acceptor在监听到有连接时,创建了一个Session,并把该Session添加到IOProcessor的队列中。IOProcessor用来处理读写请求。为了进一步提高系统的性能,这里的IOProcessor默认实现为SimpleIOProcessorPool,池的大小默认为系统内核数+1。SimpleIOProcessorPool选择一个IOProcessor来进一步处理读写请求,代码如下:
public final void add(S session) {
getProcessor(session).add(session);
}
/**
* Find the processor associated to a session. If it hasen't be stored into
* the session's attributes, pick a new processor and stores it.
*/
@SuppressWarnings("unchecked")
private IoProcessor<S> getProcessor(S session) {
IoProcessor<S> processor = (IoProcessor<S>) session.getAttribute(PROCESSOR);
if (processor == null) {
if (disposed || disposing) {
throw new IllegalStateException("A disposed processor cannot be accessed.");
}
processor = pool[Math.abs((int) session.getId()) % pool.length];
if (processor == null) {
throw new IllegalStateException("A disposed processor cannot be accessed.");
}
session.setAttributeIfAbsent(PROCESSOR, processor);
}
return processor;
}
可以看出SimpleIOProcessorPool只是以简单轮转的方式来选择一个IOProcessor来处理Session。对于复杂的应用,也可以针对自己应用的特点定制IOProcessor。另外,值得注意的是,该Session与一个IOProcessor绑在了一起,这样实际的读写请求就只会在同一个线程内,避免了线程之间的同步。继续看IOProcessor的处理:
public final void add(S session) {
if (disposed || disposing) {
throw new IllegalStateException("Already disposed.");
}
// Adds the session to the newSession queue and starts the worker
newSessions.add(session);
startupProcessor();
}
/**
* Starts the inner Processor, asking the executor to pick a thread in its
* pool. The Runnable will be renamed
*/
private void startupProcessor() {
Processor processor = processorRef.get();
if (processor == null) {
processor = new Processor();
if (processorRef.compareAndSet(null, processor)) {
executor.execute(new NamePreservingRunnable(processor, threadName));
}
}
// Just stop the select() and start it again, so that the processor
// can be activated immediately.
wakeup();
}
Session先被加入到队列中,然后IOProcessor启动了一个新的线程(Processor)来处理队列中Session的IO请求。在收到读的请求之后,Processor会触发IOFilterChain来处理读到的数据。IOFilterChain按拦截器模式设计,支持扩展。而mina框架本身提供了一个ExecutorFilter来支持在应用层实现多线程处理。这样就避免了应用层的阻塞妨碍到IO的读写。
public final void messageReceived(NextFilter nextFilter, IoSession session, Object message) {
if (eventTypes.contains(IoEventType.MESSAGE_RECEIVED)) {
IoFilterEvent event = new IoFilterEvent(nextFilter, IoEventType.MESSAGE_RECEIVED, session, message);
fireEvent(event);
} else {
nextFilter.messageReceived(session, message);
}
}
protected void fireEvent(IoFilterEvent event) {
executor.execute(event);
}
注意在这里会首先检查当前的事件是否已被用户指定,只有已指定的事件才会并发处理。所以并法控制需要用户谨慎地选择。当然用户也可以实现自己的并发策略,类似之前的IO处理线程,将同一个session的数据绑定到一个线程。
由此看来,mina对多线程的控制是非常精细的,尤其是IO处理线程与连接监听线程的分离,设计相当巧妙,值得学习。