上一篇我們試了下怎麼寫一個簡易的Servlet容器,這段時間繼續看了下面的章節,講的內容對應的分支V1.5和V1.6
https://github.com/lovejj1994/SimpleServlet
- v1.5 實現 tomcat4/5 默認的連接器
- v1.6 重點改進Processor多線程支持,並且完善BootStrap,Connector,Processor等組件
Connector
上一篇是一個簡單的demo,這一節模仿tomcat的寫法,對一些功能做拆分,實現一個tomcat4的默認連接器的簡化版,大致流程如下圖。
首先上一篇文章中的HttpServlet同時做了Connector和Processor的工作,這一分支會被替換成HttpConnector和HttpProcessor,而HttpConnector在tomcat中稱之爲連接器,它負責ServerSocket的創建,接受http請求和分發請求給Processor(處理線程)等工作,下面是改進的代碼:
這段代碼包括了一個processors隊列,我們稱爲處理線程,下節再說。
package connector.http;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.time.LocalDateTime;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Logger;
public class HttpConnector implements Runnable {
private static final Logger log = Logger.getLogger(HttpConnector.class.getName());
private boolean shutdown = false;
private String scheme = "http";
private static final int port = 8080;
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
ServerSocket serverSocket = null;
/**
* The background thread.
*/
private Thread thread = null;
/**
* The minimum number of processors to start at initialization time.
*/
protected int minProcessors = 5;
/**
* The maximum number of processors allowed, or <0 for unlimited.
*/
private int maxProcessors = 15;
/**
* The current number of processors that have been created.
*/
private int curProcessors = 0;
/**
* Processor線程池,可以多線程處理請求
* The set of processors that have been created but are not currently
* being used to process a request.
*/
private ConcurrentLinkedQueue<HttpProcessor> processors = new ConcurrentLinkedQueue();
public String getScheme() {
return scheme;
}
public HttpConnector() {
}
@Override
public void run() {
while (true) {
log.info("等待指令。。。。" + LocalDateTime.now().toString());
Socket socket;
InputStream input;
OutputStream output;
try {
socket = serverSocket.accept();
HttpProcessor httpProcessor = getProcessor();
if (httpProcessor == null) {
try {
log.info("線程池已空,忽略請求");
socket.close();
} catch (IOException e) {
continue;
}
}
httpProcessor.assign(socket);
} catch (Exception e) {
continue;
}
}
}
/**
* Recycle the specified Processor so that it can be used again.
*
* @param processor The processor to be recycled
*/
void recycle(HttpProcessor processor) {
processors.offer(processor);
}
/**
* Create and return a new processor suitable for processing HTTP
* requests and returning the corresponding responses.
*/
private HttpProcessor newProcessor() {
curProcessors++;
HttpProcessor processor = new HttpProcessor(this);
new Thread(processor).start();
return processor;
}
/**
* Create (or allocate) and return an available processor for use in
* processing a specific HTTP request, if possible. If the maximum
* allowed processors have already been created and are in use, return
* <code>null</code> instead.
*/
private HttpProcessor getProcessor() {
if (processors.size() > 0) {
return processors.poll();
}
if ((maxProcessors > 0) && (curProcessors < maxProcessors)) {
return (newProcessor());
} else {
if (maxProcessors < 0) {
return (newProcessor());
} else {
return null;
}
}
}
/**
* 初始化ServerSocket
*/
public void initialize() throws IOException {
serverSocket = new ServerSocket(port, 1, InetAddress.getByName("localhost"));
}
/**
* 初始化Connector,包括初始化Processors線程池
*
* @throws LifecycleException if a fatal startup error occurs
*/
public void start() {
// Start our background thread
threadStart();
// Create the specified minimum number of processors
while (curProcessors < minProcessors) {
if ((maxProcessors > 0) && (curProcessors >= maxProcessors))
break;
HttpProcessor processor = newProcessor();
recycle(processor);
}
}
/**
* 啓動Connector線程
*/
private void threadStart() {
log.info("httpConnector.starting");
thread = new Thread(this, "SimpleServlet v1.6");
thread.setDaemon(true);
thread.start();
}
}
Processor 與 Connector 的配合
HttpProcessor主要就是拿到socket實例後進行數據解析,根據情況轉發給ServletProcessor或StaticResourceProcessor。在上一篇文章中,整個程序是同步執行的,在一個請求沒有完全響應之前,是不能響應下一個請求的,所以在這一分支中,我們讓HttpConnector擁有一個HttpProcessor連接池,Connector拿到的請求都批量分發給Processor連接池,達到多線程並行執行的目的。代碼很長,就沒貼出來,可以去github看源代碼。
這裏有涉及到多線程的應用,重要的方法包括await()和assign(Socket socket)方法,HttpProcessor和HttpConnector是如何配合的呢?
先看下面是HttpProcessor的run方法和HttpConnector的newProcessor方法,newProcessor主要爲processor線程池創建實例並且啓動processor實例,因爲HttpProcessor實現了Runnable接口,所以激活了HttpProcessor的run方法。
HttpProcessor run()
@Override
public void run() {
// Process requests until we receive a shutdown signal
while (true) {
// Wait for the next socket to be assigned
Socket socket = await();
if (socket == null) {
continue;
}
// Process the request from this socket
try {
process(socket);
socket.close();
} catch (Throwable t) {
throw new RuntimeException(t);
}
// Finish up this request
connector.recycle(this);
}
}
HttpConnector newProcessor()
private HttpProcessor newProcessor() {
curProcessors++;
HttpProcessor processor = new HttpProcessor(this);
new Thread(processor).start();
return processor;
}
但是HttpProcessor 剛創建的實例是沒有socket給它處理的,所以我們要看下HttpProcessor 的 await()方法。available默認是false,所以await()會阻塞線程。
HttpProcessor await()
private synchronized Socket await() {
// Wait for the Connector to provide a new Socket
while (!available) {
try {
wait();
} catch (InterruptedException e) {
}
}
// Notify the Connector that we have received this Socket
Socket socket = this.socket;
available = false;
notifyAll();
return socket;
}
看到這 我們又要跳回到HttpConnector的run()方法,當serverSocket.accept()接收到socket實例時,會從processors連接池裏彈出一個已經初始化好的實例,然後調用assign方法。
HttpConnector run()
public void run() {
while (true) {
log.info("等待指令。。。。" + LocalDateTime.now().toString());
Socket socket;
InputStream input;
OutputStream output;
try {
socket = serverSocket.accept();
HttpProcessor httpProcessor = getProcessor();
if (httpProcessor == null) {
try {
log.info("線程池已空,忽略請求");
socket.close();
} catch (IOException e) {
continue;
}
}
httpProcessor.assign(socket);
} catch (Exception e) {
continue;
}
}
}
因爲available爲false,所以assign方法不會走while循環,它會拿到HttpConnector 給的socket實例,並將available設爲true,關鍵的來了,它通過notifyAll 喚醒之前HttpProcessor run方法裏阻塞的線程,因爲available已經爲true,所以HttpProcessor await方法的代碼會跳出循環,繼續往下執行,而且available會重新設爲false,再下面處理socket的過程跟上一篇文章一樣。
因爲存在socket還沒處理完(available 仍爲true),HttpConnector又給HttpProcessor一個socket待處理,所以這時調用assign方法會進入while循環並阻塞,直到前一個任務處理完並設available爲false並且notifyAll 喚醒線程。
HttpProcessor assign(Socket socket)
synchronized void assign(Socket socket) {
// Wait for the Processor to get the previous Socket
while (available) {
try {
wait();
} catch (InterruptedException e) {
}
}
// Store the newly available Socket and notify our thread
this.socket = socket;
available = true;
notifyAll();
}
處理完socket之後,會執行一個recycle方法回收processor線程
HttpConnector recycle(HttpProcessor processor)
void recycle(HttpProcessor processor) {
processors.offer(processor);
}
BootStrap
前面可以知道,Connector具有舉足輕重的作用,它支配Processor的運行,那誰負責Connector的啓動呢?這就是BootStrap 的工作了.
package startup;
import connector.http.HttpConnector;
import java.io.IOException;
public class BootStrap {
public static void main(String[] args) throws IOException {
HttpConnector httpConnector = new HttpConnector();
httpConnector.initialize();
httpConnector.start();
}
}
initialize 用於SocketServer的初始化;
HttpConnector initialize()
/**
* 初始化ServerSocket
*/
public void initialize() throws IOException {
serverSocket = new ServerSocket(port, 1, InetAddress.getByName("localhost"));
}
因爲HttpConnector實現了Runnable,所以start用於啓動Connector線程
HttpConnector start()
/**
* 初始化Connector,包括初始化Processors線程池
*
* @throws LifecycleException if a fatal startup error occurs
*/
public void start() {
// Start our background thread
threadStart();
// Create the specified minimum number of processors
while (curProcessors < minProcessors) {
if ((maxProcessors > 0) && (curProcessors >= maxProcessors))
break;
HttpProcessor processor = newProcessor();
recycle(processor);
}
}