服务端启动后,便会一直阻塞在NioSocketAcceptor父类AbstractPollingIoAcceptor内部类Acceptor.run()方法的int selected = select();上,直到有建立连接的请求到来。
监听到连接请求就会执行其后的processHandles(selectedHandles());
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);
}
}
(一)、accept方法
protected NioSession accept(IoProcessor<NioSession> processor, ServerSocketChannel handle) throws Exception {
SelectionKey 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);
}
创建了SocketChannel,建立连接,此时会通知客户端OP_CONNECT事件。
再创建NioSocketSession对象来封装相关的类:
protected NioSession newSession(IoProcessor<NioSession> processor, SocketChannel handle) {
return new NioSocketSession(this, processor, handle);
}
1、将NioSocketAcceptor父类AbstractPollingIoAcceptor的processor赋值给其processor字段。
2、NioSocketSession保存了客户端通道SocketChannel对象,有SocketChannel ch = handle.accept();
3、NioSocketAccptor对象将赋值给NioSocketSession的service字段。
(二)、initSession(session, null, null);设置session的一些参数。
protected final void initSession(IoSession session, IoFuture future, IoSessionInitializer sessionInitializer) {
// Update lastIoTime if needed.
if (stats.getLastReadTime() == 0) {
stats.setLastReadTime(getActivationTime());
}
if (stats.getLastWriteTime() == 0) {
stats.setLastWriteTime(getActivationTime());
}
// Every property but attributeMap should be set now.
// Now initialize the attributeMap. The reason why we initialize
// the attributeMap at last is to make sure all session properties
// such as remoteAddress are provided to IoSessionDataStructureFactory.
try {
((AbstractIoSession) session).setAttributeMap(session.getService().getSessionDataStructureFactory()
.getAttributeMap(session));
} catch (IoSessionInitializationException e) {
throw e;
} catch (Exception e) {
throw new IoSessionInitializationException("Failed to initialize an attributeMap.", e);
}
try {
((AbstractIoSession) session).setWriteRequestQueue(session.getService().getSessionDataStructureFactory()
.getWriteRequestQueue(session));
} catch (IoSessionInitializationException e) {
throw e;
} catch (Exception e) {
throw new IoSessionInitializationException("Failed to initialize a writeRequestQueue.", e);
}
if ((future != null) && (future instanceof ConnectFuture)) {
// DefaultIoFilterChain will notify the future. (We support ConnectFuture only for now).
session.setAttribute(DefaultIoFilterChain.SESSION_CREATED_FUTURE, future);
}
if (sessionInitializer != null) {
sessionInitializer.initializeSession(session, future);
}
finishSessionInitialization0(session, future);
}
(三)、session.getProcessor().add(session);
1、该方法从上一步的processor中取一个NioProcessor对象,将这个session添加到NioProcessor父类AbstractPollingIoProcessor的线程安全队列对象newSessions中。
2、调用startupProcessor()启动多线程的内部类Processor。
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();
}
Processor.run()
public void run() {
assert (processorRef.get() == this);
int nSessions = 0;
lastIdleCheckTime = System.currentTimeMillis();
for (;;) {
try {
// This select has a timeout so that we can manage
// idle session when we get out of the select every
// second. (note : this is a hack to avoid creating
// a dedicated thread).
long t0 = System.currentTimeMillis();
int selected = select(SELECT_TIMEOUT);
long t1 = System.currentTimeMillis();
long delta = (t1 - t0);
if ((selected == 0) && !wakeupCalled.get() && (delta < 100)) {
// Last chance : the select() may have been
// interrupted because we have had an closed channel.
if (isBrokenConnection()) {
LOG.warn("Broken connection");
// we can reselect immediately
// set back the flag to false
wakeupCalled.getAndSet(false);
continue;
} else {
LOG.warn("Create a new selector. Selected is 0, delta = " + (t1 - t0));
// Ok, we are hit by the nasty epoll
// spinning.
// Basically, there is a race condition
// which causes a closing file descriptor not to be
// considered as available as a selected channel, but
// it stopped the select. The next time we will
// call select(), it will exit immediately for the same
// reason, and do so forever, consuming 100%
// CPU.
// We have to destroy the selector, and
// register all the socket on a new one.
registerNewSelector();
}
// Set back the flag to false
wakeupCalled.getAndSet(false);
// and continue the loop
continue;
}
// Manage newly created session first
nSessions += handleNewSessions();
updateTrafficMask();
// Now, if we have had some incoming or outgoing events,
// deal with them
if (selected > 0) {
//LOG.debug("Processing ..."); // This log hurts one of the MDCFilter test...
process();
}
// Write the pending requests
long currentTime = System.currentTimeMillis();
flush(currentTime);
// And manage removed sessions
nSessions -= removeSessions();
// Last, not least, send Idle events to the idle sessions
notifyIdleSessions(currentTime);
// Get a chance to exit the infinite loop if there are no
// more sessions on this Processor
if (nSessions == 0) {
processorRef.set(null);
if (newSessions.isEmpty() && isSelectorEmpty()) {
// newSessions.add() precedes startupProcessor
assert (processorRef.get() != this);
break;
}
assert (processorRef.get() != this);
if (!processorRef.compareAndSet(null, this)) {
// startupProcessor won race, so must exit processor
assert (processorRef.get() != this);
break;
}
assert (processorRef.get() == this);
}
// Disconnect all sessions immediately if disposal has been
// requested so that we exit this loop eventually.
if (isDisposing()) {
for (Iterator<S> i = allSessions(); i.hasNext();) {
scheduleRemove(i.next());
}
wakeup();
}
} catch (ClosedSelectorException cse) {
// If the selector has been closed, we can exit the loop
break;
} catch (Throwable t) {
ExceptionMonitor.getInstance().exceptionCaught(t);
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
ExceptionMonitor.getInstance().exceptionCaught(e1);
}
}
}
try {
synchronized (disposalLock) {
if (disposing) {
doDispose();
}
}
} catch (Throwable t) {
ExceptionMonitor.getInstance().exceptionCaught(t);
} finally {
disposalFuture.setValue(true);
}
}
和监听端口一样,一进来就被int selected = select(SELECT_TIMEOUT);阻塞了。要知道,这个已经是在NioProcessor类里了,NioProcessor也有一个Selector对象,在其构造方法中就通过open()方法得到了。此时这个Selector对象上还没绑定任何事件他就阻塞了。
所以在startupProcessor()中有个wakeup()方法,用于唤醒这个阻塞,使之能够执行nSessions += handleNewSessions();进行session的更进一步的配置。
int selected = select(SELECT_TIMEOUT);和nSessions += handleNewSessions();中的那段代码主要是为了防止nio自身的bug,可参考这篇的解释。
(四)、handleNewSessions()方法如何继续配置session?
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;
}
转调AbstractPollingIoProcessor.addNow()方法
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 (Throwable e) {
ExceptionMonitor.getInstance().exceptionCaught(e);
try {
destroy(session);
} catch (Exception e1) {
ExceptionMonitor.getInstance().exceptionCaught(e1);
} finally {
registered = false;
}
}
return registered;
}
1、init方法在其子类NioProcessor中
protected void init(NioSession session) throws Exception {
SelectableChannel ch = (SelectableChannel) session.getChannel();
ch.configureBlocking(false);
session.setSelectionKey(ch.register(selector, SelectionKey.OP_READ, session));
}
终于碰到在客户端通道上绑定OP_READ事件。
2、session.getService().getFilterChainBuilder()取NioSocketAcceptor父类的filterChainBuilder字段。这个在上一篇中启动服务端的时候有配置,如果没配置则默认创建一个DefaultIoFilterChainBuilder类的对象。
3、listeners.fireSessionCreated(session);调用IoServiceListenerSupport类的fireSessionCreated方法对关联在session中的过滤器链进行初始化。
在nSessions += handleNewSessions();初始化NioSocketSession后,Processor.run()方法中接下去的代码基本都不会执行了,阻塞在int selected = select(SELECT_TIMEOUT);等待客户端的数据。
至此,就可以等待客户端就往通道中写数据了。NioSocketAcceptor中有一个Selector负责监听建立连接的请求(OP_ACCEPT),请求建立后,转由NioProcessor处理。NioProcessor的Selector则负责监听数据的读写(OP_READ)了。