聲明:本文章中的說法僅是我的理解總結,不必定徹底正確,可是能夠有助於理解。php
當咱們在瀏覽器的地址欄輸入 www.linux178.com ,而後回車,回車這一瞬間到看到頁面到底發生了什麼呢?css
如下過程僅是我的理解:html
域名解析 --> 發起TCP的3次握手 --> 創建TCP鏈接後發起http請求 --> 服務器響應http請求,瀏覽器獲得html代碼 --> 瀏覽器解析html代碼,並請求html代碼中的資源(如js、css、圖片等) --> 瀏覽器對頁面進行渲染呈現給用戶node
關於HTTP協議能夠參考如下:linux
HTTP協議漫談 http://kb.cnblogs.com/page/140611/nginx
HTTP協議概覽 http://www.cnblogs.com/vamei/archive/2013/05/11/3069788.htmlweb
瞭解HTTP Headers的方方面面 http://kb.cnblogs.com/page/55442/正則表達式
如下就是上面過程的一一分析,咱們就以Chrome瀏覽器爲例:chrome
1.域名解析數據庫
首先Chrome瀏覽器會解析 www.linux178.com 這個域名(準確的叫法應該是主機名)對應的IP地址。怎麼解析到對應的IP地址?
① Chrome瀏覽器 會首先搜索瀏覽器自身的DNS緩存(緩存時間比較短,大概只有1分鐘,且只能容納1000條緩存),看自身的緩存中是否有www.linux178.com 對應的條目,並且沒有過時,若是有且沒有過時則解析到此結束。
注:咱們怎麼查看Chrome自身的緩存?可使用 chrome://net-internals/#dns 來進行查看
② 若是瀏覽器自身的緩存裏面沒有找到對應的條目,那麼Chrome會搜索操做系統自身的DNS緩存,若是找到且沒有過時則中止搜索解析到此結束.
注:怎麼查看操做系統自身的DNS緩存,以Windows系統爲例,能夠在命令行下使用 ipconfig /displaydns 來進行查看
③ 若是在Windows系統的DNS緩存也沒有找到,那麼嘗試讀取hosts文件(位於C:\Windows\System32\drivers\etc),看看這裏面有沒有該域名對應的IP地址,若是有則解析成功。
④ 若是在hosts文件中也沒有找到對應的條目,瀏覽器就會發起一個DNS的系統調用,就會向本地配置的首選DNS服務器(通常是電信運營商提供的,也可使用像Google提供的DNS服務器)發起域名解析請求(經過的是UDP協議向DNS的53端口發起請求,這個請求是遞歸的請求,也就是運營商的DNS服務器必須得提供給咱們該域名的IP地址),運營商的DNS服務器首先查找自身的緩存,找到對應的條目,且沒有過時,則解析成功。若是沒有找到對應的條目,則有運營商的DNS代咱們的瀏覽器發起迭代DNS解析請求,它首先是會找根域的DNS的IP地址(這個DNS服務器都內置13臺根域的DNS的IP地址),找打根域的DNS地址,就會向其發起請求(請問www.linux178.com這個域名的IP地址是多少啊?),根域發現這是一個頂級域com域的一個域名,因而就告訴運營商的DNS我不知道這個域名的IP地址,可是我知道com域的IP地址,你去找它去,因而運營商的DNS就獲得了com域的IP地址,又向com域的IP地址發起了請求(請問www.linux178.com這個域名的IP地址是多少?),com域這臺服務器告訴運營商的DNS我不知道www.linux178.com這個域名的IP地址,可是我知道linux178.com這個域的DNS地址,你去找它去,因而運營商的DNS又向linux178.com這個域名的DNS地址(這個通常就是由域名註冊商提供的,像萬網,新網等)發起請求(請問www.linux178.com這個域名的IP地址是多少?),這個時候linux178.com域的DNS服務器一查,誒,果然在我這裏,因而就把找到的結果發送給運營商的DNS服務器,這個時候運營商的DNS服務器就拿到了www.linux178.com這個域名對應的IP地址,並返回給Windows系統內核,內核又把結果返回給瀏覽器,終於瀏覽器拿到了www.linux178.com 對應的IP地址,該進行一步的動做了。
注:通常狀況下是不會進行如下步驟的
若是通過以上的4個步驟,尚未解析成功,那麼會進行以下步驟(如下是針對Windows操做系統):
⑤ 操做系統就會查找NetBIOS name Cache(NetBIOS名稱緩存,就存在客戶端電腦中的),那這個緩存有什麼東西呢?凡是最近一段時間內和我成功通信的計算機的計算機名和Ip地址,就都會存在這個緩存裏面。什麼狀況下該步能解析成功呢?就是該名稱正好是幾分鐘前和我成功通訊過,那麼這一步就能夠成功解析。
⑥ 若是第⑤步也沒有成功,那會查詢WINS 服務器(是NETBIOS名稱和IP地址對應的服務器)
⑦ 若是第⑥步也沒有查詢成功,那麼客戶端就要進行廣播查找
⑧ 若是第⑦步也沒有成功,那麼客戶端就讀取LMHOSTS文件(和HOSTS文件同一個目錄下,寫法也同樣)
若是第八步尚未解析成功,那麼就宣告此次解析失敗,那就沒法跟目標計算機進行通訊。只要這八步中有一步能夠解析成功,那就能夠成功和目標計算機進行通訊。
看下圖抓包截圖:
Linux虛擬機測試,使用命令 wget www.linux178.com 來請求,發現直接使用chrome瀏覽器請求時,干擾請求比較多,因此就使用wget命令來請求,不過使用wget命令只能把index.html請求回來,並不會對index.html中包含的靜態資源(js、css等文件)進行請求。
抓包分析:
① 號包,這個是那臺虛擬機在廣播,要獲取192.168.100.254(也就是網關)的MAC地址,由於局域網的通訊靠的是MAC地址,它爲何須要跟網關進行通訊是由於咱們的DNS服務器IP是外圍IP,要出去必需要依靠網關幫咱們出去才行。
② 號包,這個是網關收到了虛擬機的廣播以後,迴應給虛擬機的迴應,告訴虛擬機本身的MAC地址,因而客戶端找到了路由出口。
③ 號包,這個包是wget命令向系統配置的DNS服務器提出域名解析請求(準確的說應該是wget發起了一個DNS解析的系統調用),請求的域名www.linux178.com,指望獲得的是IP6的地址(AAAA表明的是IPv6地址)
④ 號包,這個DNS服務器給系統的響應,很顯然目前使用IPv6的仍是極少數,因此得不到AAAA記錄的
⑤ 號包,這個仍是請求解析IPv6地址,可是www.linux178.com.leo.com這個主機名是不存在的,因此獲得結果就是no such name
⑥ 號包,這個纔是請求的域名對應的IPv4地址(A記錄)
⑦ 號包,DNS服務器無論是從緩存裏面,仍是進行迭代查詢最終獲得了域名的IP地址,響應給了系統,系統再給了wget命令,wget因而獲得了www.linux178.com的IP地址,這裏也能夠看出客戶端和本地的DNS服務器是遞歸的查詢(也就是服務器必須給客戶端一個結果)這就能夠開始下一步了,進行TCP的三次握手。
2.發起TCP的3次握手
拿到域名對應的IP地址以後,User-Agent(通常是指瀏覽器)會以一個隨機端口(1024 < 端口 < 65535)向服務器的WEB程序(經常使用的有httpd,nginx等)80端口發起TCP的鏈接請求。這個鏈接請求(原始的http請求通過TCP/IP4層模型的層層封包)到達服務器端後(這中間經過各類路由設備,局域網內除外),進入到網卡,而後是進入到內核的TCP/IP協議棧(用於識別該鏈接請求,解封包,一層一層的剝開),還有可能要通過Netfilter防火牆(屬於內核的模塊)的過濾,最終到達WEB程序(本文就以Nginx爲例),最終創建了TCP/IP的鏈接。
以下圖:
1) Client首先發送一個鏈接試探,ACK=0 表示確認號無效,SYN = 1 表示這是一個鏈接請求或鏈接接受報文,同時表示這個數據報不能攜帶數據,seq = x 表示Client本身的初始序號(seq = 0 就表明這是第0號包),這時候Client進入syn_sent狀態,表示客戶端等待服務器的回覆
2) Server監聽到鏈接請求報文後,如贊成創建鏈接,則向Client發送確認。TCP報文首部中的SYN 和 ACK都置1 ,ack = x + 1表示指望收到對方下一個報文段的第一個數據字節序號是x+1,同時代表x爲止的全部數據都已正確收到(ack=1實際上是ack=0+1,也就是指望客戶端的第1個包),seq = y 表示Server 本身的初始序號(seq=0就表明這是服務器這邊發出的第0號包)。這時服務器進入syn_rcvd,表示服務器已經收到Client的鏈接請求,等待client的確認。
3) Client收到確認後還需再次發送確認,同時攜帶要發送給Server的數據。ACK 置1 表示確認號ack= y + 1 有效(表明指望收到服務器的第1個包),Client本身的序號seq= x + 1(表示這就是個人第1個包,相對於第0個包來講的),一旦收到Client的確認以後,這個TCP鏈接就進入Established狀態,就能夠發起http請求了。
看抓包截圖:
⑨ 號包 這個就是對應上面的步驟 1)
⑩ 號包 這個對應的上面的步驟 2)
號包 這個對應的上面的步驟 3)
TCP 爲何須要3次握手?
舉個例子:
假設一個老外在故宮裏面迷路了,看到了小明,因而就有下面的對話:
老外: Excuse me,Can you Speak English?
小明: yes 。
老外: OK,I want ...
在問路以前,老外先問小明是否會說英語,小明回答是的,這時老外才開始問路
2個計算機通訊是靠協議(目前流行的TCP/IP協議)來實現,若是2個計算機使用的協議不同,那是不能進行通訊的,因此這個3次握手就至關於試探一下對方是否遵循TCP/IP協議,協商完成後就能夠進行通訊了,固然這樣理解不是那麼準確。
爲何HTTP協議要基於TCP來實現?
目前在Internet中全部的傳輸都是經過TCP/IP進行的,HTTP協議做爲TCP/IP模型中應用層的協議也不例外,TCP是一個端到端的可靠的面向鏈接的協議,因此HTTP基於傳輸層TCP協議不用擔憂數據的傳輸的各類問題。
3.創建TCP鏈接後發起http請求
進過TCP3次握手以後,瀏覽器發起了http的請求(看第包),使用的http的方法 GET 方法,請求的URL是 / ,協議是HTTP/1.0
以上的報文是HTTP請求報文。
那麼HTTP請求報文和響應報文會是什麼格式呢?
起始行:如 GET / HTTP/1.0 (請求的方法 請求的URL 請求所使用的協議)
頭部信息:User-Agent Host等成對出現的值
主體
無論是請求報文仍是響應報文都會遵循以上的格式。
那麼起始行中的請求方法有哪些種呢?
GET: 完整請求一個資源 (經常使用)
HEAD: 僅請求響應首部
POST:提交表單 (經常使用)
PUT: (webdav) 上傳文件(可是瀏覽器不支持該方法)
DELETE:(webdav) 刪除
OPTIONS:返回請求的資源所支持的方法的方法
TRACE: 追求一個資源請求中間所通過的代理(該方法不能由瀏覽器發出)
那什麼是URL、URI、URN?
URI Uniform Resource Identifier 統一資源標識符
URL Uniform Resource Locator 統一資源定位符
格式以下: scheme://[username:password@]HOST:port/path/to/source
http://www.magedu.com/downloads/nginx-1.5.tar.gz
URN Uniform Resource Name 統一資源名稱
URL和URN 都屬於 URI
爲了方便就把URL和URI暫時都通指一個東西
請求的協議有哪些種?
有如下幾種:
http/0.9: stateless
http/1.0: MIME, keep-alive (保持鏈接), 緩存
http/1.1: 更多的請求方法,更精細的緩存控制,持久鏈接(persistent connection) 比較經常使用
下面是Chrome發起的http請求報文頭部信息
其中
Accept 就是告訴服務器端,我接受那些MIME類型
Accept-Encoding 這個看起來是接受那些壓縮方式的文件
Accept-Lanague 告訴服務器可以發送哪些語言
Connection 告訴服務器支持keep-alive特性
Cookie 每次請求時都會攜帶上Cookie以方便服務器端識別是不是同一個客戶端
Host 用來標識請求服務器上的那個虛擬主機,好比Nginx裏面能夠定義不少個虛擬主機
那這裏就是用來標識要訪問那個虛擬主機。
User-Agent 用戶代理,通常狀況是瀏覽器,也有其餘類型,如:wget curl 搜索引擎的蜘蛛等
條件請求首部:
If-Modified-Since 是瀏覽器向服務器端詢問某個資源文件若是自從什麼時間修改過,那麼從新發給我,這樣就保證服務器端資源
文件更新時,瀏覽器再次去請求,而不是使用緩存中的文件
安全請求首部:
Authorization: 客戶端提供給服務器的認證信息;
什麼是MIME?
MIME(Multipurpose Internet Mail Extesions 多用途互聯網郵件擴展)是一個互聯網標準,它擴展了電子郵件標準,使其可以支持非ASCII字符、二進制格式附件等多種格式的郵件消息,這個標準被定義在RFC 204五、RFC 204六、RFC 204七、RFC 204八、RFC 2049等RFC中。 由RFC 822轉變而來的RFC 2822,規定電子郵件標準並不容許在郵件消息中使用7位ASCII字符集之外的字符。正因如此,一些非英語字符消息和二進制文件,圖像,聲音等非文字消息都不能在電子郵件中傳輸。MIME規定了用於表示各類各樣的數據類型的符號化方法。 此外,在萬維網中使用的HTTP協議中也使用了MIME的框架,標準被擴展爲互聯網媒體類型。
MIME 遵循如下格式:major/minor 主類型/次類型 例如:
p_w_picpath/jpg p_w_picpath/gif text/html video/quicktime appliation/x-httpd-php
4.服務器端響應http請求,瀏覽器獲得html代碼
看下圖 第12號包是http請求包,第32包是http響應包
服務器端WEB程序接收到http請求之後,就開始處理該請求,處理以後就返回給瀏覽器html文件。
第32號包 是服務器返回給客戶端http響應包(200 ok 響應的MIME類型是text/html),表明這一次客戶端發起的http請求已成功響應。200 表明是的 響應成功的狀態碼,還有其餘的狀態碼以下:
1xx: 信息性狀態碼
100, 101
2xx: 成功狀態碼
200:OK
3xx: 重定向狀態碼
301: 永久重定向, Location響應首部的值仍爲當前URL,所以爲隱藏重定向;
302: 臨時重定向,顯式重定向, Location響應首部的值爲新的URL
304:Not Modified 未修改,好比本地緩存的資源文件和服務器上比較時,發現並無修改,服務器返回一個304狀態碼,
告訴瀏覽器,你不用請求該資源,直接使用本地的資源便可。
4xx: 客戶端錯誤狀態碼
404: Not Found 請求的URL資源並不存在
5xx: 服務器端錯誤狀態碼
500: Internal Server Error 服務器內部錯誤
502: Bad Gateway 前面代理服務器聯繫不到後端的服務器時出現
504:Gateway Timeout 這個是代理能聯繫到後端的服務器,可是後端的服務器在規定的時間內沒有給代理服務器響應
用Chrome瀏覽器看到的響應頭信息:
Connection 使用keep-alive特性
Content-Encoding 使用gzip方式對資源壓縮
Content-type MIME類型爲html類型,字符集是 UTF-8
Date 響應的日期
Server 使用的WEB服務器
Transfer-Encoding:chunked 分塊傳輸編碼 是http中的一種數據傳輸機制,容許HTTP由網頁服務器發送給客戶端應用(一般是網頁瀏覽器)的數據能夠分紅多個部分,分塊傳輸編碼只在HTTP協議1.1版本(HTTP/1.1)中提供
Vary 這個能夠參考(http://blog.csdn.net/tenfyguo/article/details/5939000)
X-Pingback 參考(http://blog.sina.com.cn/s/blog_bb80041c0101fmfz.html)
那到底服務器端接收到http請求後是怎麼樣生成html文件?
假設服務器端使用nginx+php(fastcgi)架構提供服務
① nginx讀取配置文件
咱們在瀏覽器的地址欄裏面輸入的是 http://www.linux178.com (http://能夠不用輸入,瀏覽器會自動幫咱們添加),其實完整的應該是http://www.linux178.com./ 後面還有個點(這個點表明就是根域,通常狀況下咱們不用輸入,也不顯示),後面的/也是不用添加,瀏覽器會自動幫咱們添加(且看第3部那個圖裏面的URL),那麼實際請求的URL是http://www.linux178.com/,那麼好了Nginx在收到 瀏覽器 GET / 請求時,會讀取http請求裏面的頭部信息,根據Host來匹配 本身的全部的虛擬主機的配置文件的server_name,看看有沒有匹配的,有匹配那麼就讀取該虛擬主機的配置,發現以下配置:
root /web/echo
經過這個就知道全部網頁文件的就在這個目錄下 這個目錄就是/ 當咱們http://www.linux178.com/時就是訪問這個目錄下面的文件,例如訪問http://www.linux178.com/index.html,那麼表明/web/echo下面有個文件叫index.html
index index.html index.htm index.php
經過這個就能得知網站的首頁文件是那個文件,也就是咱們在入http://www.linux178.com/ ,nginx就會自動幫咱們把index.html(假設首頁是index.php 固然是會嘗試的去找到該文件,若是沒有找到該文件就依次往下找,若是這3個文件都沒有找到,那麼就拋出一個404錯誤)加到後面,那麼添加以後的URL是/index.php,而後根據後面的配置進行處理
location ~ .*\.php(\/.*)*$ { root /web/echo; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; astcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; }
這一段配置指明凡是請求的URL中匹配(這裏是啓用了正則表達式進行匹配) *.php後綴的(後面跟的參數)都交給後端的fastcgi進程進行處理。
② 把php文件交給fastcgi進程去處理
因而nginx把/index.php這個URL交給了後端的fastcgi進程處理,等待fastcgi處理完成後(結合數據庫查詢出數據,填充模板生成html文件)返回給nginx一個index.html文檔,Nginx再把這個index.html返回給瀏覽器,因而乎瀏覽器就拿到了首頁的html代碼,同時nginx寫一條訪問日誌到日誌文件中去。
注1:nginx是怎麼找index.php文件的?
當nginx發現須要/web/echo/index.php文件時,就會向內核發起IO系統調用(由於要跟硬件打交道,這裏的硬件是指硬盤,一般須要靠內核來操做,而內核提供的這些功能是經過系統調用來實現的),告訴內核,我須要這個文件,內核從/開始找到web目錄,再在web目錄下找到echo目錄,最後在echo目錄下找到index.php文件,因而把這個index.php從硬盤上讀取到內核自身的內存空間,而後再把這個文件複製到nginx進程所在的內存空間,因而乎nginx就獲得了本身想要的文件了。
注2:尋找文件在文件系統層面是怎麼操做的?
好比nginx須要獲得/web/echo/index.php這個文件
每一個分區(像ext3 ext3等文件系統,block塊是文件存儲的最小單元 默認是4096字節)都是包含元數據區和數據區,每個文件在元數據區都有元數據條目(通常是128字節大小),每個條目都有一個編號,咱們稱之爲inode(index node 索引節點),這個inode裏面包含 文件類型、權限、鏈接次數、屬主和數組的ID、時間戳、這個文件佔據了那些磁盤塊也就是塊的編號(block,每一個文件能夠佔用多個block,而且block不必定是連續的,每一個block是有編號的),以下圖所示:
還有一個要點:目錄其實也普通是文件,也須要佔用磁盤塊,目錄不是一個容器。你看默認建立的目錄就是4096字節,也就說只須要佔用一個磁盤塊,但這是不肯定的。因此要找到目錄也是須要到元數據區裏面找到對應的條目,只有找到對應的inode就可找到目錄所佔用的磁盤塊。
那到底目錄裏面存放着什麼,難道不是文件或者其餘目錄嗎?
其實目錄存着這麼一張表(姑且這麼理解),裏面放着 目錄或者文件的名稱和對應的inode號(暫時稱之爲映射表),以下圖:
假設
/ 在數據區佔據 一、2號block ,/其實也是一個目錄 裏面有3個目錄 web 111
web 佔據 5號block 是目錄 裏面有2個目錄 echo data
echo 佔據 11號 block 是目錄 裏面有1個文件 index.php
index.php 佔據 15 16號 block 是文件
其在文件系統中分佈以下圖所示
那麼內核到底是怎麼找到index.php這個文件的呢?
內核拿到nginx的IO系統調用要獲取/web/echo/index.php這個文件請求以後
① 內核讀取元數據區 / 的inode,從inode裏面讀取/所對應的數據塊的編號,而後在數據區找到其對應的塊(1 2號塊),讀取1號塊上的映射表找到web這個名稱在元數據區對應的inode號
② 內核讀取web對應的inode(3號),從中得知web在數據區對應的塊是5號塊,因而到數據區找到5號塊,從中讀取映射表,知道echo對應的inode是5號,因而到元數據區找到5號inode
③ 內核讀取5號inode,獲得echo在數據區對應的是11號塊,因而到數據區讀取11號塊獲得映射表,獲得index.php對應的inode是9號
④ 內核到元數據區讀取9號inode,獲得index.php對應的是15和16號數據塊,因而就到數據區域找到15 16號塊,讀取其中的內容,獲得index.php的完整內容
5. 瀏覽器解析html代碼,並請求html代碼中的資源
瀏覽器拿到index.html文件後,就開始解析其中的html代碼,遇到js/css/p_w_picpath等靜態資源時,就向服務器端去請求下載(會使用多線程下載,每一個瀏覽器的線程數不同),這個時候就用上keep-alive特性了,創建一次HTTP鏈接,能夠請求多個資源,下載資源的順序就是按照代碼裏的順序,可是因爲每一個資源大小不同,而瀏覽器又多線程請求請求資源,因此從下圖看出,這裏顯示的順序並不必定是代碼裏面的順序。
瀏覽器在請求靜態資源時(在未過時的狀況下),向服務器端發起一個http請求(詢問自從上一次修改時間到如今有沒有對資源進行修改),若是服務器端返回304狀態碼(告訴瀏覽器服務器端沒有修改),那麼瀏覽器會直接讀取本地的該資源的緩存文件。
詳細的瀏覽器工做原理請看:http://kb.cnblogs.com/page/129756/
6.瀏覽器對頁面進行渲染呈現給用戶
最後,瀏覽器利用本身內部的工做機制,把請求到的靜態資源和html代碼進行渲染,渲染以後呈現給用戶。
自此一次完整的HTTP事務宣告完成.