<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          理清 HTTP 之下的 TCP 流程,讓你的 HTTP 水平更上一層

          共 3452字,需瀏覽 7分鐘

           ·

          2022-05-24 01:10

          平時我們用 chrome devtools 的 Network 工具也只是能分析 HTTP 請求:

          TCP 層的東西看不見摸不著的,所以對它的理解也模模糊糊。

          那怎么能看到 TCP 層的數據包來理清 TCP 和 HTTP 的關系呢?

          這里推薦一個抓包工具 WireShark,它能抓取 TCP 層的包:

          今天我們就用它來抓包分析下 TCP 和 HTTP 吧!

          首先,我們準備這樣一段服務端代碼:

          const?express?=?require('express')

          const?app?=?express()

          app.get('/',?function?(req,?res)?{
          ??res.setHeader('Connection',?'close')
          ??res.end('hello?world');
          })

          app.listen(4000)

          用 express 起了一個服務,監(jiān)聽 4000 端口,處理路徑為 / 的 get 請求,返回 hello world 的響應體,并設置 Connection: close 的 header。

          瀏覽器訪問下:

          header 和 body 都符合預期。

          那 TCP 層都做了什么呢?

          我們用 WireShark 抓包分析下:

          打開 WireShark 后會看到有個設置按鈕:

          因為我們訪問的是 localhost: 4000,所以這里選擇本地回環(huán)地址那個虛擬網卡,并輸入抓包過濾條件為 port 4000:

          點擊 start 開始錄制,然后刷新一下瀏覽器:

          這樣就能看到抓到的 TCP 數據包:

          我們一一分析下。

          在分析之前需要了解一些 TCP 基礎知識:

          TCP 的頭部是這樣的:

          TCP 是從端口到端口的傳輸協議,所以開始是源端口和目的端口。

          接下來是序列號(sequence number),表示當前包的序號,后面是確認的序列號(acknowledgment number),表示我收到了序號為 xxx 的包。

          然后紅框標出的部分是 flags 標識位,通過 0、1 表示有沒有:

          這里我們只會用到其中的 SYN、ACK、FIN:

          • SYN:請求建立一個連接(說明這是鏈接的開始)
          • ACK:表示 ack number 是否是有效的
          • FIN:表示本端要斷開鏈接了(說明這是鏈接的結束)

          有了這些,我們就知道怎么區(qū)分 TCP 鏈接的開始和結束了。

          再看一下抓到的包:

          有 SYN 標志位的是連接的開始,有 FIN 標志位的是連接的結束,所以我們分為 3 段來看:

          首先是連接開始的部分:

          大家聽過 TCP 的三次握手么?說的就是這個。

          其中有一個端口是 4000,這個是服務的端口,那另一個端口 57454 明顯就是瀏覽器的端口。

          首先是瀏覽器向服務器發(fā)送了一個 SYN 的 TCP 請求,表示希望建立連接,序列號 Seq 是 0。

          嚴格來說,序列號的相對值是 0,絕對值是 2454579144。

          然后服務器向瀏覽器發(fā)送了一個 SYN 的 TCP 請求,表示希望建立連接,ACK 是 1,代表現在的 ack number 是有效的:

          這里 ack number 的相對值是 1,絕對值是 2454579145,不就是上個 TCP 數據包的 seq 加 1 么?

          TCP 連接中就是通過返回 seq number + 1 作為 ack number 來確認收到的。

          然后又返回了一個 seq number 給瀏覽器,相對值是 0, 絕對值是 2765691269。

          瀏覽器收到后返回了一個 TCP 數據包給服務器,ack number 自然是 2765691270,代表收到了連接請求。

          這樣瀏覽器和服務器各自向對方發(fā)送了 SYN 的建立連接請求,并且都收到了對方的確認,那么 TCP 連接就建立成功了。

          這就是 TCP 三次握手的原理!

          趁熱打鐵來看下四次揮手的部分:

          瀏覽器向服務器發(fā)送了有 FIN 標志位的數據包,表示要斷開連接,然后服務端返回了 ACK 的包表示確認。

          之后服務端發(fā)送了 FIN 標志位的數據包給瀏覽器,表示要斷開連接,瀏覽器也返回了 ACK 的包表示確認。

          這樣就完成了四次揮手的過程。

          當然,具體確認的還是靠 ack number = seq number + 1 來實現的,和上面的一樣,就不展開了:

          我們通過抓包理清了 TCP 連接建立和連接的過程。

          那么為什么握手是三次,揮手是四次呢?

          因為揮手是一個 FIN,一個 ACK,一個 FIN + ACK,一個 ACK:

          而握手是一個 SYN,一個 ACK + SYN,一個 ACK:

          不過是因為握手時把 ACK 和 SYN 合并到一個數據包了而已。

          那揮手時能合并成三次么?

          不能!因為有兩個 ack number,怎么合并,沖突了,而握手時只有一個 ack number,自然可以合并。

          接下來再來看下連接建立后的 http 請求和響應吧:

          其實一次 HTTP 請求響應會有四個 TCP 數據包,其中兩個數據包與滑動窗口有關,這里先不展開了。

          我們就看下 HTTP 的那兩個包吧:

          請求的 seq 是這樣的:

          而響應的 ack 是這樣的:

          相對值是 ack number = seq number + 1 沒錯,但是絕對值不是:

          絕對值 2454579855 = 2454579145 + 710,也就是 ack number = seq number + segment len。

          這些細節(jié)暫時不用深究。

          總之,我們知道了HTTP 的請求和響應是通過序列號關聯在一起的。

          就算同一個 TCP 鏈接并行發(fā)送多個 HTTP 的請求和響應,它們也能找到各自對應的那個。就是通過這個 seq number 和 ack number。

          這里為啥鏈接建立了發(fā)送了一個請求就斷掉了呢?

          我刷新瀏覽器,請求了兩次,發(fā)現經歷了兩次連接的建立、http 請求響應、連接斷開:

          這是因為我設置了 Connection:close 的 header,它的作用就是一次 http 請求響應結束就斷開 TCP 鏈接。

          我們改成 HTTP 1.1 支持的 keep-alive 試試:

          設置 Connection 為 keep-alive,然后設置 keep-alive 的細節(jié)為 timeout 10 ,也就是 10s 后斷開。

          重啟服務器,再刷新下瀏覽器試試:

          可以看到在一個 TCP 連接內發(fā)送了多次 http 請求響應。(通過 SYN 開始,FIN 結束)

          這就是 keep-alive 的作用。

          細心的同學會發(fā)現只是瀏覽器向服務器發(fā)送了 FIN 數據包,服務器沒有發(fā)給瀏覽器 FIN 數據包。

          這是因為 keep-alive 的 header 只是控制的瀏覽器的斷開連接的行為,服務器的斷開連接邏輯是獨立的。

          這樣,我們就理清了 HTTP 在 TCP 層面的流程,連接的建立、斷開,請求響應,還有 keep-alive。

          總結

          我們平時都是分析 HTTP 請求響應,TCP 對我們來說看不見摸不著的,理解的模模糊糊。

          所以今天我們用 WireShark 抓了下 TCP 的包,來理清了 TCP 和 HTTP 的關系。

          TCP 是從一個端口到另一個端口的傳輸控制協議,TCP header 中有序列號 seq number、確認序列號 ack number,還有幾個標志位:

          • SYN 標志位代表請求建立連接
          • ACK 標志位代表當前確認序列號是有效的。
          • FIN 標志位代表請求斷開連接

          然后我們抓了 localhost:4000 的包分析了下 HTTP 請求的 TCP 流程,理清了三次握手(SYN、SYN + ACK、ACK),四次揮手(FIN、ACK、FIN + ACK、ACK)的連接建立、斷開的流程。知道了為什么不能三次揮手(因為兩個 ACK 沖突了)

          然后還理清了同一個 TCP 連接傳輸的多個 HTTP 請求響應是通過 seq number 和 ack number 來關聯的。

          之后我們分別測試了 Connection:close 和 Connection:keep-alive 的情況,發(fā)現確實 keep-alive 能減少頻繁的連接建立和斷開,能復用同一個 TCP 鏈接。

          HTTP 是通過 TCP 完成端口到端口的數據傳輸的。一個 TCP 連接可以傳輸多個 HTTP 請求、響應。請求和響應的關聯是通過 TCP 包的序列號 seq。

          理清了 TCP 和 HTTP 的關系,你是否對 HTTP 的理解更深了呢?


          瀏覽 33
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  手机操逼网站 | 欧美激情成人 | 日日撸夜夜草 | 国产精品国产三级国芦专播精品人 | 国产超碰青青草 |