Tomcat 接收連接的accept流程

轉自晴天哥_374的簡書


開篇

這篇文章的主要目的是分析下Tomcat在處理連接請求的整個過程,參考了前人的文章並在文末指出,通過時序圖能夠較清楚的走通整個流程。

Tomcat處理流程

在這裏插入圖片描述

Tomcat處理流程

說明:

Connector 啓動以後會啓動一組線程用於不同階段的請求處理過程,Acceptor、Poller、worker 所在的線程組都維護在 NioEndpoint 中。

    1. Acceptor線程組。用於接受新連接,並將新連接封裝一下,選擇一個 Poller 將新連接添加到 Poller 的事件隊列中,Acceptor線程組是多個線程組成的線程組
    1. Poller 線程組。用於監聽 Socket 事件,當 Socket 可讀或可寫等等時,將 Socket 封裝一下添加到 worker 線程池的任務隊列中,Poller線程組是多個線程組成的線程組
    1. worker 線程組。用於對請求進行處理,包括分析請求報文並創建 Request 對象,調用容器的 pipeline 進行處理,worker線程組是Executor創建的線程池
    public class NioEndpoint extends AbstractJsseEndpoint<NioChannel> {
    
        public void startInternal() throws Exception {
                // 創建worker線程組
                if ( getExecutor() == null ) {
                    createExecutor();
                }
    
                // Poller線程組由一堆線程組成
                pollers = new Poller[getPollerThreadCount()];
                for (int i=0; i<pollers.length; i++) {
                    pollers[i] = new Poller();
                    Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
                    pollerThread.setPriority(threadPriority);
                    pollerThread.setDaemon(true);
                    pollerThread.start();
                }
    
                startAcceptorThreads();
            }
        }
    }
    
    public abstract class AbstractEndpoint<S> {
        // Acceptor線程組由一堆線程組成
        protected final void startAcceptorThreads() {
            int count = getAcceptorThreadCount();
            acceptors = new Acceptor[count];
    
            for (int i = 0; i < count; i++) {
                acceptors[i] = createAcceptor();
                String threadName = getName() + "-Acceptor-" + i;
                acceptors[i].setThreadName(threadName);
                Thread t = new Thread(acceptors[i], threadName);
                t.setPriority(getAcceptorThreadPriority());
                t.setDaemon(getDaemon());
                t.start();
            }
        }
    
        // worker的線程組由executor創建線程池組成
        public void createExecutor() {
            internalExecutor = true;
            TaskQueue taskqueue = new TaskQueue();
            TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
            executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
            taskqueue.setParent( (ThreadPoolExecutor) executor);
        }
    }
    

請求處理過程分解

Acceptor接收連接過程

在這裏插入圖片描述

Acceptor.jpg

說明:
Acceptor接受的新連接沒有立即註冊到selector當中,需要先封裝成PollerEvent對象後保存至PollerEvent隊列當中,Poller對象會消費PollerEvent隊列,類似生產消費模型。

    1. Acceptor 在啓動後會阻塞在 ServerSocketChannel.accept(); 方法處,當有新連接到達時,該方法返回一個 SocketChannel。
    1. setSocketOptions()方法將 Socket 封裝到 NioChannel 中,並註冊到 Poller。
    1. 我們一開始就啓動了多個 Poller 線程,註冊的時候採用輪詢選擇 Poller 。NioEndpoint 維護了一個 Poller 數組,當一個連接分配給 pollers[index] 時,下一個連接就會分配給 pollers[(index+1)%pollers.length]。
    1. addEvent() 方法會將 Socket 添加到該 Poller 的 PollerEvent 隊列中。到此 Acceptor 的任務就完成了。
    public class NioEndpoint extends AbstractJsseEndpoint<NioChannel> {
    
        private volatile ServerSocketChannel serverSock = null;
        protected class Acceptor extends AbstractEndpoint.Acceptor {
    
            public void run() {
    
                while (running) {
                    state = AcceptorState.RUNNING;
                    try {
                        SocketChannel socket = null;
                        try {
                            // 監聽socket負責接收新連接
                            socket = serverSock.accept();
                        } catch (IOException ioe) {
                        }
    
                        if (running && !paused) {
                            // 處理接受到的socket對象
                            if (!setSocketOptions(socket)) {
                                closeSocket(socket);
                            }
                        } 
                    } catch (Throwable t) {
                    }
                }
                state = AcceptorState.ENDED;
            }
        }
    
    
        protected boolean setSocketOptions(SocketChannel socket) {
            try {
                socket.configureBlocking(false);
                Socket sock = socket.socket();
                socketProperties.setProperties(sock);
                
                channel = new NioChannel(socket, bufhandler);
                // 註冊到Poller當中
                getPoller0().register(channel);
            } catch (Throwable t) {
            }
    
            return true;
        }
    
        public Poller getPoller0() {
            int idx = Math.abs(pollerRotater.incrementAndGet()) % pollers.length;
            return pollers[idx];
        }
    
    
        public class Poller implements Runnable {
            public void register(final NioChannel socket) {
                socket.setPoller(this);
                NioSocketWrapper ka = new NioSocketWrapper(socket, NioEndpoint.this);
                r = new PollerEvent(socket,ka,OP_REGISTER);
                
                // 添加PollerEvent隊列當中
                addEvent(r);
            }
    
    
            private void addEvent(PollerEvent event) {
                // 投入到PollerEvent隊列當中
                events.offer(event);
                if ( wakeupCounter.incrementAndGet() == 0 ) selector.wakeup();
            }
        }
    
    }
 

Poller處理請求

在這裏插入圖片描述

Poller.jpg

說明:
Poller會消費PollerEvent隊列(由Acceptor進行投遞),並註冊到Selector當中。當註冊到Selector的socket數據可讀的時候將socket封裝成SocketProcessor對象,投遞到Executor實現的線程池進行處理。

    1. selector.select(1000)。當 Poller 啓動後因爲 selector 中並沒有已註冊的 Channel,所以當執行到該方法時只能阻塞。所有的 Poller 共用一個 Selector,其實現類是 sun.nio.ch.SelectorImpl。
    1. events() 方法通過 addEvent() 方法添加到事件隊列中的 Socket 註冊到SelectorImpl。這裏指的socket是accept過來的請求的socket。
    1. 當 Socket 可讀時,Poller 纔對其進行處理,createSocketProcessor() 方法將 Socket 封裝到 SocketProcessor 中,SocketProcessor 實現了 Runnable 接口。worker 線程通過調用其 run() 方法來對 Socket 進行處理。
    1. execute(SocketProcessor) 方法將 SocketProcessor 提交到線程池,放入線程池的 workQueue 中。workQueue 是 BlockingQueue 的實例。
    public class NioEndpoint extends AbstractJsseEndpoint<NioChannel> {
    
        public static class PollerEvent implements Runnable {
            private NioChannel socket;
            private int interestOps;
            private NioSocketWrapper socketWrapper;
    
            public PollerEvent(NioChannel ch, NioSocketWrapper w, int intOps) {
                reset(ch, w, intOps);
            }
    
            public void run() {
                if (interestOps == OP_REGISTER) {
                    try {
                        socket.getIOChannel().register(
                                socket.getPoller().getSelector(), SelectionKey.OP_READ, socketWrapper);
                    } catch (Exception x) {
    
                    }
                }
            }
        }
    
    
        public class Poller implements Runnable {
    
            public void run() {
                while (true) {
                    // events()負責處理PollerEvent事件並註冊到selector當中
                    hasEvents = events();
                    keyCount = selector.select(selectorTimeout);
    
                    // 處理新接受的socket的讀寫事件
                    Iterator<SelectionKey> iterator =
                        keyCount > 0 ? selector.selectedKeys().iterator() : null;
                    while (iterator != null && iterator.hasNext()) {
                        SelectionKey sk = iterator.next();
                        NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment();
    
                        processKey(sk, attachment);
                    }
                }
            }
    
            // 處理讀寫事件
            protected void processKey(SelectionKey sk, NioSocketWrapper attachment) {
                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;
                    }
                }
            }
        }
    }
    
    
    public abstract class AbstractEndpoint<S> {
        public boolean processSocket(SocketWrapperBase<S> socketWrapper,
                SocketEvent event, boolean dispatch) {
    
            try {
                sc = createSocketProcessor(socketWrapper, event);
                Executor executor = getExecutor();
                // 註冊到Worker的線程池ThreadPoolExecutor。
                if (dispatch && executor != null) {
                    executor.execute(sc);
                } 
            } catch (RejectedExecutionException ree) {
            } 
    
            return true;
        }
    }
    

Worker處理具體請求

在這裏插入圖片描述

Worker.jpg

說明:

    1. 當新任務添加到 workQueue(ThreadPoolExecutor)後,workQueue.take()方法會返回一個 Runnable,通常是 SocketProcessor,然後 worker 線程調用 SocketProcessor的run() -> doRun()方法對 Socket 進行處理。
    1. createProcessor() 會創建一個Http11Processor, 它用來解析 Socket,將 Socket 中的內容封裝到Request中。注意這個Request是臨時使用的一個類,它的全類名是org.apache.coyote.Request。
    1. CoyoteAdapter的postParseRequest()方法封裝一下 Request,並處理一下映射關係(從 URL 映射到相應的 Host、Context、Wrapper)。
    1. CoyoteAdapter將 Rquest 提交給 Container(StandardEngine) 處理之前,並將 org.apache.coyote.Request封裝到 org.apache.catalina.connector.Request,傳遞給 Container處理的 Request 是 org.apache.catalina.connector.Request。
    1. connector.getService().getMapper().map(),用來在Mapper中查詢 URL 的映射關係。映射關係會保留到 org.apache.catalina.connector.Request 中,Container處理階段 request.getHost()是使用的就是這個階段查詢到的映射主機,以此類推 request.getContext()、request.getWrapper()都是。
    1. connector.getService().getContainer().getPipeline().getFirst().invoke()會將請求傳遞到 Container(StandardEngine)處理,至此進入了Engine->Host->Context->Wrapper的處理流程,當然了 Container處理也是在 Worker線程中執行的(也就是說Tomcat處理請求是通過ThreadPoolExecutor的線程池實現的),但是這是一個相對獨立的模塊,所以單獨分出來一節。

Container單個請求處理流程

在這裏插入圖片描述

StandardEngineValve

說明:

    1. 每個容器(Engine、Host、Context、Wrapper)的 StandardPipeline 上都會有多個已註冊的 Valve,我們只關注每個容器的 Basic Valve,其他 Valve 都是在 Basic Valve 前執行。
    1. request.getHost().getPipeline().getFirst().invoke() 先獲取對應的 StandardHost,並執行其 pipeline。
    1. request.getContext().getPipeline().getFirst().invoke() 先獲取對應的 StandardContext,並執行其 pipeline。
    1. request.getWrapper().getPipeline().getFirst().invoke() 先獲取對應的 StandardWrapper,並執行其 pipeline。
    1. StandardWrapper的Basic Valve是StandardWrapperValve,通過allocate() 用來加載並初始化 Servlet,值的一提的是 Servlet 並不都是單例的,當 Servlet 實現了 SingleThreadModel 接口後,StandardWrapper 會維護一組 Servlet 實例,這是享元模式。當然了 SingleThreadModel在 Servlet 2.4 以後就棄用了。
    1. createFilterChain() 方法會從 StandardContext 中獲取到所有的過濾器,然後將匹配 Request URL 的所有過濾器挑選出來添加到 filterChain 中。
      doFilter() 執行過濾鏈,當所有的過濾器都執行完畢後調用 Servlet 的 service() 方法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章