一个IoSession的I/O事件是注册在一个Selector对象上,并且每个Processor线程只轮询一个Selector对象,即每一个链接只有一个线程处理I/O事件,这样能保证同一IoSession数据的有序性。
下面就从部分源码探究其中的原理,以NioAcceptor为例子:
public NioSocketAcceptor() {
super(new DefaultSocketSessionConfig(), NioProcessor.class);
((DefaultSocketSessionConfig) getSessionConfig()).init(this);
}
这里的NioProcessor.class就是Processor的具体类型。
protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, Class extends IoProcessor> processorClass) {
this(sessionConfig, null, new SimpleIoProcessorPool(processorClass), true, null);
}
SimpleIoProcessorPool是Processor的线程池,使用NioProcessor创建具体的线程。
跳过Acceptor的初始化过程,当客户端请求建立链接,服务端Acceptor线程会执行以下代码:
private void processHandles(Iterator 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); //这里的processor是processor线程池
if (session == null) {
continue;
}
initSession(session, null, null);
// add the session to the SocketIoProcessor
session.getProcessor().add(session);
}
}
@Override
protected NioSession accept(IoProcessor processor, ServerSocketChannel handle) throws Exception {
SelectionKey key = null;
if (handle != null) {
key = handle.keyFor(selector);
}
if ((key == null) || (!key.isValid()) || (!key.isAcceptable())) {
return null;
}
// accept the connection from the client
SocketChannel ch = handle.accept();
if (ch == null) {
return null;
}
return new NioSocketSession(this, processor, ch);
}
这里创建了NioSocketSession将Processor线程池与SocketChannel绑定在一起。然后通过 session.getProcessor().add(session)将会话注册到SimpleIoProcessorPool线程池中的一个Processor对象内部的Selector对象。
为什么这里的processor是线程池?还记得NioSocketAcceptor的构造函数中的SimpleIoProcessorPool,processor就是它的实例。
看以下NioSocketSession的getProcessor()方法:
public IoProcessor getProcessor() {
return processor;
}
返回的就是与它关联的SimpleIoProcessorPool线程池对象.再看SimpleIoProcessorPool的addI()方法:
public final void add(S session) {
getProcessor(session).add(session);
}
private IoProcessor getProcessor(S session) {
IoProcessor processor = (IoProcessor) 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;
}
getProcessor()这个方法是SimpleIoProcessorPool中的,负责根据Session返回一个与之关联的Processor线程,这里用了session id对线程池中的线程总数取模的算法。与Session关联的Processor被添加到Session的Attribute中以便下次直接取出。
到这一部还没有看到Session内部的SocketChannel的IO事件是怎么注册到Processor线程的Selector对象上的,继续分析Processor的add()方法:
@Override
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();
}
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();
}
//NamePreservingRunnable的run方法,显示给线程命名,然后执行Processor的run方法。
public void run() {
Thread currentThread = Thread.currentThread();
String oldName = currentThread.getName();
if (newName != null) {
setName(currentThread, newName);
}
try {
runnable.run();
} finally {
setName(currentThread, oldName);
}
}
private class Processor implements Runnable {
public void run() {
assert (processorRef.get() == this);
int nSessions = 0;
lastIdleCheckTime = System.currentTimeMillis();
int nbTries = 10;
for (;;) {
try {
...
int selected = select(SELECT_TIMEOUT);
...
nSessions += handleNewSessions();
...
if (selected > 0) {
// LOG.debug("Processing ..."); // This log hurts one of
// the MDCFilter test...
process();
}
...
}
} catch (ClosedSelectorException cse) {
ExceptionMonitor.getInstance().exceptionCaught(cse);
break;
} catch (Exception e) {
ExceptionMonitor.getInstance().exceptionCaught(e);
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
ExceptionMonitor.getInstance().exceptionCaught(e1);
}
}
}
}
}
这里开始显露一些马脚了,先是把session添加到newSessions这个队列中。然后建立了Processor实例,这就是具体的Processor线程。通过executor的execute()方法先是执行了NamePreservingRunnable的run()方法,其内部执行了Processor的run()方法。
执行Processor的run()中的select()其实就是调用其内部Selector对象的select()方法,会导致Processor线程的阻塞:
protected int select(long timeout) throws Exception {
return selector.select(timeout);
}
然后调用了Processor内部的Selector对象的wakeup()方法,wakeup()这个方法是当Selector对象执行select()方法阻塞时,立即返回。
@Override
protected void wakeup() {
wakeupCalled.getAndSet(true);
selector.wakeup();
}
于是后续就执行了:
private int handleNewSessions() {
int addedSessions = 0;
for (S session = newSessions.poll(); session != null; session = newSessions.poll()) {
if (addNow(session)) {
// A new session has been created
addedSessions++;
}
}
return addedSessions;
}
private boolean addNow(S session) {
boolean registered = false;
try {
init(session);
registered = true;
// Build the filter chain of this session.
IoFilterChainBuilder chainBuilder = session.getService().getFilterChainBuilder();
chainBuilder.buildFilterChain(session.getFilterChain());
// DefaultIoFilterChain.CONNECT_FUTURE is cleared inside here
// in AbstractIoFilterChain.fireSessionOpened().
// Propagate the SESSION_CREATED event up to the chain
IoServiceListenerSupport listeners = ((AbstractIoService) session.getService()).getListeners();
listeners.fireSessionCreated(session);
} catch (Exception e) {
ExceptionMonitor.getInstance().exceptionCaught(e);
try {
destroy(session);
} catch (Exception e1) {
ExceptionMonitor.getInstance().exceptionCaught(e1);
} finally {
registered = false;
}
}
return registered;
}
@Override
protected void init(NioSession session) throws Exception {
SelectableChannel ch = (SelectableChannel) session.getChannel();
ch.configureBlocking(false);
session.setSelectionKey(ch.register(selector, SelectionKey.OP_READ, session));
}
到此终于理清了,Processor线程先是阻塞的,由Acceptor线程把session添加到newSessions队列,然后通过wakeup将Processor从Selector对象的select()方法返回执行到handleNewSessions()方法,此方法会取出newSessions队列中的session然后通过addNow()方法执行NioProcessor的init()方法,由init()方法将session中的Channel的OP_READ事件注册到Selector对象上。
所以一个IoSession对应的是一个Proceccor线程,也是一个Selector对象,每个IoSession的读取数据处理一定是同步的。
既然有读就一定有写,记得上述代码中有一段:
private void processHandles(Iterator 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); //这里的processor是processor线程池
if (session == null) {
continue;
}
initSession(session, null, null);
// add the session to the SocketIoProcessor
session.getProcessor().add(session);
}
}
重点是initSession()方法:
protected final void initSession(IoSession session, IoFuture future, IoSessionInitializer sessionInitializer) {
...
((AbstractIoSession) session).setWriteRequestQueue(session.getService().getSessionDataStructureFactory()
.getWriteRequestQueue(session));
...
}
这里为session添加了WriteRequestQueue其实就是session的消息写入队列,当session被暂停或者WriteRequestQueue队列非空写入的消息会添加到这个队列里:
if (!s.isWriteSuspended()) {
if (writeRequestQueue.isEmpty(session)) {
// We can write directly the message
s.getProcessor().write(s, writeRequest);
} else {
s.getWriteRequestQueue().offer(s, writeRequest);
s.getProcessor().flush(s);
}
} else {
s.getWriteRequestQueue().offer(s, writeRequest);
}
而如果队列是空的则会执行write()方法,其实也是将写入请求插入队列然后直接执行flush()方法。
@Override
public void write(S session, WriteRequest writeRequest) {
WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue();
writeRequestQueue.offer(session, writeRequest);
if (!session.isWriteSuspended()) {
this.flush(session);
}
}
flush()方法会在flushingSessions队列添加session并通过wakeup()方法将Processor线程从阻塞中恢复:
@Override
public final void flush(S session) {
// add the session to the queue if it's not already
// in the queue, then wake up the select()
if (session.setScheduledForFlush(true)) {
flushingSessions.add(session);
wakeup();
}
}
在Processor线程中会执行flush(long currentTime)方法,依次取出队列的每个session,注意这里的队列是ConcurrentLinkedQueue,所以不管在任何线程调用IoSession的write()方法写入消息,最终都会同步的插入到这个队列。
通过flushNow(session, currentTime)方法先是取出session的WriteRequestQueue队列(每个session都有一个写入消息的同步队列),然后依次取出其中的写消息请求,然后调用writeBuffer(S session, WriteRequest req, boolean hasFragmentation, int maxLength, long currentTime),最终调用write(NioSession session, IoBuffer buf, int length) 通过session关联的Channel的write()方法将字节流发送。由于代码过多只贴出最终部分:
@Override
protected int write(NioSession session, IoBuffer buf, int length) throws IOException {
if (buf.remaining() <= length) {
return session.getChannel().write(buf.buf());
}
int oldLimit = buf.limit();
buf.limit(buf.position() + length);
try {
return session.getChannel().write(buf.buf());
} finally {
buf.limit(oldLimit);
}
}
到此分析Processor线程读写终于结束了,可以得出结论,会话的读写都是在Processor线程池中的一个Processor线程执行的。其中读消息是按事件顺序依次完成的,写消息可以由多个线程同时写,但是写入的请求一定是同步地插入到Session地写消息队列中,然后由Processor线程按顺序依次完成发送。担心Mina框架读写的并发问题可以打住了。