從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(順便佩服找不到實習還花了幾天水博客的自己)

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/162677.html
標籤:Java
