前言
这篇文章里讲到了 NioEndpint 的启动过程中创建了多个 Poller 对象,并启动了 Poller 线程。在上篇文章中介绍了 Acceptor 的 run 方法,其中讲到了 Acceptor 的工作就是接受客户端的连接并转交给 Poller 线程处理,本文将分析 Poller 和 PollerEvent。Poller 和 PollerEvent 都是 NioEndpoint 的内部类。
1. PollerEvent#run
Acceptor 线程将接受的连接封装成 PollerEvent 对象,并加入到一个队列里等待 Poller 线程的执行。PollerEvent 实现了 Runnable 接口,因此 run 方法是其关键方法。
private NioChannel socket;
private NioSocketWrapper socketWrapper;
@Override
public void run() {
if (interestOps == OP_REGISTER) {
try {
socket.getIOChannel().register(
socket.getPoller().getSelector(), SelectionKey.OP_READ, socketWrapper);
} catch (Exception x) {
log.error(sm.getString("endpoint.nio.registerFail"), x);
}
} else {
final SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
try {
if (key == null) {
// The key was cancelled (e.g. due to socket closure)
// and removed from the selector while it was being
// processed. Count down the connections at this point
// since it won't have been counted down when the socket
// closed.
socket.socketWrapper.getEndpoint().countDownConnection();
((NioSocketWrapper) socket.socketWrapper).closed = true;
} else {
final NioSocketWrapper socketWrapper = (NioSocketWrapper) key.attachment();
if (socketWrapper != null) {
//we are registering the key to start with, reset the fairness counter.
int ops = key.interestOps() | interestOps;
socketWrapper.interestOps(ops);
key.interestOps(ops);
} else {
socket.getPoller().cancelledKey(key);
}
}
} catch (CancelledKeyException ckx) {
try {
socket.getPoller().cancelledKey(key);
} catch (Exception ignore) {}
}
}
}
interestOps 是在构造方法里传入的。PollerEvent 的构造方法在两处用到,一处是 Poller#register 方法里,也就是上篇文章里提到的,另一处是在 Poller#add 方法里,这个 add 方法的调用点有多处,传入的 interestOps 的值是 SelectionKey.OP_WRITE 或者 SelectionKey.OP_READ。
if 语句块里,socket 是在构造方法里传入的 NioChannel 对象,
protected SocketChannel sc = null;
public SocketChannel getIOChannel() {
return sc;
}
NioChannel#getIOChannel 返回的是 SocketChannel 对象,这个对象是在创建 NioChannel 对象是传入的,是 Acceptor 线程里调用 endpoint.serverSocketAccept() 获取到的对象。
socket.getPoller().getSelector() 是获取 Poller 的 Selector 类型的对象。
private Selector selector;
public Poller() throws IOException {
this.selector = Selector.open();
}
public Selector getSelector() { return selector;}
可以看出,这个 selector 是在 Poller 构造方法里初始化的,一个 Poller 里有一个 Selector 对象。
if 语句块里,是将 SocketChannel 对象注册到 Poller 内部的 Selector 对象,并附加了一个 NioSocketWrapper 对象。注册的感兴趣的事件是 SelectionKey.OP_READ,也就是说,这个 Selector 对象会监听这个 SocketChannel 的读事件。
else 语句块的逻辑也不复杂,就是将传入的 interestOps 操作(SelectionKey.OP_WRITE 或者 SelectionKey.OP_READ)附加到 SocketChannel 关联的 SelectionKey 里,或者取消掉关联的 SelectionKey。
2. Poller#run
Poller 实现了 Runnable,它的 run 方法是关键。
/**
* The background thread that adds sockets to the Poller, checks the
* poller for triggered events and hands the associated socket off to an
* appropriate processor as events occur.
*/
@Override
public void run() {
// Loop until destroy() is called
while (true) {
boolean hasEvents = false;
try {
if (!close) {
hasEvents = events();
if (wakeupCounter.getAndSet(-1) > 0) {
//if we are here, means we have other stuff to do
//do a non blocking select
keyCount = selector.selectNow();
} else {
keyCount = selector.select(selectorTimeout);
}
wakeupCounter.set(0);
}
if (close) {
events();
timeout(0, false);
try {
selector.close();
} catch (IOException ioe) {
log.error(sm.getString("endpoint.nio.selectorCloseFail"), ioe);
}
break;
}
} catch (Throwable x) {
ExceptionUtils.handleThrowable(x);
log.error(sm.getString("endpoint.nio.selectorLoopError"), x);
continue;
}
//either we timed out or we woke up, process events first
if ( keyCount == 0 ) hasEvents = (hasEvents | events());
Iterator iterator =
keyCount > 0 ? selector.selectedKeys().iterator() : null;
// Walk through the collection of ready keys and dispatch
// any active event.
while (iterator != null && iterator.hasNext()) {
SelectionKey sk = iterator.next();
NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment();
// Attachment may be null if another thread has called
// cancelledKey()
if (attachment == null) {
iterator.remove();
} else {
iterator.remove();
processKey(sk, attachment);
}
}//while
//process timeouts
timeout(keyCount,hasEvents);
}//while
getStopLatch().countDown();
}
run 方法里先执行 if (!close) 语句块。先调用了 events 方法,
/**
* Processes events in the event queue of the Poller.
*
* @return true
if some events were processed,
* false
if queue was empty
*/
public boolean events() {
boolean result = false;
PollerEvent pe = null;
for (int i = 0, size = events.size(); i < size && (pe = events.poll()) != null; i++ ) {
result = true;
try {
pe.run();
pe.reset();
if (running && !paused) {
eventCache.push(pe);
}
} catch ( Throwable x ) {
log.error(sm.getString("endpoint.nio.pollerEventError"), x);
}
}
return result;
}
events() 方法就是执行了 events 这个队列里的 PollerEvent 的 run 方法,然后把 PollerEvent 对象放在 eventCache 里以方便复用。PollerEvent#run方法在上面讲过了。
之后根据 wakeupCounter 的值判断是用 selector.selectNow() 还是 selector.select(selectorTimeout)。wakeupCounter 值在 Poller#addEvent 里自增1的。
然后就进入 if (close) 语句块,也是调用 events() 方法,然后调用 timeout(0, false) 和 selector.close() 方法。
后面就是调用 Selector.selectedKeys() 获取监听到的 SelectionKey 集合并逐个调用 processKey(sk, attachment)处理,这是 nio 编程里的常规操作。
SelectionKey 的 attachment 是 NioSocketWrapper 对象,这个对象是在构造 PollerEvent 传入的,在 Poller#register 方法里。
2.1. Poller#processKey
processKey 方法就是处理 SelectionKey 的关键了。
protected void processKey(SelectionKey sk, NioSocketWrapper attachment) {
try {
if ( close ) {
cancelledKey(sk);
} else if ( sk.isValid() && attachment != null ) {
if (sk.isReadable() || sk.isWritable() ) {
if ( attachment.getSendfileData() != null ) {
processSendfile(sk,attachment, false);
} else {
unreg(sk, attachment, sk.readyOps());
boolean closeSocket = false;
// Read goes before write
if (sk.isReadable()) {
if (!processSocket(attachment, SocketEvent.OPEN_READ, true)) {
closeSocket = true;
}
}
if (!closeSocket && sk.isWritable()) {
if (!processSocket(attachment, SocketEvent.OPEN_WRITE, true)) {
closeSocket = true;
}
}
if (closeSocket) {
cancelledKey(sk);
}
}
}
} else {
//invalid key
cancelledKey(sk);
}
} catch ( CancelledKeyException ckx ) {
cancelledKey(sk);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("endpoint.nio.keyProcessingError"), t);
}
}
可以看出,attachment.getSendfileData() 不为 null 的话就调用 processSendfile 方法处理。否则调用 processKey 方法处理。
processSendfile 就是调用 FileChannel#transferTo 方法来发送数据的。这个方法不是重点,这里就不详细解析了。
processKey 方法是调用 processSocket(SocketWrapperBase socketWrapper, SocketEvent event, boolean dispatch) 方法分别处理 OP_READ 和 OP_WRITE 事件,传入的第二个参数分别是 SocketEvent.OPEN_READ 和 SocketEvent.OPEN_WRITE,第三个参数是 true。dispatch 的 true 表示是用另外的线程处理,false 是在 Poller 线程处理。
这个 processSocket 是 AbstractEndpoint 里的方法。
2.2. AbstractEndpoint#processSocket
/**
* External Executor based thread pool.
*/
private Executor executor = null;
public Executor getExecutor() { return executor; }
/**
* Process the given SocketWrapper with the given status. Used to trigger
* processing as if the Poller (for those endpoints that have one)
* selected the socket.
*
* @param socketWrapper The socket wrapper to process
* @param event The socket event to be processed
* @param dispatch Should the processing be performed on a new
* container thread
*
* @return if processing was triggered successfully
*/
public boolean processSocket(SocketWrapperBase socketWrapper,
SocketEvent event, boolean dispatch) {
try {
if (socketWrapper == null) {
return false;
}
SocketProcessorBase sc = processorCache.pop();
if (sc == null) {
sc = createSocketProcessor(socketWrapper, event);
} else {
sc.reset(socketWrapper, event);
}
Executor executor = getExecutor();
if (dispatch && executor != null) {
executor.execute(sc);
} else {
sc.run();
}
} catch (RejectedExecutionException ree) {
getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper) , ree);
return false;
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
// This means we got an OOM or similar creating a thread, or that
// the pool and its queue are full
getLog().error(sm.getString("endpoint.process.fail"), t);
return false;
}
return true;
}
processSocket 方法先从 processorCache 的缓存池里获取一个 SocketProcessorBase 对象,processorCache 是在 NioEndpoint#startInternal 里初始化的。如果获取不到就调用 createSocketProcessor 方法创建一个。
创建SocketProcessorBase 对象时传入了 SocketWrapperBase(也就是 NioSocketWrapper 对象) 和 SocketEvent 对象。
createSocketProcessor 方法是一个abstract 的,其实现在 NioEndpoint 里。
@Override
protected SocketProcessorBase createSocketProcessor(
SocketWrapperBase socketWrapper, SocketEvent event) {
return new SocketProcessor(socketWrapper, event);
}
/**
* This class is the equivalent of the Worker, but will simply use in an
* external Executor thread pool.
*/
protected class SocketProcessor extends SocketProcessorBase
NioEndpoint#createSocketProcessor 方法就是简单创建一个 SocketProcessor 对象。SocketProcessor 是 NioEndpoint 的内部类。
拿到 SocketProcessorBase 对象后,由于传入的 dispatch 为 true,所以会把这个 SocketProcessorBase 扔到 executor 里处理。SocketProcessorBase 实现了 Runnable。
executor 是在 AbstractEndpoint#createExecutor 方法里初始化的,createExecutor 在这篇文章里介绍过了,这里就不赘述了。
SocketProcessorBase 的内容如下。
public abstract class SocketProcessorBase implements Runnable {
protected SocketWrapperBase socketWrapper;
protected SocketEvent event;
public SocketProcessorBase(SocketWrapperBase socketWrapper, SocketEvent event) {
reset(socketWrapper, event);
}
public void reset(SocketWrapperBase socketWrapper, SocketEvent event) {
Objects.requireNonNull(event);
this.socketWrapper = socketWrapper;
this.event = event;
}
@Override
public final void run() {
synchronized (socketWrapper) {
// It is possible that processing may be triggered for read and
// write at the same time. The sync above makes sure that processing
// does not occur in parallel. The test below ensures that if the
// first event to be processed results in the socket being closed,
// the subsequent events are not processed.
if (socketWrapper.isClosed()) {
return;
}
doRun();
}
}
protected abstract void doRun();
}
SocketProcessorBase#run 方法很简单,就是调用抽象方法 doRun()。所以关键在于 SocketProcessor#doRun 方法。
2.3. SocketProcessor#doRun
@Override
protected void doRun() {
NioChannel socket = socketWrapper.getSocket();
SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
try {
int handshake = -1;
try {
if (key != null) {
if (socket.isHandshakeComplete()) {
// No TLS handshaking required. Let the handler
// process this socket / event combination.
handshake = 0;
} else if (event == SocketEvent.STOP || event == SocketEvent.DISCONNECT ||
event == SocketEvent.ERROR) {
// Unable to complete the TLS handshake. Treat it as
// if the handshake failed.
handshake = -1;
} else {
handshake = socket.handshake(key.isReadable(), key.isWritable());
// The handshake process reads/writes from/to the
// socket. status may therefore be OPEN_WRITE once
// the handshake completes. However, the handshake
// happens when the socket is opened so the status
// must always be OPEN_READ after it completes. It
// is OK to always set this as it is only used if
// the handshake completes.
event = SocketEvent.OPEN_READ;
}
}
} catch (IOException x) {
handshake = -1;
if (log.isDebugEnabled()) log.debug("Error during SSL handshake",x);
} catch (CancelledKeyException ckx) {
handshake = -1;
}
if (handshake == 0) {
SocketState state = SocketState.OPEN;
// Process the request from this socket
if (event == null) {
state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
} else {
state = getHandler().process(socketWrapper, event);
}
if (state == SocketState.CLOSED) {
close(socket, key);
}
} else if (handshake == -1 ) {
close(socket, key);
} else if (handshake == SelectionKey.OP_READ){
socketWrapper.registerReadInterest();
} else if (handshake == SelectionKey.OP_WRITE){
socketWrapper.registerWriteInterest();
}
} catch (CancelledKeyException cx) {
socket.getPoller().cancelledKey(key);
} catch (VirtualMachineError vme) {
ExceptionUtils.handleThrowable(vme);
} catch (Throwable t) {
log.error(sm.getString("endpoint.processing.fail"), t);
socket.getPoller().cancelledKey(key);
} finally {
socketWrapper = null;
event = null;
//return to cache
if (running && !paused) {
processorCache.push(this);
}
}
}
doRun 方法里在开始的 if-else 语句块里决定 handshake 变量的值。
先调用 socket.isHandshakeComplete() 也就是 NioChannel#isHandshakeComplete
public boolean isHandshakeComplete() {
return true;
}
直接返回 true。理论上 else 的语句都不会执行了。其实 handshake 是 HTTPS 里的内容,NioChannel 不处理 handshake,但是在 NioChannel 的子类 SecureNioChannel 里会处理。SecureNioChannel 不是本文重点,这里就不多做介绍了。
所以在第一个 if-else 语句块了,handshake 的值就已经为 0 了。
接着是第二个 if-else 语句块,根据 handshake 的值做不同的处理,如果 handshake 的值是 SelectionKey.OP_READ 或者 SelectionKey.OP_WRITE 的话,就调用 socketWrapper.registerReadInterest() 或者 socketWrapper.registerWriteInterest() 重新注册感兴趣事件。
@Override
public void registerReadInterest() {
getPoller().add(getSocket(), SelectionKey.OP_READ);
}
@Override
public void registerWriteInterest() {
getPoller().add(getSocket(), SelectionKey.OP_WRITE);
}
这两个方法其实也就是调用 Poller#add 方法,
/**
* Add specified socket and associated pool to the poller. The socket will
* be added to a temporary array, and polled first after a maximum amount
* of time equal to pollTime (in most cases, latency will be much lower,
* however).
*
* @param socket to add to the poller
* @param interestOps Operations for which to register this socket with
* the Poller
*/
public void add(final NioChannel socket, final int interestOps) {
PollerEvent r = eventCache.pop();
if ( r==null) r = new PollerEvent(socket,null,interestOps);
else r.reset(socket,null,interestOps);
addEvent(r);
if (close) {
NioEndpoint.NioSocketWrapper ka = (NioEndpoint.NioSocketWrapper)socket.getAttachment();
processSocket(ka, SocketEvent.STOP, false);
}
}
Poller#add 就是创建一个 PollerEvent 对象,并将这个对象加入的缓存队列里等待 Poller 线程的处理,PollerEvent#run 前面已经讲过了。
在 SecureNioChannel 里,handshake 可能会为根据 SecureNioChannel#handshake 的处理返回 SelectionKey.OP_READ 或者 SelectionKey.OP_WRITE。但是在 NioChannel 里 handshake 只会为 0。
第二个 if-else 语句块的 if 块里就是调用 getHandler().process(socketWrapper, event) 里处理。
然后得到一个 SocketState 对象 state,如果 state 的值为SocketState.CLOSED,则执行 close(socket, key) 方法。
getHandler() 是 AbstractEndpoint 里的方法
private Handler handler = null;
public Handler getHandler() { return handler; }
Handler 带一个泛型 S,这个泛型就是 AbstractEndpoint 里的 S。Handler 也是 AbstractEndpoint 的内部接口。
在 NioEndpoint 及其父类 AbstractJsseEndpoint 的声明里可以知道这个泛型 S 的具体类型就是 NioChannel。
这个 Handler 就是在 AbstractHttp11Protocol 的构造方法里 初始化的 ConnectionHandler 对象。这个在这篇文章里就讲到了,这里不在赘述了。
ConnectionHandler 会在下篇文章里介绍,这里就先不多讲了。
小结
本文分析了 PollerEvent 和 Poller 的 run 方法,其中 PollerEvent#run 方法就是将 SocketChannel 的读或者写事件注册的 Poller 的 selector 里。Poller#run 方法就是先处理缓存队列里的 PollerEvent,然后处理 selector.selectKeys() 返回的 SelectionKey,也就是 SocketChannel 的读写事件。