輸入URL以後和編碼

更新:加了幾張圖,圖來自阮一峰。

當我們在瀏覽器中輸入baidu.com並回車,頁面是怎麼顯示的?

如果用一句話總結,我們在客戶端/client(這裡是瀏覽器)向物理伺服器(server)上某埠(這裡是80埠)上運行著的服務端(server,與上一個server不同)發送了一個http請求報文,並收到了服務端發送的http響應報文,把響應的html內容渲染到頁面之中。

當然,實際上並沒有這麼簡單,但似乎也沒那麼難,我們慢慢來剖析。

零、基本概念

一般的URL形如messiahhh.com/question:

其實就是protocol://hostname:port/pathname?search#hash

我們的電腦是個主機,遠方的物理伺服器也是主機,主機都有多達65536個埠,每個埠上只能跑一個程序。

我們的電腦上平時運行著各種程序(客戶端),qq是,瀏覽器也是,這些程序都佔據著一個不同的埠。

而物理伺服器上也有各種東西,比如nginx,node伺服器(指的是軟體上的伺服器,跑在物理伺服器的埠中,一般node伺服器收到請求後會做出各種的響應)

TCP/IP協議族,包括各種協議,並且分成四個層,分別是應用層(包含DNS協議,HTTP協議等)、傳輸層(如TCP協議)、網路層(如IP協議)、鏈路層。

一、DNS解析

當我們輸入url的時候,先會對域名進行DNS解析,把域名解析成對應的伺服器ip地址。

1 首先先要看瀏覽器本身有沒有緩存著這個域名所對應的ip,因為我們是第一次上/很久沒上,所以瀏覽器本身沒有緩存

2 這時候要靠操作系統了,如果操作系統里有緩存倒也是好事,就不用下一步了。

3 如果操作系統中也沒有緩存,就去查看本機中一個叫hosts的文件中是否有記錄域名和ip的對應關係,就win10而言,hosts放在system32/drivers/etc下,用編輯器打開就能看到記錄的一些對應關係

4 如果hosts文件中也沒有 那麼只能寄託網路運營商的DNS伺服器了,一般情況下,DNS伺服器中緩存著許多的對應信息。

5 若DNS伺服器中也沒有緩存,那麼DNS伺服器會去查詢,這裡不展開,可以去看看頂級域,根域的相關知識。

二、HTTP協議

本質就是根據URL建立一個請求

域名和埠會寫入請求頭中,查詢字元串也會寫在請求行里,如果是在瀏覽器輸入URL的話,請求方法默認為get

此處介紹一下請求和響應的結構

請求

包含請求頭部,請求實體。

請求頭部由請求行和請求頭部欄位(分別為通用頭部欄位,實體頭部欄位,請求頭部欄位)構成

請求行分別包括請求方法,請求URL,HTTP版本號

響應

包含響應頭部,響應實體

響應頭部由狀態行和響應頭部欄位(分別為通用頭部欄位,實體頭部欄位,響應頭部欄位)構成

狀態行分別包括HTTP版本號,狀態碼,狀態原因

頭部和實體都由空行(CR+LF)隔開

CR,回車符,0x0d

LF,換行符,0x0a

註:查詢字元串是放在請求URL中的,這也是和get方法和post方法的不同點之一。post方法發送的數據是在實體中。

三、TCP協議

為了傳輸方便,把請求報文分割,並分別給每個報文打上標記和埠號。

另外一點就是TCP三次握手。三次握手的主要目的是確保報文能夠正確的傳到對方,那麼三次握手是怎麼進行的?

客戶端首先發送一個帶SYN標誌的數據包給服務端(服務端 知道 能收到數據),服務端返回一個帶SYN/ACK的數據包給客戶端(客戶端 知道 數據送達至服務端,能收到數據),客戶端返回一個帶ACK的數據包(服務端 知道 數據能送達至客戶端)。總之而言是通信的雙方確保既能夠發送數據給對方,又能確保收到對方的數據。

在TCP三次握手之後,就進行請求報文的傳輸。

等報文傳輸結束後,進行TCP四次揮手,即斷開連接,此處不細談。

TCP連接最以前是每個請求都進行一次TCP握手,揮手。後來使用了持續連接,也就是在tcp握手揮手中進行多個請求,一是減輕了伺服器的負擔,二是加快了頁面的顯示。持續連接仍然滿足不了我們,最後發展成管線化,也就是在持續化連接的基礎上加以改進,持續化連接中是請求1,響應1,請求2,響應2這樣執行,而管線化則是請求1,請求2,響應1,響應2如此,效果很明顯。

四、IP協議

增加作為通信目的地的MAC地址後轉發給鏈路層

五、鏈路層

接收端的伺服器在鏈路層接受到數據,按序往上層發送,一直到應用層

當然,我們前端一般只要了解HTTP協議就夠了,所以簡化一下就是,建立了一個HTTP請求報文,經過編碼轉化成二進位數據,經過光纜和路由,最後到達服務端,再經過解碼,得到相應的數據。

六、編碼

要不先介紹一下編碼??

事實上很多人搞不清字符集和編碼方式的區別

0x00

世界上有10種人,一種是懂二進位的,一種是不懂二進位的。

因為計算機的世界中只有0和1,信息的傳輸,存儲,都是以0和1的形式存在的。

比如當我們發送請求,最終的形式還是發送1和0的組合,經由光纖,電纜進行信息的傳輸。

我們電腦的硬碟大小,最小單位是1Byte,也就是一位元組,一位元組等於8個位,每個位上可以是1或0

0x01、ASCII

上個世紀60年代,美國制定了一套字元編碼,對英語字元與二進位位之間的關係,做了統一規定。這被稱為ASCII碼,一直沿用至今。

ASCII碼一共規定了128個字元的編碼,比如空格"SPACE"是32(二進位00100000),大寫的字母A是65(二進位01000001)。這128個符號(包括32個不能列印出來的控制符號),只佔用了一個位元組的後面7位,最前面的1位統一規定為0

也就是說,我們可以用1位元組中的7位來表示各種字元,包括控制字元和非控制字元(a,b,c等)

0x02、ISO-8859

128個字元只能滿足美國人,對其他國家卻不友好,因此許多西歐國家幾乎是同時間對ASCII進行擴展,在保留原ASCII的基礎上,利用位元組中閑置的最高位編入新的符號。從此,一個位元組就可以表示256個字元了。

不過雖然這256個字元都是向下兼容ASCII的,但每個國家新增的字元又不相同,帶來了許多問題。

比如

  • ISO/IEC 8859-1 (Latin-1) - 西歐語言

  • ISO/IEC 8859-2 (Latin-2) - 中歐語言

  • ISO/IEC 8859-3 (Latin-3) - 南歐語言。世界語也可用此字符集顯示。

  • ISO/IEC 8859-4 (Latin-4) - 北歐語言

  • ISO/IEC 8859-5 (Cyrillic) - 斯拉夫語言

  • ISO/IEC 8859-6 (Arabic) - 阿拉伯語

  • ISO/IEC 8859-7 (Greek) - 希臘語

    等等..

0x03、GB 2312,GBK,GB10830 BIG5

與ISO-8859類似,中國也在ASCII的基礎上擴展了自己的字元,有一點不同就是中國的字元太多了,一個位元組根本容納不下這麼多的數量。

GB2312/GB2312-80,是中華人民共和國國家標準簡體中文字符集,又稱G80,1981年5月1日實施,GB 2312編碼通行於中國大陸,新加坡等地也採用此編碼,GB 2312 標準共收錄 6763 個漢字。兩個位元組表示一個字元,第一個位元組稱為「高位位元組」,第二個位元組稱為「低位位元組」。

GB 2312中對所收漢字進行了「分區」處理,每區含有94個漢字/符號。這種表示方式也稱為區位碼,此處不細談,有興趣可自行查詢。

GBK,又稱漢字內碼擴展規範,1995年12月1日制訂,GBK 共收入 21886 個漢字和圖形符號。

依然是兩個位元組表示一個字元,可以看成GB2312的擴展。

GB 18030,又稱國家標準 GB 18030-2005《信息技術中文編碼字符集》,GB 18030 與 GB 2312-80 和 GBK 兼容,共收錄漢字70244個,發現很明顯兩個位元組是容不下這麼多字元的。沒錯,與GB2312、GBK不同,GB 18030採用多位元組編碼,每個字由1 個、2 個或 4 個位元組組成。

單位元組,其值從 0 到 0x7F,與 ASCII 編碼兼容。

雙位元組,第一個位元組的值從 0x81 到 0xFE,第二個位元組的值從 0x40 到 0xFE(不包括0x7F),與 GBK 標準兼容。

四位元組,第一個位元組的值從 0x81 到 0xFE,第二個位元組的值從 0x30 到 0x39,第三個位元組從0x81 到 0xFE,第四個位元組從 0x30 到 0x39。

BIG5 台灣那邊的編碼,不談..

0x04、UNICODE UTF-8,16,32

你會覺得很蛋疼,每個國家都搞一個自己的編碼方式?有毛病啊,不同的編碼解碼方式很容易出現亂碼,直接統一多好,這時候UNICODE就出現了。

這裡就要講到字符集和編碼方式的問題了。

在開始的ASCII,ISO-8859,GB系列,其實都是兩個點的映射關係,也就是一個字元總對應一個二進位。

那麼這裡有什麼不同呢?

簡單的說,可以看成三個點,字元->碼點(code point)->二進位

一個字元,總對應一個叫做碼點的東西,用U+xxxx來表示不同的碼點,這裡的x是十六進位數。

Unicode不是一次性定義的,而是分區定義。每個區可以存放65536個字元,稱為一個平面(plane)。目前,一共有17個平面,也就是說,整個Unicode字符集的大小現在是2的21次方。

這裡字元和碼點的對應關係,就是Unicode字符集。

而UTF-8/16/32叫做編碼方式,不同的編碼方式,意味著同一個碼點對應著不同的二進位/位元組。

UTF-32

這恐怕是三者中最簡單粗暴的編碼方式了,每個碼點都使用四個位元組表示,位元組內容一一對應碼點,不過很明顯,每個字元都用4個位元組太浪費了。

UTF-8

這應該是用的最多的一種編碼了。UTF-8是一種變長的編碼方法,字元長度從1個位元組到4個位元組不等

編號範圍位元組0x0000 - 0x007F10x0080 - 0x07FF20x0800 - 0xFFFF30x010000 - 0x10FFFF4

至於UTF-8這種編碼方式具體是如何把碼點轉換成對應的二進位,這裡不談,可以見阮一峰的《字元編碼筆記》

UTF-16

UTF-16編碼介於UTF-32與UTF-8之間,同時結合了定長和變長兩種編碼方法的特點。

基本平面的字元佔用2個位元組,輔助平面的字元佔用4個位元組。也就是說,UTF-16的編碼長度要麼是2個位元組(U+0000到U+FFFF),要麼是4個位元組(U+010000到U+10FFFF)

具體是如何轉換的,這裡也不談。

這樣應該就知道字符集和編碼方式的區別了吧,字元對應不同碼點,這就是字符集;同一個碼點對應不同位元組,這就是編碼方式的區別?對於ASCII那些,可以說他們的字符集就是編碼方式。

七、Content-Type,meta,charset

那麼HTTP請求報文是怎麼編碼的呢?事實上,請求頭和實體的編碼方式是不同的,請求頭都是ASCII編碼,而實體如果是本文,則一般是UTF-8編碼,圖片也是轉化成對應的二進位(本人對圖片的編碼不了解)。二進位傳到服務端後,進行解碼,就得到了相應的數據。

這裡談一談HTML文件是怎麼渲染到頁面上的。

假設伺服器有一個UTF-8編碼的HTML文件(就像我們在電腦中寫好HTML文件,用UTF-8的編碼方式保存為二進位存在硬碟中),當HTML文件作為響應的主題返回到瀏覽器的時候,瀏覽器根據響應頭部中Content-Type欄位中charset中寫的編碼方式對二進位HTML進行解碼,從而渲染到頁面上。但如果響應頭部中沒有寫charset的話怎麼辦?這時候,瀏覽器會猜,它會先用ASCII對二進位進行解碼,等讀取到meta中的charset的時候,用得到的編碼方式重新進行對二進位的解碼。如果meta中還是沒有寫編碼方式,那麼就沒轍了,整個html用ASCII進行解碼,如果頁面中有中文的話,就不可避免出現亂碼了。

補充

DNS劫持

所謂DNS劫持,一般是地方網路運營商的惡意行為,比如改寫DNS伺服器中緩存,將域名A原本對應的IP A改成IP B,比如當用戶輸入google.com,原本是指向谷歌的ip地址,經過這一修改,反而指向了百度的ip地址。一般遇到這種情況,可以更換DNS伺服器,或者直接修改HOSTS文件,因為我們知道HOSTS文件是放在DNS伺服器之前的,只要在hosts文件中讀到了對應關係,就不用管DNS伺服器了,當然直接輸入IP地址也是可行的。

Nginx代理

一般網站不會放在80埠,80埠放nginx伺服器,收到請求後轉發給其他埠上的服務端。當然Nginx還有很多其他用處。

Node中的Buffer

其實可以看成二進位數據的十六進位表示形式。

TODO:UCS-2和JavaScript

參考

Unicode與JavaScript詳解

淺談前端的 Unicode

瀏覽器根據charset判斷編碼方式的疑問?

圖解HTTP

谷歌


推薦閱讀:

計算機最底層的機器語言是如何變成物理電平信號輸給CPU的呢?
Ingress 中的 passcode 有哪些解碼技巧?
如何解決python不支持中文路徑的問題?
GB2312及其擴展標準和Unicode之間有什麼區別和優劣勢?
字元編碼是否是軟體工程中最噁心的問題?

TAG:编码 | HTTP | 前端开发 |