初探连接器的奥秘之NIO如何具体实现
前面我们聊到Tomcat有两大核心,HTTP 服务器 + Servlet 容器,那么他们分别对应的作用是连接器和容器。
- 连接器负责接收请求
- 容器负责处理请求
在这先聊聊连接器,我们将连接器的功能细化。
- 监听端口
- 接收连接请求
- 读取请求网络字节流
- 根据应用层协议解析
- 交给容器
- 响应给浏览器
我们着重先看前两步骤,首先监听端口和接收请求。
Tomcat设计了AbstractEndpoint组件来处理这两步,AbstractEndpoint相当于一个通信端口,接收具体的Socket和发送,是对传输层的抽象,实现了TCP/IP协议。
其具体的实现有如下:
我们注意到NioEndpoint,就是本节我们要着重关注的。
大致过程:
- 在AbstractEndpoint有个重要的属性Acceptor,负责监听请求;
- 在NioEndpoint也有两大重要的子组件:Poller和SocketProcessor,Poller 实现Runnable负责轮训已经接收的请求(是否内核数据已经准备好),其中SocketProcessor处理socket请求,丢给线程池。
Acceptor监听
Acceptor监听到请求,register进去Poller中的队列中;
public class Acceptor<U> implements Runnable {
private final AbstractEndpoint endpoint;
private volatile boolean stopCalled = false;
public Acceptor(AbstractEndpoint endpoint) {
this.endpoint = endpoint;
}
@SuppressWarnings("deprecation")
@Override
public void run() {
try {
while (!stopCalled) {
//接收请求
SocketChannel socket = endpoint.serverSocketAccept();
//注册
endpoint.setSocketOptions(socket);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
设置一些属性,包装成NioSocketWrapper
@Override
protected boolean setSocketOptions(SocketChannel socket) {
//包装类
NioSocketWrapper socketWrapper = null;
try {
.......。。
.......。。。
// Set socket properties
// Disable blocking, polling will be used
//设置非阻塞
socket.configureBlocking(false);
socketProperties.setProperties(socket.socket());
//设置读超时时间
socketWrapper.setReadTimeout(getConnectionTimeout());
//设置写超时时间
socketWrapper.setWriteTimeout(getConnectionTimeout());
//设置长连接时间
socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
//注册到队列中
poller.register(socketWrapper);
return true;
} catch (Throwable t) {
}
// Tell to close the socket if needed
return false;
}
注册到队列
public void register(final NioSocketWrapper socketWrapper) {
socketWrapper.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.
PollerEvent pollerEvent = createPollerEvent(socketWrapper, OP_REGISTER);
addEvent(pollerEvent);
}
private void addEvent(PollerEvent event) {
events.offer(event);
}
SynchronizedQueue队列是数组实现。
用简单的结构减少维护节点的开销,并且整个过程只会offer和poll
private final SynchronizedQueue<PollerEvent> events = new SynchronizedQueue<>();
Poller轮询事件
请求包装成PollerEvent事件,注册到队列后,Poller线程进行轮询
最后将socket封装成socketProcessor提交到Executor处理
public void run() {
while (true) {
。。。。
try {
if (!close) {
//遍历events,绑定selector
hasEvents = events();
//获取就绪IO数量
keyCount = selector.select(selectorTimeout);
}
} catch (Throwable x) {
}
//当有就绪IO事件时,再遍历selectedKeys
Iterator<SelectionKey> iterator =
keyCount > 0 ? selector.selectedKeys().iterator() : null;
while (iterator != null && iterator.hasNext()) {
SelectionKey sk = iterator.next();
iterator.remove();
NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
if (socketWrapper != null) {
交给processKey函数,判断是read还是write事件
processKey(sk, socketWrapper);
}
}
。。。。。。
}
}
丢给线程池
拿到就绪IO的Socket,到processKey函数处理,根据读还是写调用processSocket处理
protected void processKey(SelectionKey sk, NioSocketWrapper socketWrapper) {
try {
if (close) {
cancelledKey(sk, socketWrapper);
} else if (sk.isValid()) {
if (sk.isReadable() || sk.isWritable()) {
if (socketWrapper.getSendfileData() != null) {
processSendfile(sk, socketWrapper, false);
} else {
。。。
// Read goes before write
// 读优于写
if (sk.isReadable()) {
。。。
//processSocket处理可读请求
} else if (!processSocket(socketWrapper, SocketEvent.OPEN_READ, true)) {
closeSocket = true;
}
}
if (!closeSocket && sk.isWritable()) {
。。。。
//processSocket处理可写请求
} else if (!processSocket(socketWrapper, SocketEvent.OPEN_WRITE, true)) {
closeSocket = true;
}
。。。。。。
}
}
processSocket方法创建一个SocketProcessor丢给线程池
public boolean processSocket(SocketWrapperBase<S> socketWrapper,
SocketEvent event, boolean dispatch) {
try {
if (socketWrapper == null) {
return false;
}
SocketProcessorBase<S> sc = null;
if (processorCache != null) {
sc = processorCache.pop();
}
if (sc == null) {
// 创建一个 SocketProcessor 的实例
sc = createSocketProcessor(socketWrapper, event);
} else {
sc.reset(socketWrapper, event);
}
//线程池(默认有)
Executor executor = getExecutor();
//dispatch读写事件都是true,像ERROR或STOP事件都是false
if (dispatch && executor != null) {
executor.execute(sc);
} else {
sc.run();
}
。。。。。
return true;
}
总结
总结一下目前分析结果,Acceptor监听到请求后,封装成PollerEvent注册到Poller中的队列里;Poller的run方法去轮训队列的事件,绑定到Selector选择器上,然后selector.select去获取就绪IO,包装创建SocketProcessor到线程池。这是本节主要的内容,后续可以自己去debug看看源码更详细的实现,这也是Tomcat对于IO模型中NIO的具体实现。
从监听到经过一系列的处理交给线程池,后续如果具体请求如何到容器呢?看下节分析!!