一次完整的HTTP請求過程


當我們在瀏覽器的地址欄鍵入www.linux178.com,然后回車,從回車這一刻到看到頁面到底發生了什么呢?

  • 域名解析
  • 發起TCP3次握手
  • 建立TCP連接后發起http請求
  • 服務器響應請求,返回結果
  • 瀏覽器得到html標簽代碼
  • 瀏覽器解析html代碼中的資源,例如js,css,img等
  • 瀏覽器對頁面進行渲染並呈現給用戶

一下我們已chrome瀏覽器為例,對上面的過程一一分析。

一. 域名解析

首先chrome瀏覽器會解析www.linux178.com這個域名(准確的說法是主機名)對應的IP地址。怎么解析得到對應的IP地址呢?

(1)瀏覽器首先搜索自身的DNS緩存(緩存時間比較短,大概只有1分鍾,且只能容納1000條數據),看自身的緩存中是否有www.linux178.com對應的條目,而且沒有過期,如果有且沒有過期,解析就到此結束。我們可以通過在chrome瀏覽器地址欄中輸入:chrome://net-internals/#dns來查看。

(2)如果瀏覽器自身的緩存中沒有找到對應的條目,那么chrome瀏覽器會搜索操作系統自身的DNS緩存,如果找到且沒有過期則停止搜索,解析到此結束。如何查看操作系統自身的DNS緩存,以Windows系統為例,可以在cmd命令行中輸入ipconfig/displaydns來查看。

(3)如果在操作系統的DNS緩存中也沒有找到,那么嘗試讀取hosts文件(位於c:\Windows\System32\direvers\etc),看看這里有沒有該域名對應的IP地址,如果有則解析成功,解析到此結束。

(4)如果在hosts文件中也沒有找到對應的條目,瀏覽器會發起一個DNS(Domain Name System:域名服務協議)系統調用,就會向本地配置的首選DNS服務器(一般是電信運營商提供的,也可以使用像Google提供的DSN服務器)發起域名解析請求(通過UDP協議向DNS的53號端口發起請求,這個請求是遞歸請求,也就是這個運營商的DSN服務器必須給我們提供該域名的IP地址)。請求過程如下:

  • 運營商的DNS服務器首先查找自身的緩存,如果能找到對應的條目,且沒有過期則解析成功。如果沒有找到對應的條目,則運營商的DNS代我們的瀏覽器發起迭代DNS解析請求
  • 運營商DNS首先會查找根域DNS的IP地址(這個DNS服務器內置13台根DNS域服務器的IP地址),找到根域的DNS地址,就會向其發起請求(問一下www.linux178.com這個域名的ip地址是多少啊?)。根域發現這是一個com域(頂級域)的域名,於是返回com域的IP地址,然后運營商的DNS就得到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系統內核,內核就把這個結果返回給瀏覽器,最終瀏覽器得到這個IP地址,進行下一步動作。

注:一般情況下是不會進行一下步驟的

如果經過以上4個步驟,還是沒有解析成功,那么會進行如下步驟(針對Windows操作系統):

(5)操作系統就會查找NetBIOS name Cache(網上基本輸入輸出系統,NetBIOS名稱緩存,就在客戶端電腦中),這個緩存是什么東西呢?它是最近一段時間內我和我們的電腦成功通訊的計算機名和IP地址,都會存在這個緩存里面。什么情況下該不住能解析成功呢?就是這個計算機名稱正好是幾分鍾前我們成功通訊過的,那么這一步就可以解析成功。

(6)如果第5步也沒有成功,那會查詢WINS服務器(Windows網際名字服務,NETBIOS名稱和IP地址對應的服務器)。

(7)如果第6步也沒有查詢成功,那么客戶端就要進行廣播查找。

(8)如果第7步也沒有查詢成功,那么客戶端就讀取LMHOSTS文件(和hosts文件在同一個目錄下,寫法也一樣)。

如果第8步還是沒有解析成功,那么就宣告這次解析失敗,無法和目標計算機進行通信。只要這8步中任意一個解析成功,就可以成功和目標計算進進行通信。

看下圖的抓包截圖:

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命令,wegt於是得到了www.linux178.com的IP地址,這里可以看出客戶端和本地DNS服務器是遞歸的查詢(也就是服務器必須給客戶端一個結果)就可以開始下一步了,進行TCP的三次握手。

二. 發起TCP的3次握手

拿到域名對應的IP地之后,User-Agent(一般是指瀏覽器)會以一個隨機端口(1024<端口<65535)向服務器的web程序(常用的有httpd,nginx等)80端口發起TCP的連接請求。這個連接請求(原始的http請求經過TCP/IP層模型層層包裝)到達服務器后(這中間通過各種路由設備,局域網除外),進入到網卡,然后進入到內核的TCP/IP協議棧(用於識別這個連接請求,解封包,一層層剝開),還有可能要經過Netfilter防火牆(屬於內核的模塊)的過濾,最終到達web程序(本文就以Nginx為例)最終建立TCP/IP連接,如下圖:

 

  1.  客服端首先發送一個連接試探,ACK=0表示確認號無效,SYN=1表示這是一個連接請求或者接受報文,同時表示這個數據報不能攜帶數據,seq=x表示客戶端自己的初始序號(seq=0就代表這是第0號包),這時候客戶端進入syn_sent狀態,表示客戶端等待服務器的回復。
  2. 服務器監聽到連接請求報文后,如果同意建立連接,則向Client發送確認。TCP報文首部中的SYN和ACK都置1,ack=x+1表示期望收到對方下一個報文段的第一個數據字節序號是x+1,同時表明到x為止的所有數據都已經正確收到(ack=1其實是ack=0+1,也就是期望客戶端的第一個包),seq=y表示服務器自己的初始序號(seq=0就代表這是服務器這邊發出的第0號包),這時服務器進入syn_rcvd,表示服務器已經接收到客戶端的連接請求,等待Client的確認。
  3. Client收到確認后還需要再次發送確認,同時攜帶要發送給Server的數據,ACK置1表示確認號ack=y+1有效(代表期望收到服務器的第1個包),客戶端自己的需要seq=x+1(表示這就是我的第一個包,相對於第0個包來說的),一旦收到客戶端的確認之后,這個TCP連接就進入Established裝填,就可以發送http請求了。

     

    ⑨號包,這個對應上面的步驟1)
    ⑩號包,這個對應上面的步驟2)
    ⑪號包,這個對應上面的步驟3)

TCP為什么要進行三次握手?舉一個列子,假如一個老外在故宮迷了路,看到小明,於是就有了下面的對話:

老外:Excuse me,Can you speck English?
小明:Yes
老外:OK, I want to...

在問路之前老外先問小明是否會說英語,小明回答是的,這時老外才開始問路。

兩台計算機之間的通信是靠協議(目前流行TCP/IP協議)來實現的,如果兩台計算機使用的通信協議不一樣,那是不能進行通信的,所以這個3次握手就相當於試探一下對方是否遵循TCP/IP協議,協議完成后就可以進行通信了,當然這個說法不是那么准確。

為什么HTTP協議要基於TCP來實現?

目前在Internet中所有的傳輸都是通過TCP/IP進行的,HTTP協議作為TCP/IP模型中應用層的協議也不例外,TCP是一個端到端的可靠的面向連接的協議所以HTTP基於傳輸層TCP協議不用擔心數據傳輸中的各種問題

三.建立TCP連接后發起http請求

經過TCP3次握手之后,瀏覽器發起了http請求,使用的http的GET方法,請求的URL是/,協議是HTTP/1.0

下面是第⑫號包的詳細內容:

 

 

以上的報文是HTTP請求的報文。

那么HTTP請求報文和響應報文是什么格式呢?

起始行:如GET/HTTP/1.0(請求的方法,請求的URL,請求所使用的協議)。
頭部信息:User-Agent Host等成對出現的值。
空行:這個也是必須的。
主體:xxx。

不管是請求報文還是響應報文,都遵循以上的格式。

那么起始行中的請求方法有那些呢?

GET:完整請求一個資源(常用)
HEAD:僅請求響應的首部
POST:提交表單(常用)
PUT:上傳文件(部分瀏覽器不支持這個方法)
DELETE:刪除
OPTIONS:返回請求的資源所支持的方法
TRACE:跟蹤一個資源其你去中間所經過的代理(該方法不是由瀏覽器發出)

那什么是URL,URI,URN呢?

URL:Uniform Resource Identifier,統一資源標識符
URI:Uniform Resource Locator,統一資源定位符,格式如:scheme://[username:password@]HOST:port/path/to/source,http://www.magedu.com/downloads/nginx-1.5.tar.gz
URN:統一資源名稱
URL和URN都屬於URI,為了方便就把URL和URI暫時都通指一個東西

請求的協議有那些呢?

http/0.9:stateless
http/1.0:MIME,keep-alive(保持連接),緩存
http/1.1:更多的請求方法,更精細的緩存控制,持久連接(persistent content)比較常用,下面是chrome發起的http請求報文頭部信息:

 

其中

Accept:就是告訴服務器,我接受那些MIME類型
Accept-Encoding:接受那些壓縮方式的文件
Accept-Language:告訴服務器能夠發送那些語言
Connection:告訴服務器支持keep-alive特性
Cookie:每次請求都會攜帶上Cookie以方便服務器識別是否是同一個客戶端
Host:用來標識請求服務器的那個虛擬機,比如Nginx里面可以定義很多虛擬主機,這里就是來標識要訪問是哪一個
User-Agent:用戶代理,一般情況是瀏覽器,也有其他類型,如wget, curl搜索引擎的蜘蛛等

條件請求首部:

If-Modified-Since是瀏覽器向服務器端詢問某個資源文件如果自從什么時間修改過,那么重新發送給我,這樣保證服務器資源文件更新時,瀏覽器再次請求,而不是使用緩存中的文件。

安全請求首部:

Anthorization:客戶端提供給服務器的認證信息

什么是MIME

MIME:多用途互聯網郵件擴展,是一個互聯網標准,它擴展了電子郵件的標准,使其能夠支持非ASCII標准字符,二進制格式附件等多種格式的郵件消息,這個標准被定義在RFC 2045,RFC2046,RFC2047,RFC2048,RFC2049等RFC中。由於RFC 882轉化而來的RFC2882規定電子郵件標准不允許在郵件消息中使用7位ASCII字符集以外的字符。因此,一些非英語消息和二進制文件,圖像,聲音,等非文字消息不能在電子郵件中傳輸。MIME規定了用於各種各樣的數據類型的符號化方法。此外在萬維網中使用HTTP協議也是用了MIME協議中的框架,標准被擴展為互聯網媒體類型

MIME遵循以下格式:major/minor主要類型/次要類型,例如:image/jpg,image/gif,text/html,video/quicktime,application/x-httpd-php。

四. 服務器響應http請求,瀏覽器得到html代碼

看下圖中第⑫號包是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 這個是代理能聯系到后端的服務器,但是后端的服務器在規定的時間內沒有給代理服務器響應

使用chrom瀏覽器可以看到響應頭消息

 

Connection:使用keep-alive特性
Content-Encoding:使用gizp方式對資源壓縮
Content-Type:MIME類型為html類型,字符集是UTF-8
Date:響應的日期
Server:使用的Web服務器
Transfer-Encoding:chunked分塊傳輸碼,是http中的一種數據傳輸基址,允許HTTP由網頁服務器發送給客戶端應用(通常是網頁瀏覽器)的數據可以分成多部分
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)架構提供服務

  1. nginx讀取配置文件。我們在瀏覽器的地址欄里輸入的是www.linux178.com,其完整的地址應該是http:www.linux178.com./,com后面還有個點(這個點代表的就是根域一般情況我們不用輸入,也不顯示),后面的斜杠/也不用添加,瀏覽器會自動添加,那么實際請求的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錯誤)加到后偶棉,那么添加后的URI如果是/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進程進行處理。

  2. 把php文件交給fastcgi進程處理
    於是nginx把index.php這個URL交給后端的fastcgi處理,等待fastcgi處理完成后(結合數據查詢出數據,填充模板生成html文件)返回給fastcgi一個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這個文件,每個分區(像linux中的ext3等文件系統,block塊是文件存儲的最小單元,默認是4096字節)包含元數據區和數據區,每一個文件在元數據區都有元數據條目(一般是128字節大小),每一個條目都有一個編號,我們稱之為inode(index node索引節點),這個inode里面包含文件類型,權限,連接次數,屬主和數組的ID,時間戳,這個文件占據了那些磁盤塊,也就是塊的編號(block每個文件可以占用多個block,並且block不一定是連續的,每個block都有編號),如下圖所示:
     

     還有一個要點:目錄其實也是普通文件,也要占用磁盤塊,目錄不是一個容器。默認創建的目錄都是4096字節,也就是說只需要占用一個磁盤塊,但是這個是不確定的。所以要找到目錄也需要到元數據區里面找到對應的條目,只有找到對應inode就可以找到目錄鎖占用的磁盤塊。
    那到底目錄里面存放着什么,難道不是文件或者其他目錄嗎?
    其實目錄里面存着這么一張表,里面放着目錄或者文件的名稱和對應的inode號(暫時稱之為映射表),如下圖:
     

     假設

    /          在數據區占據 1、2號block ,/其實也是一個目錄 里面有3個目錄 web 111
    web    占據 5號block 是目錄 里面有2個目錄 echo data
    echo   占據 11號 block 是目錄 里面有1個文件 index.php
    index.  php 占據 15 16號 block 是文件

    其在文件系統中分布如下圖
       

name內核究竟是怎么找到index.php這個文件的呢?

內核拿到nginx的IO系統調用要獲取/web/echo/index.php這個文件請求之后:
1.內核讀取元數據 / 的inode從inode里讀取/所對應的數據塊的編號然后再數據區找到對應的塊1,2號塊),讀取1號快上的映射表找到web這個名稱在元數據區對應的inode號
2.內核讀取web對應的node(3號)從中得知web在數據區對應的塊是5號塊於是到數據區找到5號塊從中讀取映射表知道echo對應的inode是5號於是元數據找到5號inode
3.內核讀取5號的inode得到echo在數據區對應的是11號塊於是到數據區讀取11號塊得到映射表得到index.php對應的inode是9號
4.內核到元數據讀取9號inode得到index.php對應的是15和16號數據塊於是在數據區域找到15,16號塊讀取其中的內容得到index.php的完整內容

五. 瀏覽器解析html代碼,並請求html代碼中的資源

瀏覽器拿到index.html文件后,就開始解析其中的html代碼,遇到js/css/image等靜態資源時,就向服務器端去請求下載(會使用多線程下載,每個瀏覽器的線程數不一樣),這個時候就用上keep-alive特性了,建立一次HTTP連接,可以請求多個資源,下載資源的順序就是按照代碼里的順序,但是由於每個資源大小不一樣,而瀏覽器是多線程請求資源,所以從下圖可以看出,這里的順序不一定是代碼中的順序。

瀏覽器在請求靜態資源時(在未過期的情況下),向服務器發起一個http請求(詢問自從上一次修改時間到現在有沒有對資源進行修改),如果服務器端返回304狀態碼,(告訴服務器端沒有修改),那么瀏覽器會直接讀取本地的該資源文件。

  

 詳細的瀏覽器工作原理參考:http://kb.cnblogs.com/page/129756/

 最后瀏覽器使用自己內部的工作機制,把請求到的靜態資源和html代碼進行渲染,之后呈現給用戶。

至此,一次完整的http請求事務宣告完成。

 

參考:http://www.360doc.com/content/17/0427/09/35497398_649008108.shtml


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM