標籤:bin handle cat bool 關閉 簡單 pool amr print
Tomcat的模組結構設計的相當好,而且其Web 容器的效能相當出色。JBoss直接就使用了Tomcat的web容器,WebLogic的早期版本也是使用了Tomcat的代碼。
Web容器的工作過程在下面的第二個參考文檔中的文檔已經說得相當清楚,我也就不再重複說了。如果不清楚調用過程,需要先看這個文檔。這裡分析一下Connector的處理過程。
1. 一個簡單的Web Server樣本
這個例子也是從網上找得,不知道原作者,也就不在參考資料中引用了。
這個啟動服務的主程式。
public class HttpServer {
public static void main(String args[]) {
int port;
ServerSocket server_socket; // 讀取伺服器連接埠號碼
try {
port = Integer.parseInt(args[0]);
} catch (Exception e) {
port = 8080;
}
try {
// 監聽伺服器連接埠,等待串連請求
server_socket = new ServerSocket(port);
System.out.println("httpServer running on port "
+ server_socket.getLocalPort());
// 顯示啟動資訊
while (true) {
Socket socket = server_socket.accept();
System.out.println("New connection accepted "
+ socket.getInetAddress() + ":" + socket.getPort());
// 建立分線程
try {
HttpRequestHandler request = new HttpRequestHandler(socket);
Thread thread = new Thread(request);
// 啟動線程
thread.start();
} catch (Exception e) {
System.out.println(e);
}
}
} catch (IOException e) {
System.out.println(e);
}
}
}
下面是建立輸出的線程
public class HttpRequestHandler implements Runnable {
final static String CRLF = "\r\n";
Socket socket;
InputStream input;
OutputStream output;
BufferedReader br;
// 構造方法
public HttpRequestHandler(Socket socket) throws Exception {
this.socket = socket;
this.input = socket.getInputStream();
this.output = socket.getOutputStream();
this.br = new BufferedReader(new InputStreamReader(socket
.getInputStream()));
}
// 實現Runnable 介面的run()方法
public void run() {
try {
processRequest();
} catch (Exception e) {
System.out.println(e);
}
}
private void processRequest() throws Exception {
while (true) {
// 讀取並顯示網頁瀏覽器提交的請求資訊
String headerLine = br.readLine();
System.out.println("The client request is " + headerLine);
if (headerLine.equals(CRLF) || headerLine.equals(""))
break;
StringTokenizer s = new StringTokenizer(headerLine);
String temp = s.nextToken();
if (temp.equals("GET")) {
String fileName = s.nextToken();
fileName = "." + fileName;
// 開啟所請求的檔案
FileInputStream fis = null;
boolean fileExists = true;
try {
fis = new FileInputStream("D:/workspace/tomcat/bin/"+fileName);
} catch (FileNotFoundException e) {
fileExists = false;
}
// 完成回應訊息
String serverLine = "Server: a simple java httpServer";
String statusLine = null;
String contentTypeLine = null;
String entityBody = null;
String contentLengthLine = "error";
if (fileExists) {
statusLine = "HTTP/1.0 200 OK" + CRLF;
contentTypeLine = "Content-type: " + contentType(fileName) + CRLF;
contentLengthLine = "Content-Length: "
+ (new Integer(fis.available())).toString() + CRLF;
} else {
statusLine = "HTTP/1.0 404 Not Found" + CRLF;
contentTypeLine = "text/html";
entityBody = "<HTML>"
+ "<HEAD><TITLE>404 Not Found</TITLE></HEAD>" + "<BODY>404 Not Found" + "<br>usage:http://yourHostName:port/" + "fileName.html</BODY></HTML>";
}
// 發送到伺服器資訊
output.write(statusLine.getBytes());
output.write(serverLine.getBytes());
output.write(contentTypeLine.getBytes());
output.write(contentLengthLine.getBytes());
output.write(CRLF.getBytes());
// 發送資訊內容
if (fileExists) {
sendBytes(fis, output);
fis.close();
} else {
output.write(entityBody.getBytes());
}
}
}
// 關閉通訊端和流
try {
output.close();
br.close();
socket.close();
} catch (Exception e) {
}
}
private static void sendBytes(FileInputStream fis, OutputStream os)
throws Exception {
// 建立一個 1K buffer
byte[] buffer = new byte[1024];
int bytes = 0;
// 將檔案輸出到通訊端輸出資料流中
while ((bytes = fis.read(buffer)) != -1) {
os.write(buffer, 0, bytes);
}
}
private static String contentType(String fileName) {
if (fileName.endsWith(".htm") || fileName.endsWith(".html")) {
return "text/html";
}
return "fileName";
}
}
這個簡單的例子說明的web服務的基本實現。Tomcat在此之上模組化出線程池,網路連接和WebHttp協議3個包。線程池可獨立使用,網路連接使用池化,WebHttp直接從網路連接池中擷取即可。
2. 線程池的實現
這個功能的實現在包 org.apache.tomcat.util.thread 中。
ThreadPool是線程池,是這個功能實現的核心。它使用了所有的其他類進行工作。在類圖中,所有的其他類都是被此類的使用關係。
我們來看此類是如何工作得。
啟動串連池的方法:
public synchronized void start() {
stopThePool = false;
currentThreadCount = 0;
currentThreadsBusy = 0;
adjustLimits();
pool = new ControlRunnable[maxThreads];
openThreads(minSpareThreads);
if (maxSpareThreads < maxThreads) {
monitor = new MonitorRunnable(this);
}
}
方法中,根據配置情況,初始化所有線程進入備用狀態。
首先定義maxThreads數目的數組,但是僅僅初始化其中minSpareThreads個。MonitorRunnable用於檢查,是否空閑數目超過 maxSpareThreads個。
currentThreadCount 是當前初始化可以使用的線程數目,而currentThreadsBusy 是當前正在使用的線程數目。
使用串連池的方法:
public void runIt(ThreadPoolRunnable r) {
if (null == r) {
throw new NullPointerException();
}
ControlRunnable c = findControlRunnable();
c.runIt(r);
}
該方法中,先尋找可用的線程,找到後在其中運行即可。
找可用線程的方法也很簡單,就是將線程數組中第 currentThreadCount - currentThreadsBusy - 1 個元素取出返回,然後將此元素設成null。
線程運行完畢後,設定currentThreadsBusy-- ,然後將 currentThreadCount - currentThreadsBusy - 1 的線程放回就可以了。
線程不夠用就等待,等待失敗就拋出異常。
說明一下上面未提到的類的功能:
ThreadPoolRunnable 這是一個介面,規定了一個線程運行時需要啟動並執行一些動作。這裡需要寫一些商務邏輯的代碼了。
ThreadWithAttributes 這個類從上面的代碼中沒有看到,這個類標識當前運行線程的一些特徵,比如記錄當前運行線程的一些狀態。
ThreadPoolListener 用於監控ThreadPool中新增線程的情況。
ControlRunnable 這個類是ThreadPool的內部類,用於運行ThreadPoolRunnable 。當ThreadPoolRunnable 運行完畢後,通知ThreadPool回收線程。它時刻處於備用狀態。此對象執行個體化後,就一直在死迴圈檢查是否有它需要啟動並執行東西。
3. 網路連接功能的實現
這個功能的實現在包 org.apache.tomcat.util.net 中。
網路連接功能構建於線程池之上,實現了一個串連服務模型。伺服器開啟連接埠,池化進入串連,為進入的串連建立背景工作執行緒。
Tomcat的網路連接兩個主要的應用是1. 自己提供的web應用。2. 給Apache提供的web應用。這兩個過程的解析過程都是一樣的。僅僅在於網路連接協議有差別而已。兩個應用都使用此包的功能實現。
PoolTcpEndpoint是核心,它使用了ThreadPool。TcpWorkerThread通過調用介面TcpConnectionHandler來完成一次串連需要完成的工作。TcpConnection標識了一個連線物件。
PoolTcpEndpoint的初始化方法代碼很簡單,在構建器中建立或引用ThreadPool,在初始化時建立ServerSocket,用於偵聽用戶端串連。
下面是初始化方法
public void initEndpoint() throws IOException, InstantiationException {
try {
if (factory == null)
factory = ServerSocketFactory.getDefault();
if (serverSocket == null) {
try {
if (inet == null) {
serverSocket = factory.createSocket(port, backlog);
} else {
serverSocket = factory
.createSocket(port, backlog, inet);
}
} catch (BindException be) {
throw new BindException(be.getMessage() + ":" + port);
}
}
if (serverTimeout >= 0)
serverSocket.setSoTimeout(serverTimeout);
} catch (IOException ex) {
// log("couldn‘t start endpoint", ex, Logger.DEBUG);
throw ex;
} catch (InstantiationException ex1) {
// log("couldn‘t start endpoint", ex1, Logger.DEBUG);
throw ex1;
}
initialized = true;
}
啟動的方法同樣簡單,僅僅將TcpWorkerThread作為線程池的背景工作執行緒,啟動串連池,就大功告成了。
public void startEndpoint() throws IOException, InstantiationException {
if (!initialized) {
initEndpoint();
}
if (isPool) {
tp.start();
}
running = true;
paused = false;
if (isPool) {
listener = new TcpWorkerThread(this);
tp.runIt(listener);
} else {
log.error("XXX Error - need pool !");
}
}
偵聽的細節封裝在TcpWorkerThread類中。運行時,它在ServerSocket連接埠偵聽。當發現有串連進入後,立刻開啟一個新線程繼續偵聽,本線程開始處理串連。下面是代碼:
public void runIt(Object perThrData[]) {
// Create per-thread cache
if (endpoint.isRunning()) {
// Loop if endpoint is paused
while (endpoint.isPaused()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// Ignore
}
}
// Accept a new connection
Socket s = null;
try {
s = endpoint.acceptSocket();
} finally {
// Continue accepting on another thread...
if (endpoint.isRunning()) {
endpoint.tp.runIt(this);
}
}
// Process the connection
if (null != s) {
TcpConnection con = null;
int step = 1;
try {
// 1: Set socket options: timeout, linger, etc
endpoint.setSocketOptions(s);
// 2: SSL handshake
step = 2;
if (endpoint.getServerSocketFactory() != null) {
endpoint.getServerSocketFactory().handshake(s);
}
// 3: Process the connection
step = 3;
con = (TcpConnection) perThrData[0];
con.setEndpoint(endpoint);
con.setSocket(s);
endpoint.getConnectionHandler().processConnection(con,
(Object[]) perThrData[1]);
} catch (SocketException se) {
……
4. 協議 web http的實現
這個功能的實現在包 org.apache.coyote.http11 中。
對於Http協議的實現核心類是Http11Protocol。具體功能的實作類別有MXPoolListener(實現ThreadPoolListener),Http11ConnectionHander(實現TcpConnectionHandler)。
Http11Protocol的初始化方法比較簡單,就是設定一下讓網路連接開始運行。
Http11ConnectionHander 則初始化類Http11Processor,由它解析請求的字串,交給產生此Connection的Connector的Container,也就是 Engine完成。Engine通過遞迴,解析應返回使用者的資料。這個過程在參考文檔中有介紹了。
tomcat web容器工作原理