tomcat服务器解析(二) --- Endpoint
来源:互联网 发布:照片电子相册制作软件 编辑:程序博客网 时间:2024/05/18 15:26
Endpoint主要用来提供基础的网络I/O服务,封装了网络通讯相关的细节。在AbstractProtocol中对Endpoint有这样一段注释
/**
* Endpoint that provides low - level network I/O - must be matched to the
* ProtocolHandler implementation (ProtocolHandler using BIO, requires BIO
* Endpoint etc.).
*/
protected AbstractEndpoint<S> endpoint = null ;
先对AbstractEndpoint(org.apache.tomcat.util.net.AbstractEndpoint)类做了解。
【AbstractEndpoint的线程池】
AbstractEndpoint有一个Executor的属性,是它所用的线程池。这个线程池可以是外界指定的,也可以是由AbstractEndpoint自己创建的。通过属性internalExecutor来标识使用的是外部的线程池,还是有Endpoint自己创建的线程池。
可以由外部调用显式指定endpoint使用的线程池
/**
* External Executor based thread pool.
*/
private Executor executor = null;
public void setExecutor(Executor executor) {
this.executor = executor;
this.internalExecutor = (executor==null);
}
public Executor getExecutor() { return executor ; }
在当调用者没有显式指定所用线程池时,会创建一个自己所用的线程池,创建方法如下。
public void createExecutor () {
internalExecutor = true ;
TaskQueue taskqueue = new TaskQueue();
TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-" , daemon , getThreadPriority());
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS ,taskqueue, tf);
taskqueue.setParent( (ThreadPoolExecutor) executor);
}
【AbstractEndpoint的Acceptor】
在AbstractEndpoint中定义了Acceptor类(实现了Runnable接口),同时定义了acceptors属,主要用于接收网络请求。
/**
* Threads used to accept new connections and pass them to worker threads.
*/
protected Acceptor [] acceptors;
启动acceptors时,并没有使用前面提到过的线程池,而是生成了新的守护线程(getDaemon方法,默认返回true),来运行。但,具体在acceptors中线程的执行体,则交由具体的子类负责实现(貌似template-method模式是各种框架的基础配置),通过重写抽象方法createAcceptor来完成。
protected final void startAcceptorThreads() {
int count = getAcceptorThreadCount();
acceptors = new Acceptor [count];
for (int i = 0; i < count; i++) {
acceptors[i] = createAcceptor();
String threadName = getName() + "-Acceptor-" + i;
acceptors[i].setThreadName(threadName);
Thread t = new Thread(acceptors [i], threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
t.start();
}
}
AbstractEndpoint框架主要定义了一些基本的属性,同时规定了生命周期的调用顺序。
Endpoint的初始化和启动,主要执行具体子类的所实现的startInternal方法来完成。
public final void init() throws Exception {
if (bindOnInit ) {
bind();
bindState = BindState.BOUND_ON_INIT;
}
}
public final void start() throws Exception {
if (bindState == BindState.UNBOUND) {
bind();
bindState = BindState.BOUND_ON_START;
}
startInternal();
}
在Http11NioProtocol的构造函数中指定的是使用NioEndpoint实例,因此这里通过分析AbstractEndpoint的子类NioEndpoint来做进一步的了解。
这里主要关注bind和startInternal两个函数
【NioEndpoint】
bind操作的主体部分的代码如下
@Override
public void bind() throws Exception {
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = (getAddress()!= null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
serverSock.socket().bind(addr,getBacklog());
serverSock.configureBlocking( true); //mimic APR behavior
serverSock.socket().setSoTimeout(getSocketProperties().getSoTimeout());
// 这里省却了一些与配置设置和ssl相关的代码
selectorPool.open();
}
从代码上来看,bind操作主要是做一些配置参数的计算,以及开始对socket的监听
startInternal操作的代码如下
/**
* Start the NIO endpoint, creating acceptor, poller threads.
*/
@Override
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
keyCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getKeyCache());
eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getEventCache());
nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getBufferPool());
// Create worker collection
if ( getExecutor() == null ) {
createExecutor();
}
initializeConnectionLatch();
// Start poller threads
pollers = new Poller[getPollerThreadCount()];
for (int i=0; i<pollers.length; i++) {
pollers[i] = new Poller();
Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-" +i);
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon( true);
pollerThread.start();
}
startAcceptorThreads();
}
}
在startInternal中,初始化线程池,创建和启动网络数据接收线程组,创建和启动poller线程组。
在startInternal中,会检查是否指定要使用外部的线程池,如果没有指定外部线程池,Endpoint就会创建一个内部的线程池。但这里面没有与线程的调度和使用相关的代码
【线程池的调用时机】
与Acceptor和Poller相关?
在NioEndpoint中对Acceptor的功能说明如下,acceptor主要监听网络连接并且进行任务分发的后台线程。
// --------------------------------------------------- Acceptor Inner Class
/**
* The background thread that listens for incoming TCP/IP connections and
* hands them off to an appropriate processor.
*/
protected class Acceptor extends AbstractEndpoint.Acceptor {
Acceptor负责接收网络请求,建立连接。连接建立之后,将这个socket连接交给Poller。由Poller来负责执行数据的读取和业务执行。
从代码上看,Acceptor的负责控制底层同时连接的socket数目,它的任务在把建立建立之后socket交给Poller之后就结束了。
运行的主体部分在重写的run方法中,将建立连接之后的socket交给Poller的工作在setSocketOptions方法中实现。
setSocketOptions方法是专门处理特定socket连接的方法,将一个SocketChannel对象包装成一个NioChannel之后,注册到Poller中。
/**
* Process the specified connection.
*/
protected boolean setSocketOptions(SocketChannel socket) {
// Process the connection
try {
//disable blocking, APR style, we are gonna be polling it
socket.configureBlocking( false);
Socket sock = socket.socket();
socketProperties.setProperties(sock);
NioChannel channel = nioChannels.pop();
// 这里省略了设置channel属性的一些语句
getPoller0().register(channel);
} catch (Throwable t) {
// 省略异常处理的相关代码
return false ;
}
return true ;
}
getPoller0是从在startInternal方法中初始化的pollsers数组中取一个poller。
然后通过Poller对象的register方法把这个channel注册到此Poller对象上。
pollers数组的大小是根据当前的运行环境计算出来的,无法通过配置修改。
【Poller是什么】
Poller是实现了Runnable接口的,在NioEndpoint的时候,会初始化pollers数组,同时启动pollers数组中的线程,让pollers开始工作。
每个Poller都有一个自己的Selector对象,在Poller的构造函数中,通过调用Selector.open方法生成,虽然看上去这很像是一个单例模式,但实际上没法返回的都是一个全新的对象(可能与jdk的底层实现有关,目前从Oracle提供的jdk的试验来看,两次调用返回的是不同的对象)。
在Poller重写的run方法中,会首先根据当前endpoint的状态来选择操作。
如果endpoint被暂停,让Poller线程进行休眠,直到暂停被解除
// Loop if endpoint is paused
while (paused && (!close) ) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// Ignore
}
}
endpoint是暂停状态,但没有被关闭,暂停状态是有可能恢复的,所以让poller休眠等待即可
如果endpoint被关闭,那就处理完已有的数据,这个Poller打开的selector。结束poller线程的执行。通过break跳出run方法体的while(true)循环。
// Time to terminate?
if (close) {
events();
timeout(0, false);
try {
selector.close();
} catch (IOException ioe) {
log.error(sm.getString(
"endpoint.nio.selectorCloseFail" ), ioe);
}
break;
} else {
hasEvents = events();
}
如果endpoint是正常工作状态,处理已有的数据。通过events方法来处理当前Poller中已有的事件(数据)。同时使用selector.select或者selectNow来获取这个Poller上
状态已经OK的渠道,并进行数据处理。
if ( !close ) {
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;
}
正常状态下的数据处理,通过processKey来实现。获取对应的渠道的key,然后调用processKey方法
Iterator<SelectionKey> 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();
KeyAttachment attachment = (KeyAttachment)sk.attachment();
// Attachment may be null if another thread has called
// cancelledKey()
if (attachment == null) {
iterator.remove();
} else {
attachment.access();
iterator.remove();
processKey(sk, attachment);
}
}
processKey的主要工作是调用NioEndpoint的processSocket来实现socket的读写。
if ( isWorkerAvailable() ) {
unreg(sk, attachment, sk.readyOps());
boolean closeSocket = false;
// Read goes before write
if (sk.isReadable()) {
if (!processSocket(attachment, SocketStatus.OPEN_READ, true )) {
closeSocket = true;
}
}
if (!closeSocket && sk.isWritable()) {
if (!processSocket(attachment, SocketStatus.OPEN_WRITE, true )) {
closeSocket = true;
}
}
if (closeSocket) {
cancelledKey(sk,SocketStatus.DISCONNECT);
}
} else {
result = false;
}
根据socket的状态,来进行调用,于是这里又转到了NioEndpoint上。
这里绕回到NioEndpoint上的processSocket,才发现对前面提到过的线程池的使用,
attachment.setCometNotify(false); //will get reset upon next reg
SocketProcessor sc = processorCache.pop();
if ( sc == null ) sc = new SocketProcessor(attachment, status);
else sc.reset(attachment, status);
Executor executor = getExecutor();
if (dispatch && executor != null) {
executor.execute(sc);
} else {
sc.run();
}
这里通过getExecutor来获取可用的线程池。任务被封装成SocketProcessor对象,在成功获取线程池后,则通过线程池来进行socket数据数据的读写操作。在此就使用到了启动tomcat时所配置的线程池了。
在此对NioEndpoint的架构做个总结
Acceptor:
负责监听并接收socket连接建立。由Acceptor来控制与服务端建立连接的客户端socket数目。具体的数目为一个服务可配置项,可以在启动服务时指定
Poller:
负责处理已建立连接的socket,将channel封装后,提交至线程池(Executors)来处理。Poller线程的数目与运行时环境有关,通过计算得出,不可配置。
Executors:
处理socket请求的线程池。线程池中线程的数目可在启动服务时配置。
[Poller中Selector的注册]
在Poller中,对selector的使用上,只看到通过selector.select活selector.selectNow来获取对应的渠道。但在java的nio中,一个渠道必须要先在selector上注册后,才能被
selector获取到。那么,各个channel是何时再selector上注册的呢?
答案在PollerEvent上。
前面提到过Acceptor的主要工作是把建立好连接的socket注册到Poller上,通过register上实现。
Poller的register把建立好连接的socket封装成一个PollerEvent对象,然后放入这个Poller所维护的事件队列中。
Poller内部所维护的事件队列,定义如下 private final SynchronizedQueue<PollerEvent> events = new SynchronizedQueue<>();
在Poller的run方法中,通过events方法,来处理已有的事件。
public boolean events() {
boolean result = false;
PollerEvent pe = null;
while ( (pe = events.poll()) != null ) {
result = true;
try {
pe.run();
pe.reset();
if (running && !paused) {
eventCache.push(pe);
}
} catch ( Throwable x ) {
log.error( "",x);
}
}
return result;
}
events是一个PollerEvent类型队列。events方法中有一个while循环,取出队列中的每一个PollerEvent对象,然后执行它。
PollerEvent实现了Runnable接口,在其run方法中,完成了channel对selector的注册
if ( interestOps == OP_REGISTER ) {
try {
socket.getIOChannel().register(socket.getPoller().getSelector(), SelectionKey.OP_READ, key);
} catch (Exception x) {
log.error( "", x);
}
} else {
socket.getPoller()返回这个channel所注册的Poller对象。getSelector()返回这个Poller对象的selector。注册之后,当这个socket的channel有数据到达,便能通过selector.select活selector.selectNow被返回,放入到Executor中进行处理。
阅读全文
0 0
- tomcat服务器解析(二) --- Endpoint
- tomcat服务器解析(二) --- Endpoint
- tomcat服务器解析(三)---- Handler for Endpoint
- tomcat服务器解析(七)-- Processor&Endpoint&ProtocolHandler
- tomcat服务器解析(一)
- tomcat服务器学习(二)
- tomcat服务器解析(五)-- Poller
- tomcat服务器解析(六)-- Acceptor
- Tomcat服务器学习和使用(二)
- JavaWeb学习心得之Tomcat服务器(二)
- 初步了解Tomcat服务器(二)
- Tomcat服务器配置二
- Tomcat 请求过程源码解析(二)
- tomcat 解析(二)-消息处理过程
- Tomcat源码解析(二):Connector
- tomcat解析(二)
- (二)Tomcat源码解析 - Tomcat架构详解
- Tomcat总体架构(Tomcat源码解析系列二)
- 1小时教你理解HTTP,TCP,UDP,Socket,WebSocket
- Android 铃声多媒体音量、静音、震动!震动!震动!哈哈(附源码)
- JVM相关
- MVC——ajax发送delete请求 报错404,405
- 开源静态代码分析工具列表---开源介绍
- tomcat服务器解析(二) --- Endpoint
- Tableau 10.3 简单Dashboard创建
- checkbox全选或反选
- openjudge Highways(最小生成树)
- php 文件及目录处理
- 数据结构学习笔记
- 腾讯 Web UI 解决方案 QMUI Web —— 探索与沉淀
- Android studio 上传到 GitHub
- jsp动态include 头部页面