之前看到一篇文章關(guān)于SpringMVC中Request線程安全問題强窖,文中提到每次請(qǐng)求服務(wù)器都會(huì)從線程池中取一個(gè)線程接收處理凸椿,而Request是每個(gè)線程的變量〕崮纾看完后不禁引起我的思考脑漫,Request是從怎樣產(chǎn)生的髓抑,是什么把請(qǐng)求數(shù)據(jù)封裝成Request的呢?帶著問題优幸,開始了我的研究道路吨拍。
Http請(qǐng)求處理流程
從本質(zhì)來說,Http請(qǐng)求其實(shí)是客戶端與服務(wù)器建立socket進(jìn)行數(shù)據(jù)通訊网杆。
為什么我會(huì)這么說羹饰,希望看完這篇文章你能心領(lǐng)神會(huì)。
從宏觀角度看問題跛璧, Tomcat接收Http請(qǐng)求過程如下:
Http請(qǐng)求 -> Connector -> Protocol -> Endpoint
NioEndpoint是非阻塞IO,所以對(duì)請(qǐng)求進(jìn)行了Nio處理严里,它被Acceptor
、Poller
(NioEndpoint的內(nèi)部類)追城、Worker
分開處理刹碾。Acceptor只負(fù)責(zé)控制連接數(shù)和接收請(qǐng)求,Acceptor請(qǐng)求接收請(qǐng)求后會(huì)通過隊(duì)列(PollerEvent棧)發(fā)送請(qǐng)求給Poller座柱,使用了典型的生產(chǎn)者-消費(fèi)者
模式迷帜。在Poller中,維護(hù)了一個(gè)Seletor對(duì)象色洞,
Acceptor
戏锹、Poller
、Worker
的工作流程可以總結(jié)如下圖:
下面以Tomcat9.0的Nio為例火诸,進(jìn)行分析源碼:
Connector的生命周期:構(gòu)造器 -> initInternal( ) -> startInternal( ) -> stopInternal( )
為了抓住重點(diǎn)锦针,我們從startInternal( )執(zhí)行完畢開始,此時(shí)Connector置蜀、Protocol奈搜、Endpoint已經(jīng)初始化好實(shí)例。Acceptor和Poller開始監(jiān)聽請(qǐng)求盯荤。
Acceptor
①只要endpoint處于運(yùn)行(running)狀態(tài)馋吗,Acceptor線程會(huì)不斷接受http請(qǐng)求;
②如果當(dāng)前endpoint連接數(shù)大于最大連接數(shù)(maxConnections)事秋秤,它會(huì)阻塞等待至有空閑連接后繼續(xù)輪詢宏粤。
③Acceptor會(huì)調(diào)用endpoint.serverSocketAccept( )接受請(qǐng)求獲取的SocketChannel。實(shí)際就是通過NioServerSocketChannel.accept( )獲取SocketChannel灼卢。
④隨后會(huì)把獲取的NioChannel綁定一個(gè)PollerEvent加入到Poller的PollerEvent棧中(見NioEndpoint.java)
Acceptor.java
: 代碼備注與上面序號(hào)對(duì)應(yīng)
public void run() {
int errorDelay = 0;
// Loop until we receive a shutdown command
while (endpoint.isRunning()) { //①
...
try {
//if we have reached max connections, wait
endpoint.countUpOrAwaitConnection(); //②
// Endpoint might have been paused while waiting for latch
// If that is the case, don't accept new connections
if (endpoint.isPaused()) {
continue;
}
U socket = null; //Http11NioProtocol中的U是SocketChannel
try {
// Accept the next incoming connection from the server
// socket
socket = endpoint.serverSocketAccept(); //③
} catch (Exception ioe) {
...
}
// Successful accept, reset the error delay
errorDelay = 0;
// Configure the socket
if (endpoint.isRunning() && !endpoint.isPaused()) {
// setSocketOptions() will hand the socket off to
// an appropriate processor if successful
if (!endpoint.setSocketOptions(socket)) {
endpoint.closeSocket(socket); //④
}
}
...
}
state = AcceptorState.ENDED;
}
Poller
Poller是NioEndpoint的內(nèi)部類绍哎,是Nio協(xié)議與其他協(xié)議不同的特殊處理類,也是關(guān)鍵類鞋真。它使用事件驅(qū)動(dòng)方式處理socket蛇摸,非阻塞交給Worker的線程池執(zhí)行。這也是NIO模式與BIO模式的最主要區(qū)別灿巧,在并發(fā)量大的場(chǎng)景下可以顯著提升Tomcat的效率赶袄。繼續(xù)上面的代碼分析:
1. 綁定PollerEvent
①Acceptor調(diào)用NioEndpoint.setSocketOptions( ),首先將SocketChannel設(shè)置為非阻塞狀態(tài)抠藕;然后獲取Socket將其封裝成NioChannel饿肺,注冊(cè)到NioEndpoint第一個(gè)Poller。
NioEndpoint.java
:
@Override
protected boolean setSocketOptions(SocketChannel socket) {
// Process the connection
try {
//disable blocking, APR style, we are gonna be polling it
socket.configureBlocking(false); //設(shè)置為非阻塞
Socket sock = socket.socket();
socketProperties.setProperties(sock);
...
//復(fù)用NioChannel池中的NioChannel盾似,如果沒有則使用socket新建一個(gè)
...
getPoller0().register(channel); //將NioChannel注冊(cè)到第一個(gè)Poller(實(shí)際最多只能2個(gè))
} catch (Throwable t) {
...
// Tell to close the socket
return false;
}
return true;
}
②Poller.register( )中會(huì)把NioChannel與當(dāng)前Poller綁定敬辣,并創(chuàng)建一個(gè)NioSocketWrapper賦值給NioChannel。NioSocketWrapper包含著很多重要的管理這次連接的屬性零院,如讀寫超時(shí)時(shí)間等溉跃。然后,Poller會(huì)用NioChannel封裝成PollerEvent告抄,如果eventCache有可復(fù)用則拿出來 reset( ) 沒有就 new 一個(gè)撰茎。
NioEndpoint.Poller.java
:
public class Poller implements Runnable {
private Selector selector;
private final SynchronizedQueue<PollerEvent> events =
new SynchronizedQueue<>();
...
public void register(final NioChannel socket) {
socket.setPoller(this);
NioSocketWrapper ka = new NioSocketWrapper(socket, NioEndpoint.this);
socket.setSocketWrapper(ka);
ka.setPoller(this);
ka.setReadTimeout(getConnectionTimeout());
ka.setWriteTimeout(getConnectionTimeout());
ka.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
ka.setSecure(isSSLEnabled());
PollerEvent r = eventCache.pop(); //復(fù)用已用的PollerEvent
ka.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.
if ( r==null) r = new PollerEvent(socket,ka,OP_REGISTER);
else r.reset(socket,ka,OP_REGISTER);
addEvent(r);
}
private void addEvent(PollerEvent event) {
events.offer(event); //添加PollerEvent到棧,給Poller輪詢調(diào)用
if ( wakeupCounter.incrementAndGet() == 0 ) selector.wakeup();
}
}
2. 處理PollerEvent與Socket
①Poller會(huì)輪詢通過events( )監(jiān)聽PollerEvent募疮,當(dāng)有新的PollerEvent加入棧,它會(huì)執(zhí)行PollerEvent.run把它消費(fèi)掉炫惩。消費(fèi)過程中會(huì)把NioChannel注冊(cè)到Poller的Selector中,類型為讀阿浓。典型的Nio操作channel.register(selector, SelectionKey.OP_READ)
筐钟。
②SocketChannel事件注冊(cè)好了普办,自然會(huì)觸發(fā)阻塞等待的selector.select(selectorTimeout)
。
③接下來就是Nio的操作了。遍歷selectedKeys獲取SelectionKey逐個(gè)處理妇菱。這里Poller交給了processKey( )
④processSocket中會(huì)根據(jù)SelectionKey的讀寫類型執(zhí)行processSocket( )
⑤processSocket( )會(huì)復(fù)用或創(chuàng)建一個(gè)SocketProcessor(相當(dāng)于Worker)使用線程池執(zhí)行SocketChannel
NioEndpoint.Poller.java
:
@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);
}
....
//either we timed out or we woke up, process events first
if ( keyCount == 0 ) hasEvents = (hasEvents | events());
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();
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();
}
protected void processKey(SelectionKey sk, NioSocketWrapper attachment) {
...
if ( sk.isValid() && attachment != null ) {
...
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;
}
...
}
//AbstractEndpoint.java
public boolean processSocket(SocketWrapperBase<S> socketWrapper,
SocketEvent event, boolean dispatch) {
try {
...
SocketProcessorBase<S> 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();
}
...
}
}
類比Nio Demo
大家最初學(xué)習(xí)Nio時(shí),大概都接觸過一個(gè)經(jīng)典的Demo苛聘。下面我們就用它來類比Tomcat接收請(qǐng)求的流程:
①對(duì)應(yīng)的是NioEndpoint.bind()->initServerSocket()
它是在NioPoint初始化時(shí)執(zhí)行的
②對(duì)應(yīng)的是Poller.run( )輪詢監(jiān)聽selector涂炎。得到SelectionKey后根據(jù)類型執(zhí)行對(duì)應(yīng)的操作,即執(zhí)行Poller.processKey( )
③Tomcat與demo最大不同之處在于设哗,它把a(bǔ)ccept( )抽出來唱捣,用一個(gè)線程接收請(qǐng)求,也就是Acceptor网梢。Acceptor將請(qǐng)求封裝成PollerEvent丟給Poller處理震缭。
/** ① Begin **/
Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
/** ① End **/
/** ② Begin **/
while(true) {
int readyChannels = selector.selectNow();
if(readyChannels == 0) continue;
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if(key.isAcceptable()) {
/**endpoint.serverSocketAccept()**/
accept(selectionKey);
/** End **/
channel.register(selector, SelectionKey.OP_READ); //endpoint.setSocketOptions(socket)
} else if (key.isConnectable()) {
// a connection was established with a remote server.
} else if (key.isReadable()) {
// a channel is ready for reading
} else if (key.isWritable()) {
// a channel is ready for writing
}
keyIterator.remove();
}
/** ② End **/
}
/** ③ Begin **/
private void accept(SelectionKey selectionKey) throws IOException {
ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
SocketChannel channel = ssc.accept(); //endpoint.serverSocketAccept()
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ); //endpoint.setSocketOptions(socket)
}
/** ③ End **/
對(duì)比后能發(fā)現(xiàn),Tomcat用Nio處理Socket其實(shí)萬變不離其中战虏,都源于這個(gè)demo拣宰;Tomcat只是將其中的步驟封裝成Acceptor党涕,Poller, Worker分工合作而已巡社。
小結(jié)
本文介紹了Tomcat使用Nio協(xié)議接收Http請(qǐng)求的過程膛堤,通過源碼分析了解Acceptor是如何接收請(qǐng)求,通過生產(chǎn)者-消費(fèi)者模式通知到Poller處理晌该。其中涉及到Nio接收socket的模型肥荔;最后用Nio的經(jīng)典demo與Tomcat進(jìn)行對(duì)比,更加簡(jiǎn)化朝群、深入理解當(dāng)中的原理燕耿。
寫到這里我們已經(jīng)知道Tomcat接收Http請(qǐng)求的實(shí)現(xiàn)原理(接收socket到處理socket),但仍未看見Request姜胖,我們一開始的目標(biāo)仍未實(shí)現(xiàn)誉帅。
想知道Work是如何將socket一步步處理轉(zhuǎn)化成servlet的Request。由于篇幅有限谭期,欲知后事如何請(qǐng)關(guān)注Http請(qǐng)求是如何轉(zhuǎn)化成Request的(二)堵第。