SpringBoot應用啟動內置Tomcat的過程源碼分析
Connector啟動過程
Connector是Tomcat提供的類。
// 通過此 Connector 開始處理請求 @Override protected void startInternal() throws LifecycleException { // Validate settings before starting if (getPortWithOffset() < 0) { throw new LifecycleException(sm.getString( "coyoteConnector.invalidPort", Integer.valueOf(getPortWithOffset()))); } setState(LifecycleState.STARTING); try { // 核心動作 protocolHandler.start(); } catch (Exception e) { throw new LifecycleException( sm.getString("coyoteConnector.protocolHandlerStartFailed"), e); } }
springboot默認會在8080端口提供 HTTP 服務,所以這裡是一個處理HTTP協議請求的 Http11NioProtocol 實例,使用 NIO 方式處理 HTTP 協議。
Connector 對HTTP請求的接收和處理並非親自完成,而是委托該 Http11NioProtocol protocolHandler 完成
而 protocolHandler 又進一步將請求處理工作交給 NioEndpoint 完成。
AbstractProtocol
@Override public void start() throws Exception { if (getLog().isInfoEnabled()) { getLog().info(sm.getString("abstractProtocolHandler.start", getName())); logPortOffset(); } endpoint.start(); monitorFuture = getUtilityExecutor().scheduleWithFixedDelay( new Runnable() { @Override public void run() { if (!isPaused()) { startAsyncTimeout(); } } }, 0, 60, TimeUnit.SECONDS); }
調用鏈 :
- Connector.start()
- startInternal()
- Http11NioProtocol protocolHandler.start();
- Http11NioProtocol 的 start方法,由基類 AbstractProtocol 提供實現。它們都是tomcat提供的類。
- NioEndpoint endpoint.start()
start成員變量endpoint,一個 NioEndpoint 實例。Http11NioProtocol 類實例也並非最終處理請求,具體這些請求的處理都委托給瞭 NioEndpint endpoint 來完成
AbstractEndpoint
public final void start() throws Exception { if (bindState == BindState.UNBOUND) { bindWithCleanup(); bindState = BindState.BOUND_ON_START; } startInternal(); }
可見 tomcat 的三種模式,默認使用 NIO 模式。
@Override public void bind() throws Exception { initServerSocket(); setStopLatch(new CountDownLatch(1)); // Initialize SSL if needed initialiseSsl(); selectorPool.open(getName()); }
protected void initServerSocket() throws Exception { if (!getUseInheritedChannel()) { // 建立服務套接字 serverSock = ServerSocketChannel.open(); socketProperties.setProperties(serverSock.socket()); InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset()); // 綁定到指定端口 serverSock.socket().bind(addr,getAcceptCount()); } else { // Retrieve the channel provided by the OS Channel ic = System.inheritedChannel(); if (ic instanceof ServerSocketChannel) { serverSock = (ServerSocketChannel) ic; } if (serverSock == null) { throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited")); } } // 設置 serverSock 為阻塞模式 serverSock.configureBlocking(true); //mimic APR behavior }
serverSocket配置的是阻塞模式,明明默認使用NIO 模式,為何還要設置阻塞模式呢?
為什麼使用NIO,因為BIO的accept是阻塞方法,write和read也都是阻塞的。隻能當新連接到來時,去創建新線程去處理這個連接。如此,最大問題是不能同時處理大量連接,因為大量連接帶來的是創建很多線程,大量線程很容易讓操作系統崩潰,而且雖然並發度很高,但是很多線程都在空轉,很多時間都浪費在線程空跑和線程切換上,效率也很差。
於是誕生瞭NIO。
其實處理連接的操作不必放在後臺線程,因為後臺線程很可能會處理連接建立不及時,不如將其置於主線程,增加並發度(雖然優勢並不是特別明顯)。
重點關心的是連接建立後獲得的與客戶端交互的那個socket,它的操作必須是非阻塞的,這很顯然。因為在處理長連接時,我們關心的是在本次連接之內數據的讀寫。
NioEndpoint 正在使用阻塞模式的 ServerSocketChannel 以使其阻塞並等待連接傳入,並且隻有在accept後,才以非阻塞方式處理此傳入的socket channel (見setSocketOptions 方法)。
正如作者指出的那樣,使 ServerSocketChannel 成為非阻塞的將導致忙讀取,即一個線程將不斷輪詢有無傳入的連接,因為在非阻塞模式下 accept() 可能返回 null。
APR 代表 Apache Portable Runtime
Tomcat在接收到socket的時候做瞭如下操作:
參考
https://blog.csdn.net/andy_zhang2007/article/details/78641974
https://stackoverflow.com/questions/23168910/why-tomcats-non-blocking-connector-is-using-a-blocking-socket
到此這篇關於SpringBoot應用啟動內置Tomcat的過程分析的文章就介紹到這瞭,更多相關SpringBoot 內置Tomcat啟動內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 解決SpringBoot內嵌Tomcat並發容量的問題
- 詳解如何更改SpringBoot TomCat運行方式
- 使用SpringBoot配置https(SSL證書)
- springboot2如何禁用自帶tomcat的session功能
- springBoot server.port=-1的含義說明