從url到ip地址 dns解析 瀏覽器檢查功能變數名稱是否在緩存當中 如果緩存中沒有,就去調用 gethostbyname 庫函數進行查詢。 gethostbyname 函數在試圖進行DNS解析之前首先檢查功能變數名稱是否在本地 Hosts 里 沒有緩存,也沒有在 hosts 里找到,則將會向 DNS 伺服器發送一 ...
從url到ip地址
dns解析
- 瀏覽器檢查功能變數名稱是否在緩存當中
- 如果緩存中沒有,就去調用 gethostbyname 庫函數進行查詢。
- gethostbyname 函數在試圖進行DNS解析之前首先檢查功能變數名稱是否在本地 Hosts 里
- 沒有緩存,也沒有在
hosts
里找到,則將會向 DNS 伺服器發送一條 DNS 查詢請求(UDP,53埠) - 查詢本地 DNS 伺服器
- 如果 DNS 伺服器和我們的主機在同一個子網內,系統會按照下麵的 ARP 過程對 DNS 伺服器進行 ARP查詢
- 如果 DNS 伺服器和我們的主機在不同的子網,系統會按照下麵的 ARP 過程對預設網關進行查詢
ARP
- 首先查詢 ARP 緩存,如果緩存命中,我們返回結果:目標 IP = MAC,如果緩存沒有命中:
- 向本網段的所有主機發送ARP數據包
- 當本網路的所有主機收到該ARP數據包時,首先檢查數據包中的IP地址是否是自己的IP地址,如果不是,則忽略該數據包,如果是,則首先從數據包中取出源主機的IP和MAC地址寫入到ARP列表中,如果已經存在,則覆蓋,然後將自己的MAC地址寫入ARP響應包中,告訴源主機自己是它想要找的MAC地址。
- 源主機收到ARP響應包後。將目的主機的IP和MAC地址寫入ARP列表,並利用此信息發送數據。如果源主機一直沒有收到ARP響應數據包,表示ARP查詢失敗。
廣播發送ARP請求,單播發送ARP響應。
遞歸查詢迭代查詢
之後從DNS伺服器中獲得功能變數名稱對應的ip地址
從tcp數據報到比特流
當瀏覽器得到了目標伺服器的 IP 地址,以及 URL 中給出來埠號(http 協議預設埠號是 80, https 預設埠號是 443),它會調用系統庫函數socket ,請求一個 TCP流套接字,對應的參數是 AF_INET/AF_INET6 和 SOCK_STREAM 。
操作系統的任務:
- 這個請求首先被交給傳輸層,在傳輸層請求被封裝成 TCP segment。目標埠會被加入頭部,源埠會在系統內核的動態埠範圍內選取(Linux下是ip_local_port_range)
- TCP segment 被送往網路層,網路層會在其中再加入 IP 頭部(可能會切片),裡面包含了目標伺服器的IP地址以及本機的IP地址,把它封裝成IP packet。
集成網卡的任務(實現乙太網協議,負責組裝成幀、串列/並行轉換、緩存數據:由於網路上的數據率和電腦匯流排上的數據率並不相同,因此在網卡中必須裝有對數據進行緩存的存儲晶元):
- 這個 IP packet 接下來會進入鏈路層,鏈路層會在封包中加入 frame 頭部,也就是封成乙太網幀。裡面包含了本地內置網卡的MAC地址以及網關(本地路由器)的 MAC 地址。像前面說的一樣,如果內核不知道網關的 MAC 地址,它必須進行 ARP 廣播來查詢其地址。
- 集成網卡將乙太網幀編碼成適合線上路上進行傳輸的物理信號(比特流),並將比特流從網路介面中發出。
再通過數據機把數字信號轉換成模擬信號從網線發出
從路由器到路由器
如上圖,比特流在路由器中剖成ip數據報之後再提取出目標ip地址,並根據分組轉發協議進行查找:
分組轉發協議
- 從數據報的首部提取ip地址D,得出目的網路地址為N
- 若N就是直接相連的某個主機,直接交付
- 若路由表中有目的地址為D的特定主機路由,則把數據報傳送給路由表中的下一跳路由器
- 若路由器中有到達目的網路N的路由,則把數據報傳送給路由表中所指明的下一跳路由器
- 若路由表中有一個預設路由,則把數據報傳送給路由表中所指明的下一跳路由器
- 使用ICMP差錯報告報文報錯
通過分組轉發協議,得到相應的路由器或主機ip後,不是填入ip數據報,而是進行ARP將該ip地址轉化為物理地址。之後將物理地址包入乙太網幀,轉成比特流之後繼續發送。
在路由器之間移動的過程中可能會經過一些AS,順便一提AS的路由選擇協議有RIP(UDP)和OSPF(IP),AS間是BGP(TCP)
從網線到Socket
- 數據機把模擬信號轉換回數字信號
- 經過網卡拆解成IP packet存入網卡的緩衝區隊列
- 之後發出中斷,CPU保存運行現場後響應中斷,運行網卡中斷程式(這裡以epoll為例)(這裡已經變成TCP segment了,之後epoll流程處理的是TCP segment,具體怎麼變成TCP segment的我目前還不清楚,有知道的請告訴我一聲):
- 添加socket,並加入到eventpoll的等待隊列中(第一次發起才有添加socket的操作),將網卡的數據寫入到對應 socket 的接收緩衝區裡面;
- 修改 rdlist,並喚醒 eventpoll 等待隊列中的進程對socket進行處理
(epoll的具體流程可以看這裡epoll的實現原理)
這一部分將會在後面不停進行以傳輸TCP segment
從socket到http或https
這裡你的http伺服器(可以是nginx也可以是tomcat)就會開始接受socket連接(也就是tcp連接,socket是對tcp和udp的封裝),這裡如果是tomcat,源碼中會有 .accept 和 .register 的調用,在經過三次握手之後
如果你是採用https,則會創建ssl連接:
- 客戶端通過發送 Client Hello 報文開始 SSL通信。報文中包含客戶端支持的 SSL的指定版本、加密組件(Cipher Suite)列表(所使用的加密演算法及密鑰長度等)。
- 伺服器可進行 SSL通信時,會以 Server Hello 報文作為應答。和客戶端一樣,在報文中包含 SSL版本以及加密組件。伺服器的加密組件內容是從接收到的客戶端加密組件內篩選出來的。
- 之後伺服器發送 Certificate 報文。報文中包含公開密鑰證書。(這裡的證書是怎麼來的,如何驗證的我們一會再說,我們只要知道它包含伺服器的公鑰就夠了)
- 最後伺服器發送 Server Hello Done 報文通知客戶端,最初階段的 SSL握手協商部分結束。
- SSL第一次握手結束之後,客戶端以 Client Key Exchange 報文作為回應。報文中包含通信加密中使用的一種被稱為 Pre-master secret 的隨機密碼串。該隨機密碼串已用步驟 3 中的公開密鑰進行加密。
- 接著客戶端繼續發送 Change Cipher Spec 報文。該報文會提示伺服器,在此報文之後的通信會採用 Pre-master secret 密鑰加密。
- 客戶端發送 Finished 報文。該報文包含連接至今全部報文的整體校驗值。這次握手協商是否能夠成功,要以伺服器是否能夠正確解密該報文作為判定標準。
- 伺服器用自己的私鑰解開步驟5中的報文,得到隨機密碼串。伺服器同樣發送 Change Cipher Spec 報文。
- 伺服器同樣發送 Finished 報文。
連接完成之後繼續接收從socket拿到的數據,如果是https,後續的數據都要進行簡單的解密(加密方式是使用前面獲得的隨機密碼串作為密碼參數進行對稱加密)
之後就是http伺服器對傳來的數據進行封裝,封裝成http請求類等。
總結一下,這裡這些操作包括ssl連接主要是http伺服器對socket的調用(如Java寫的Tomcat調用的accept、register、select等,為NIO部分的知識),並封裝http或https對象,感興趣可以看一下我的這篇源碼解析:jdk下httpserver源碼解析,https部分詳情請見:Https原理
從http到servlet
之後就是容器的各種封裝了,下圖是Tomcat的架構圖,這裡會送到最右邊的Container封裝成servlet。這裡本來可以寫不少東西,不過我沒研究過,就不多說了。
前面的Connector部分的解析的話可以看這裡:Tomcat中對NIO的應用
從servlet到springMVC框架
之後就是SpringMVC對Servlet的封裝了,具體就不細說了,之後就是常見的SpringMVC的流程了:
至於返回到瀏覽器的流程就大同小異了
本文是我當前水平對這個問題所能做到的最詳細的解答了,後續如果有更加深入(例如我一直沒看的linux內核)的理解的話再更新吧。
因為我是後端的,就不提瀏覽器解析部分了,感興趣的可以看這裡:What-happens-when,裡面還有從鍵盤按鍵中斷開始聊起的,還蠻有意思的。
最後慣例附一圖:太棒了,我逐漸理解一切.jpg(順便佩服找不到實習還花了幾天水博客的自己)