tomcat&jetty筆記2

Jetty架構特點之Connector組件

Tomcat一樣,Jetty也是一個“HTTP服務器 + Servlet容器 Jetty中的Connector組件和Handler組件分別來實現這兩個功能,而這兩個組件工作時所需要的線程資源都直接從一個全局線程池ThreadPool中獲取。

Jetty Server可以有多個Connector在不同的端口上監聽客戶請求,而對於請求處理的Handler組件,也可以根據具體場景使用不同的Handler。這樣的設計提高了Jetty的靈活性,需要支持Servlet,則可以使用ServletHandler;需要支持Session,則再增加一個SessionHandler。也就是說我們可以不使用Servlet或者Session,只要不配置這個Handler就行了。

爲了啓動和協調上面的核心組件工作,Jetty提供了一個Server類來做這個事情,它負責創建並初始化ConnectorHandlerThreadPool組件,然後調用start方法啓動它們。

二者區別

第一個區別是Jetty中沒有Service的概念,Tomcat中的Service包裝了多個連接器和一個容器組件,一個Tomcat實例可以配置多個Service,不同的Service通過不同的連接器監聽不同的端口;而JettyConnector是被所有Handler共享的。

第二個區別是,在Tomcat中每個連接器都有自己的線程池,而在Jetty中所有的Connector共享一個全局的線程池。

Connector組件

Tomcat一樣,Connector的主要功能是對I/O模型和應用層協議的封裝。I/O模型方面,最新的Jetty 9版本只支持NIO,因此JettyConnector設計有明顯的Java NIO通信模型的痕跡。至於應用層協議方面,跟TomcatProcessor一樣,Jetty抽象出了Connection組件來封裝應用層協議的差異。

Java NIO回顧

Java NIO的核心組件是ChannelBufferSelectorChannel表示一個連接,可以理解爲一個Socket,通過它可以讀取和寫入數據,但是並不能直接操作數據,需要通過Buffer來中轉。

Selector可以用來檢測Channel上的I/O事件,比如讀就緒、寫就緒、連接就緒,一個Selector可以同時處理
多個Channel,因此單個線程可以監聽多個Channel,這樣會大量減少線程上下文切換的開銷。下面我們通
過一個典型的服務端NIO程序來回顧一下如何使用這些組件。

首先,創建服務端Channel,綁定監聽端口並把Channel設置爲非阻塞方式。

ServerSocketChannel server = ServerSocketChannel.open();
server.socket().bind(new InetSocketAddress(port));
server.configureBlocking(false);

然後,創建Selector,並在Selector中註冊Channel感興趣的事件OP_ACCEPT,告訴Selector如果客戶端有新的連接請求到這個端口就通知我。

Selector selector = Selector.open();
server.register(selector, SelectionKey.OP_ACCEPT);

接下來,Selector會在一個死循環裏不斷地調用select()去查詢I/O狀態,select()會返回一個SelectionKey列表,Selector會遍歷這個列表,看看是否有客戶感興趣的事件,如果有,就採取相應的動作。

比如下面這個例子,如果有新的連接請求,就會建立一個新的連接。連接建立後,再註冊Channel的可讀事件到Selector中,告訴Selector我對這個Channel上是否有新的數據到達感興趣。

while (true) {
    selector.select();//查詢I/O事件
    for (Iterator<SelectionKey> i = selector.selectedKeys().iterator(); i.hasNext();) {
        SelectionKey key = i.next();
        i.remove();
        if (key.isAcceptable()) {
            // 建立一個新連接
            SocketChannel client = server.accept();
            client.configureBlocking(false);
            //連接建立後, 告訴Selector, 我現在對I/O可讀事件感興趣
            client.register(selector, SelectionKey.OP_READ);
        }
    }
}

服務端在I/O通信上主要完成了三件事情:監聽連接、I/O事件查詢以及數據讀寫。因此Jetty設計了AcceptorSelectorManagerConnection來分別做這三件事情,下面分別來說說這三個組件。

Acceptor

Acceptor用於接受請求,跟Tomcat一樣,Jetty也有獨立的Acceptor線程組用於處理連接請求。在Connector的實現類ServerConnector中,有一個_acceptors的數組,在Connector啓動的時候, 會根據_acceptors數組的長度創建對應數量的Acceptor,而Acceptor的個數可以配置。

for (int i = 0; i < _acceptors.length; i++){
    Acceptor a = new Acceptor(i);
    getExecutor().execute(a);
}

AcceptorServerConnector中的一個內部類,同時也是一個RunnableAcceptor線程是通過getExecutor()得到的線程池來執行的,前面提到這是一個全局的線程池。

Acceptor通過阻塞的方式來接受連接,這一點跟Tomcat也是一樣的。

public void accept(int acceptorID) throws IOException{
    ServerSocketChannel serverChannel = _acceptChannel;
    if (serverChannel != null && serverChannel.isOpen()){
        // 這⾥是阻塞的
        SocketChannel channel = serverChannel.accept();
        // 執⾏到這⾥時說明有請求進來了
        accepted(channel);
    }
}

接受連接成功後會調用accepted()函數,accepted()函數中會將SocketChannel設置爲非阻塞模式,然後交給Selector去處理,因此這也就到了Selector的地界了。

private void accepted(SocketChannel channel) throws IOException{
    channel.configureBlocking(false);
    Socket socket = channel.socket();
    configure(socket);
    // _manager是SelectorManager實例, ⾥⾯管理了所有的Selector實例
    _manager.accept(channel);
}

SelectorManager

JettySelectorSelectorManager類管理,而被管理的Selector叫作ManagedSelectorSelectorManager內部有一個ManagedSelector數組,真正幹活的是ManagedSelector

public void accept(SelectableChannel channel, Object attachment){
    //選擇一個ManagedSelector來處理Channel
    final ManagedSelector selector = chooseSelector();
    //提交一個任務Accept給ManagedSelector
    selector.submit(selector.new Accept(channel, attachment));
}

SelectorManager從本身的Selector數組中選擇一個Selector來處理這個Channel,並創建一個任務Accept交給ManagedSelectorManagedSelector在處理這個任務主要做了兩步:

第一步,調用Selectorregister方法把Channel註冊到Selector上,拿到一個SelectionKey

_key = _channel.register(selector, SelectionKey.OP_ACCEPT, this);

第二步,創建一個EndPointConnection,並跟這個SelectionKeyChannel)綁在一起:

private void createEndPoint(SelectableChannel channel, SelectionKey selectionKey) throws IOException{
    //1. 創建Endpoint
    EndPoint endPoint = _selectorManager.newEndPoint(channel, this, selectionKey);
    //2. 創建Connection
    Connection connection = _selectorManager.newConnection(channel, endPoint, selectionKey.attachment());
    //3. 把Endpoint、 Connection和SelectionKey綁在一起
    endPoint.setConnection(connection);
    selectionKey.attach(endPoint);
}

你到餐廳喫飯,先點菜(註冊I/O事件),服務員(ManagedSelector)給你一個單子(SelectionKey),等菜做好了(I/O事件到了),服務員根據單子就知道是哪桌點了這個菜,於是喊一嗓子某某桌的菜做好了(調用了綁定在SelectionKey上的EndPoint的方法)。

ManagedSelector並沒有調用直接EndPoint的方法去處理數據,而是通過調用EndPoint的方法返回一個Runnable,然後把這個Runnable扔給線程池執行,這個Runnable纔會去真正讀數據和處理請求。

Connection

RunnableEndPoint的一個內部類,它會調用Connection的回調方法來處理請求。JettyConnection組件類比就是TomcatProcessor,負責具體協議的解析,得到Request對象,並調用Handler容器進行處理。下面我簡單介紹一下它的具體實現類HttpConnection對請求和響應的處理過程。

請求處理HttpConnection並不會主動向EndPoint讀取數據,而是向在EndPoint中註冊一堆回調方法:

getEndPoint().fillInterested(_readCallback);

告訴EndPoint,數據到了你就調我這些回調方法_readCallback吧,有點異步I/O的感覺,也就是說Jetty在應用層面模擬了異步I/O模型。

而在回調方法_readCallback裏,會調用EndPoint的接口去讀數據,讀完後讓HTTP解析器去解析字節流,HTTP解析器會將解析後的數據,包括請求行、請求頭相關信息存到Request對象裏。

響應處理Connection調用Handler進行業務處理,Handler會通過Response對象來操作響應流,向流裏面寫入數據,HttpConnection再通過EndPoint把數據寫到Channel,這樣一次響應就完成了。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章