Tomcat源碼之旅--讓我們的Servlet容器支持多線程

上一篇我們試了下怎麼寫一個簡易的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);
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章