1. 程式人生 > >記一次springboot2.x 內建tomcat在apr模式下無法訪問的問題排查

記一次springboot2.x 內建tomcat在apr模式下無法訪問的問題排查

關於tomcat的apr、bio、nio模式

首先將springboot應用程式跑起來,用postman隨便訪問一個介面可以看到打印出來的記錄裡顯示出是基於nio模式的
在這裡插入圖片描述

接下來我們參考上面的這些文章,將springboot程式設定為Apr模式執行。關鍵的幾個步驟見下面的
注意啟用apr模式之前,相關的apr要安裝。具體安裝方法很簡單,見這篇文章 [Tomcat中如何配置使用APR],為了方便懶人,直接截圖過來了,(https://blog.csdn.net/elinespace/article/details/53017967)。感謝原作者。
在這裡插入圖片描述
附圖片中的apr下載路徑apr下載路徑
設定tomcat模式為 apr 模式。這一步都是so easy
在這裡插入圖片描述

好了,一切都已經搞定了。應用程式跑起來就是這個樣子的了
在這裡插入圖片描述
已經根據Apr模式啟動了。一切看起來都很OK。走到這一步,基本上都沒啥問題了。

接下來讓人鬱悶的事情來了

問題現象

用POSTMAN 訪問127.0.0.1 沒有任何響應!見下圖

在這裡插入圖片描述

因為只改動了這個protocol設定。那麼登出掉下圖紅色這一行的設定(這行程式碼是設定protocol模式為 spring.server.protocol=org.apache.coyote.http11.Http11AprProtocol 的)。

在這裡插入圖片描述
登出掉紅色這部分的程式碼,發現應用以nio模式啟動,正常接收和響應資料。
說明問題的確出在apr模式上。但是為什麼apr模式會有問題,nio模式沒有問題呢?

問題描述

1.nio模式沒有問題,可以正常響應和輸出資料。只要切換到apr模式就會不響應。內心是這樣的
在這裡插入圖片描述

分析思路
1.請求資料發過去了沒有,tomcat有沒有接收到
2.埠有沒有繫結(做過socket程式設計的小夥伴們就有了解了,socket需要create、bind、accept、listen之類的操作)有沒有正常監聽?

因為我這個springboot應用程式指定的埠是8102。用cmd netstat命令查看了一下繫結的埠
這個是在apr模式下:
在這裡插入圖片描述
可以看到8102埠有在正常的listening狀態。但是ip地址有些詭異。
再切換回nio模式
在這裡插入圖片描述

看出區別了沒有?
在NIO模式下,會有兩個地址繫結,一個是IP4,一個是IP6。而在APR模式下只有一個IP6的地址繫結。那麼問題就在於tomcat的地址繫結上。為什麼NIO模式會有IP4和IP6,而APR模式只有IP6呢?導致用本機地址127.0.0.1無法訪問。

於是窮追不捨的我就跟到了springboot內嵌的tomcat原始碼裡面去了。手動滑稽。

nio模式 bind相關的程式碼

這個是NIO模式的bind函式,在這個路徑
C:\Users\Administrator.gradle\caches\modules-2\files-2.1\org.apache.tomcat.embed\tomcat-embed-core\8.5.31\5ade5e787bcaabb9d088d8e4b22a8edffa43a783\tomcat-embed-core-8.5.31-sources.jar!\org\apache\tomcat\util\net\NioEndpoint.java
繫結的 0.0.0.0 地址
在這裡插入圖片描述

它的地址是怎麼來的呢??

仔細看下它獲取ip地址的業務邏輯是一個三目運算

InetSocketAddress addr = 
(getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));

如果沒有指定地址,呢麼就

new InetSocketAddress(getPort()))

跟進去看
C:\Program Files\Java\jdk1.8.0_151\src.zip!\java\net\InetSocketAddress.java

在這裡插入圖片描述

主要呼叫了一個anyLocalAddress 字面意思理解就是任意的實體地址

在這裡插入圖片描述
anyLocalQAddress地址就是 0.0.0.0
在這裡插入圖片描述
所以,綜上所述,在NIO模式下,不需要指定地址,它會自動預設一個 0.0.0.0的地址

apr模式 bind相關的程式碼

這個是APR模式的bind函式,在這個路徑
C:\Users\Administrator.gradle\caches\modules-2\files-2.1\org.apache.tomcat.embed\tomcat-embed-core\8.5.31\5ade5e787bcaabb9d088d8e4b22a8edffa43a783\tomcat-embed-core-8.5.31-sources.jar!\org\apache\tomcat\util\net\AprEndpoint.java
在這裡插入圖片描述
我們可以看到,它呼叫 getAddress() ,因為沒有手動設定address地址,所以返回值是空。接下來的addressStr也是空的。然後在後面的繫結中也是基於這個引數去繫結。自然是沒有地址了。

在這裡插入圖片描述

斷點走過了listen之後,cmd命令去看,就已經綁定了埠。但是沒有監聽地址
在這裡插入圖片描述

結果分析

1.在NIO模式下沒有指定地址的情況,會呼叫
new InetSocketAddress(getPort())) 去獲取一個 anyLocalAddress (這個引數啟動時候會被初始化為 0.0.0.0),所以NIO模式下不用指定地址即可繫結。
2.在apr模式下沒有指定地址的情況,會繼續往前執行,看這個
Address.info函式,傳入的addressStr引數是空的。
在這裡插入圖片描述
Address.info是一個native函式,也就是jni呼叫。對接到系統層了,無法原始碼除錯。但是看函式的註釋,也很清楚,如果沒有地址沒有指定,它會給你返回一個 0.0.0.0 或 :: 也就是我們看到的繫結的地址是 ::

在這裡插入圖片描述

真相大白了。沒有指定地址的情況下,的確是會bind到 :: 這個地址。

解決方案

在apr配置中手工指定地址。
在這裡插入圖片描述
在這裡插入圖片描述

再次啟動,tomcat繫結地址
在這裡插入圖片描述

apr模式啟動且介面產生響應。

總結

解決問題後,我沒有非常深入去探究為什麼沒有指定address的情況下,它繫結的地址會返回 :: (是不是哪個引數沒有設定?跟我本機安裝了vmware產生多個虛擬網絡卡有沒有關係?跟開啟ipv6有沒有關係?跟內建tomcat預設設定有沒有關係等)
大概的追蹤了一下tomcat原始碼,學到了很多東西,也找到了關鍵問題,記錄一下,希望對遇到同樣問題的夥伴們有所幫助