㈠ Android socket源碼解析(三)socket的connect源碼解析
上一篇文章著重的聊了socket服務端的bind,listen,accpet的邏輯。本文來著重聊聊connect都做了什麼?
如果遇到什麼問題,可以來本文 https://www.jianshu.com/p/da6089fdcfe1 下討論
當服務端一切都准備好了。客戶端就會嘗試的通過 connect 系統調用,嘗試的和服務端建立遠程連接。
首先校驗當前socket中是否有正確的目標地址。然後獲取IP地址和埠調用 connectToAddress 。
在這個方法中,能看到有一個 NetHooks 跟蹤socket的調用,也能看到 BlockGuard 跟蹤了socket的connect調用。因此可以hook這兩個地方跟蹤socket,不過很少用就是了。
核心方法是 socketConnect 方法,這個方法就是調用 IoBridge.connect 方法。同理也會調用到jni中。
能看到也是調用了 connect 系統調用。
文件:/ net / ipv4 / af_inet.c
在這個方法中做的事情如下:
注意 sk_prot 所指向的方法是, tcp_prot 中 connect 所指向的方法,也就是指 tcp_v4_connect .
文件:/ net / ipv4 / tcp_ipv4.c
本質上核心任務有三件:
想要能夠理解下文內容,先要明白什麼是路由表。
路由表分為兩大類:
每個路由器都有一個路由表(RIB)和轉發表 (fib表),路由表用於決策路由,轉發表決策轉發分組。下文會接觸到這兩種表。
這兩個表有什麼區別呢?
網上雖然給了如下的定義:
但實際上在Linux 3.8.1中並沒有明確的區分。整個路由相關的邏輯都是使用了fib轉發表承擔的。
先來看看幾個和FIB轉發表相關的核心結構體:
熟悉Linux命令朋友一定就能認出這裡面大部分的欄位都可以通過route命令查找到。
命令執行結果如下:
在這route命令結果的欄位實際上都對應上了結構體中的欄位含義:
知道路由表的的內容後。再來FIB轉發表的內容。實際上從下面的源碼其實可以得知,路由表的獲取,實際上是先從fib轉發表的路由字典樹獲取到後在同感加工獲得路由表對象。
轉發表的內容就更加簡單
還記得在之前總結的ip地址的結構嗎?
需要進行一次tcp的通信,意味著需要把ip報文准備好。因此需要決定源ip地址和目標IP地址。目標ip地址在之前通過netd查詢到了,此時需要得到本地發送的源ip地址。
然而在實際情況下,往往是面對如下這么情況:公網一個對外的ip地址,而內網會被映射成多個不同內網的ip地址。而這個過程就是通過DDNS動態的在內存中進行更新。
因此 ip_route_connect 實際上就是選擇一個緩存好的,通過DDNS設置好的內網ip地址並找到作為結果返回,將會在之後發送包的時候填入這些存在結果信息。而查詢內網ip地址的過程,可以成為RTNetLink。
在Linux中有一個常用的命令 ifconfig 也可以實現類似增加一個內網ip地址的功能:
比如說為網卡eth0增加一個IPV6的地址。而這個過程實際上就是調用了devinet內核模塊設定好的添加新ip地址方式,並在回調中把該ip地址刷新到內存中。
注意 devinet 和 RTNetLink 嚴格來說不是一個存在同一個模塊。雖然都是使用 rtnl_register 注冊方法到rtnl模塊中:
文件:/ net / ipv4 / devinet.c
文件:/ net / ipv4 / route.c
實際上整個route模塊,是跟著ipv4 內核模塊一起初始化好的。能看到其中就根據不同的rtnl操作符號注冊了對應不同的方法。
整個DDNS的工作流程大體如下:
當然,在tcp三次握手執行之前,需要得到當前的源地址,那麼就需要通過rtnl進行查詢內存中分配的ip。
文件:/ include / net / route.h
這個方法核心就是 __ip_route_output_key .當目的地址或者源地址有其一為空,則會調用 __ip_route_output_key 填充ip地址。目的地址為空說明可能是在回環鏈路中通信,如果源地址為空,那個說明可能往目的地址通信需要填充本地被DDNS分配好的內網地址。
在這個方法中核心還是調用了 flowi4_init_output 進行flowi4結構體的初始化。
文件:/ include / net / flow.h
能看到這個過程把數據中的源地址,目的地址,源地址埠和目的地址埠,協議類型等數據給記錄下來,之後內網ip地址的查詢與更新就會頻繁的和這個結構體進行交互。
能看到實際上 flowi4 是一個用於承載數據的臨時結構體,包含了本次路由操作需要的數據。
執行的事務如下:
想要弄清楚ip路由表的核心邏輯,必須明白路由表的幾個核心的數據結構。當然網上搜索到的和本文很可能大為不同。本文是基於LInux 內核3.1.8.之後的設計幾乎都沿用這一套。
而內核將路由表進行大規模的重新設計,很大一部分的原因是網路環境日益龐大且復雜。需要全新的方式進行優化管理系統中的路由表。
下面是fib_table 路由表所涉及的數據結構:
依次從最外層的結構體介紹:
能看到路由表的存儲實際上通過字典樹的數據結構壓縮實現的。但是和常見的字典樹有點區別,這種特殊的字典樹稱為LC-trie 快速路由查找演算法。
這一篇文章對於快速路由查找演算法的理解寫的很不錯: https://blog.csdn.net/dog250/article/details/6596046
首先理解字典樹:字典樹簡單的來說,就是把一串數據化為二進制格式,根據左0,右1的方式構成的。
如圖下所示:
這個過程用圖來展示,就是沿著字典樹路徑不斷向下讀,比如依次讀取abd節點就能得到00這個數字。依次讀取abeh就能得到010這個數字。
說到底這種方式只是存儲數據的一種方式。而使用數的好處就能很輕易的找到公共前綴,在字典樹中找到公共最大子樹,也就找到了公共前綴。
而LC-trie 則是在這之上做了壓縮優化處理,想要理解這個演算法,必須要明白在 tnode 中存在兩個十分核心的數據:
這負責什麼事情呢?下面就簡單說說整個lc-trie的演算法就能明白了。
當然先來看看方法 __ip_dev_find 是如何查找
文件:/ net / ipv4 / fib_trie.c
整個方法就是通過 tkey_extract_bits 生成tnode中對應的葉子節點所在index,從而通過 tnode_get_child_rcu 拿到tnode節點中index所對應的數組中獲取葉下一級別的tnode或者葉子結點。
其中查找index最為核心方法如上,這個過程,先通過key左移動pos個位,再向右邊移動(32 - bits)演算法找到對應index。
在這里能對路由壓縮演算法有一定的理解即可,本文重點不在這里。當從路由樹中找到了結果就返回 fib_result 結構體。
查詢的結果最為核心的就是 fib_table 路由表,存儲了真正的路由轉發信息
文件:/ net / ipv4 / route.c
這個方法做的事情很簡單,本質上就是想要找到這個路由的下一跳是哪裡?
在這裡面有一個核心的結構體名為 fib_nh_exception 。這個是指fib表中去往目的地址情況下最理想的下一跳的地址。
而這個結構體在上一個方法通過 find_exception 獲得.遍歷從 fib_result 獲取到 fib_nh 結構體中的 nh_exceptions 鏈表。從這鏈表中找到一模一樣的目的地址並返回得到的。
文件:/ net / ipv4 / tcp_output.c
㈡ 什麼是域名的源碼解析
域名(Domain Name),是由一串用點分隔的名字組成的Internet上某一台計算機或計算機組的名稱,用於在數據傳輸時標識計算機的電子方位(有時也指地理位置,地理上的域名,指代有行政自主權的一個地方區域)。域名是一個IP地址上有「面具」 。一個域名的目的是便於記憶和溝通的一組伺服器的地址(網站,電子郵件,FTP等)。域名作為力所能及難忘的互聯網參與者的名稱,世界上第一個注冊的域名是在1985年1月注冊的。
簡單的說,IP地址不方便記憶,有一定意義的數字和字母更方便記憶。
㈢ 怎麼在網頁的源代碼查找自己要的文字
可以用瀏覽器自帶的搜索功能在網頁源代碼中查找自己要的文字。
1、右擊需要查看源代碼的頁面,在展開的菜單中點擊「查看網嫌念頁源皮者鍵代碼」按鈕:
㈣ 怎麼找到spring註解解析器的源碼
下面用的是4.2.5的源碼。
從這個文件開始看:META-INF/spring.handlers
文件里的內容是http://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
MvcNamespaceHandler源碼:
㈤ 工具如何查找源代碼
為了顯示帶注釋的源代碼和帶注釋的反匯編代碼,性能分析器和er_print實用程序對於運行實驗的程序所使用的源代碼文件和裝入對象文件必須具有訪問許可權。
首先在實驗的archives目錄中查找裝入對象文件。如果在該目錄中未找到,則將使用與下面所述的源文件和對象文件相同的演算法查找這些文件。
在大多數實驗中,源文件和對象文件按照完整路徑的格式記錄。Java 源文件還具有一個軟體包名稱,其中列出文件的目錄結構。如果在記錄實驗的同一系統上查看實驗,則可以使用完整路徑找到源文件和裝入對象。當實驗移到其他計算機或者在其他計算機上查看實驗時,這些完整路徑可能無法訪問。
可以使用兩個補充方法來查找源文件和對象文件:路徑映射和搜索路徑。如果在archives子目錄中沒有找到裝入對象文件,可以使用相同的方法來查找這些文件。
可以設置路徑映射和搜索路徑,幫助工具查找實驗中引用的文件。在分析器中,使用 "Set Data Preferences"(設置數據首選項)對話框的 "Pathmaps"(路徑映射)標簽設置路徑映射,並使用 "Search Path"(搜索路徑)標簽設置搜索路徑,如
設置數據表示選項
中所述。對於er_print實用程序,使用pathmap和setpath指令,如控制源文件搜索的命令中所述。
/a/b/c/sourcefile
,並且pathmap指令指定將/a/映射到/x/y/
,則可以在/x/y/b/c/sourcefile
中找到文件。如果pathmap指令將/a/b/c/映射到/x/
,則可以在/x/sourcefile
中找到文件。
如果通過路徑映射找不到文件,則將使用搜索路徑。搜索路徑提供了一個要為具有指定基名的文件搜索的目錄列表,在上面的示例中,指定的基名為sourcefile
。可以使用setpath命令設置搜索路徑,並使用addpath命令向搜索路徑附加一個目錄。對於 Java 文件,將嘗試軟體包名稱,然後再嘗試基名。
使用搜索路徑中的每個目錄來構造嘗試搜索的完整路徑。對於 Java 源文件,將構造兩個完整路徑,一個用於基名,另一個用於軟體包名稱。工具會將路徑映射應用於每個完整路徑,如果沒有映射路徑指向文件,則將嘗試下一個搜索路徑目錄。
如果在搜索路徑中沒有找到文件,並且沒有映射前綴與原始完整路徑匹配,則將嘗試原始完整路徑。如果有任何路徑映射前綴與原始完整路徑匹配,但沒有找到文件,則不會嘗試原始完整路徑。
請注意,預設搜索路徑包含當前目錄和實驗目錄,因此一個使源文件可訪問的方法是將源文件復制到這些位置之一,或者在這些位置中放置指向源文件當前位置的符號鏈接。
㈥ Feign源碼解析二
本文會基於Feign源碼,看看Feign到底是怎麼實現遠程調用
上文中,我們的 user-service 服務需要調用遠程的 order-service 服務完成一定的業務邏輯,而基本實現是order-service提供一個spi的jar包給user-service依賴,並且在user-service的啟動類上添加了一個註解
這個註解就是@EnableFeignClients,接下來我們就從這個註解入手,一步一步解開Feign的神秘面紗
該註解類上的注釋大概的意思就是:
掃描那些被聲明為 Feign Clients (只要有 org.springframework.cloud.openfeign.FeignClient 註解修飾的介面都是Feign Clients介面)的介面
下面我們繼續追蹤源碼,看看到底什麼地方用到了這個註解
利用IDEA的查找調用鏈快捷鍵,可以發現在.class類型的文件中只有一個文件用到了這個註解
OK,下面主要就是看這個類做了什麼
通過UML圖我們發現該類分別實現了 ImportBeanDefinitionRegistrar , ResourceLoaderAware 以及 EnvironmentAware 介面
這三個介面均是spring-framework框架的spring-context模塊下的介面,都是和spring上下文相關,具體作用下文會分析
總結下來就是利用這兩個重要屬性,一個獲取應用配置屬性,一個可以載入classpath下的文件,那麼FeignClientsRegistrar持有這兩個東西之後要做什麼呢?
上面將bean配置類包裝成 FeignClientSpecification ,注入到容器。該對象非常重要,包含FeignClient需要的 重試策略 , 超時策略 , 日誌 等配置,如果某個FeignClient服務沒有設置獨立的配置類,則讀取默認的配置,可以將這里注冊的bean理解為整個應用中所有feign的默認配置
由於 FeignClientsRegistrar 實現了 ImportBeanDefinitionRegistrar 介面,這里簡單提下這個介面的作用
我們知道在spring框架中,我們如果想注冊一個bean的話主要由兩種方式:自動注冊/手動注冊
知道了 ImportBeanDefinitionRegistrar 介面的作用,下面就來看下 FeignClientsRegistrar 類是何時被載入實例化的
通過IDEA工具搜索引用鏈,發現該類是在註解@EnableFeignClients上被import進來的,文章開始的圖片中有
這里提下@Import註解的作用
該註解僅有一個屬性value,使用該註解表明導入一個或者多個@Configuration類,其作用和.xml文件中的<import>等效,其允許導入@Configuration類,ImportSelector介面/ImportBeanDefinitionRegistrar介面的實現,也同樣可以導入一個普通的組件類
注意,如果是XML或非@Configuration的bean定義資源需要被導入的話,需要使用@ImportResource註解代替
這里我們導入的FeignClientsRegistrar類正是一個ImportBeanDefinitionRegistrar介面的實現
FeignClientsRegistrar重寫了該介面的 registerBeanDefinitions 方法,該方法有兩個參數註解元數據 metadata 和bean定義注冊表 registry
該方法會由spring負責調用,繼而注冊所有標注為@FeignClient註解的bean定義
下面看registerBeanDefinitions方法中的第二個方法,在該方法中完成了所有@FeignClient註解介面的掃描工作,以及注冊到spring中,注意這里注冊bean的類型為 FeignClientFactoryBean ,下面細說
總結一下該方法,就是掃描@EnableFeignClients註解上指定的basePackage或clients值,獲取所有@FeignClient註解標識的介面,然後將這些介面一一調用以下 兩個重要方法 完成 注冊configuration配置bean 和注冊 FeignClient bean
斷點位置相當重要
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
這里是利用了spring的代理工廠來生成代理類,即這里將所有的 feignClient的描述信息 BeanDefinition 設定為 FeignClientFactoryBean 類型,該類繼承自FactoryBean,因此這是一個代理類,FactoryBean是一個工廠bean,用作創建代理bean,所以得出結論,feign將所有的 feignClient bean定義的類型包裝成 FeignClientFactoryBean
最終其實就是存入了BeanFactory的beanDefinitionMap中
那麼代理類什麼時候會觸發生成呢? 在spring 刷新容器時 ,會根據beanDefinition去實例化bean,如果beanDefinition的beanClass類型為代理bean,則會調用其 T getObject() throws Exception; 方法生成代理bean,而我們實際利用注入進來的FeignClient介面就是這些一個個代理類
這里有一個需要注意的點,也是開發中會遇到的一個 啟動報錯點
如果我們同時定義了兩個不同名稱的介面 (同一個包下/或依賴方指定全部掃描我們提供的 @FeignClient ),且這兩個 @FeignClient 介面註解的 value/name/serviceId 值一樣的話,依賴方拿到我們的提供的spi依賴,啟動類上 @EnableFeignClients 註解掃描能同時掃描到這兩個介面,就會 啟動報錯
原因就是Feign會為每個@FeignClient註解標識的介面都注冊一個以serviceId/name/value為key,FeignClientSpecification類型的bean定義為value去spring注冊bean定義,又默認不允許覆蓋bean定義,所以報錯
官方提示給出的解決方法要麼改個@FeignClient註解的serviceId,name,value屬性值,要麼就開啟spring允許bean定義覆寫
至此我們知道利用在springboot的啟動類上添加的@EnableFeignClients註解,該註解中import進來了一個手動注冊bean的 FeignClientsRegistrar注冊器 ,該注冊器會由spring載入其 registerBeanDefinitions方法 ,由此來掃描所有@EnableFeignClients註解定義的basePackages包路徑下的所有標注為@FeignClient註解的介面,並將其注冊到spring的bean定義Map中,並實例化bean
下一篇博文中,我會分析為什麼我們在調用(@Resource)這些由@FeignClient註解的bean的方法時會發起 遠程調用