Menu
快讀
  • 旅遊
  • 生活
    • 美食
    • 寵物
    • 養生
    • 親子
  • 娛樂
    • 動漫
  • 時尚
  • 社會
  • 探索
  • 故事
  • 科技
  • 軍事
  • 国际
快讀

身爲前端,你不得不懂的一些HTTP知識(附贈3道面試題)

2020 年 2 月 4 日 前端明澈

全文閱讀大致3分鍾,學習本文可以掌握以下知識:

  1. netcat、ss、lsof命令的使用
  2. tcp協議的三次握手和四次揮手
  3. udp協議的基本表現過程以及icmp報文發送的原因
  4. tcpdump、nc命令的使用
  5. 三道關于TCP/IP協議的面試題答案

1、從查看系統端口監聽說起

在平時的開發中,出現listen EADDRINUSE: address already in use :::3000這種錯誤的頻率很高,尤其在windows系統下,殺死個進程都殺不徹底。當遇到這種問題的時候,我們第一反應就是查看系統是哪個進程也在監聽同樣的端口。于是引出了我們要介紹的以下三個命令。

以下三個命令只在類UNI\系統上,系統之間的命令參數有一些細微差異,以系統提示爲准,下面說的都是指在linux系統上*

1.1、netstat

netstat命令提供了一些關于網絡連接的信息,可以用它來羅列所有監聽的TCP端口或UDP端口,以及對應的套接字狀態,如下:

netstat -tunlp
  • -t 顯示TCP端口
  • -u 顯示UDP端口
  • -n 顯示IP地址而不是域名
  • -l 只顯示正在監聽的端口
  • -p 顯示監聽端口的進程ID

輸出大致如下:

Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:27017                         0.0.0.0:*               LISTEN      1889/mongod
tcp        0      0 0.0.0.0:80                               0.0.0.0:*               LISTEN      786/nginx -g daemon
tcp        0      0 0.0.0.0:22                               0.0.0.0:*               LISTEN      884/sshd
tcp        0      0 0.0.0.0:443                             0.0.0.0:*               LISTEN      786/nginx -g daemon
tcp6       0      0 :::8080                                      :::*                    LISTEN      23087/node
tcp6       0      0 :::10000                                               :::*                    LISTEN      4988/node
tcp6       0      0 :::80                   :::*                    LISTEN      786/nginx -g daemon
tcp6       0      0 :::8054                 :::*                    LISTEN      11915/node
udp        0      0 172.16.179.237:123      0.0.0.0:*                           750/ntpd
udp        0      0 127.0.0.1:123           0.0.0.0:*                           750/ntpd
udp        0      0 0.0.0.0:123             0.0.0.0:*                           750/ntpd
udp6       0      0 :::123                  :::*                                750/ntpd

netstat命令如今已經過時了,因爲有新的命令替換-ss。

1.2、ss

ss命令沒有了netstat的一些特性,不過它暴露出更多的TCP狀態並且它更加輕量快速。該命令的選項和netstat大致一樣,所以很容易上手:

ss -tunlp

輸出大致如下:

Netid  State      Recv-Q Send-Q           Local Address:Port                          Peer Address:Port
udp    UNCONN     0      0               172.16.179.237:123                                      *:*                   users:(("ntpd",pid=750,fd=19))
udp    UNCONN     0      0                    127.0.0.1:123                                      *:*                   users:(("ntpd",pid=750,fd=18))
udp    UNCONN     0      0                            *:123                                      *:*                   users:(("ntpd",pid=750,fd=17))
udp    UNCONN     0      0                           :::123                                     :::*                   users:(("ntpd",pid=750,fd=16))
tcp    LISTEN     0      128                          *:27017                                    *:*                   users:(("mongod",pid=1889,fd=7))
tcp    LISTEN     0      128                          *:80                                       *:*                   users:(("nginx",pid=11173,fd=10),("nginx",pid=786,fd=10))
tcp    LISTEN     0      128                          *:22                                       *:*                   users:(("sshd",pid=884,fd=3))
tcp    LISTEN     0      128                          *:443                                      *:*                   users:(("nginx",pid=11173,fd=9),("nginx",pid=786,fd=9))
tcp    LISTEN     0      128                         :::8080                                    :::*                   users:(("node",pid=23087,fd=10))
tcp    LISTEN     0      128                         :::10000                                   :::*                   users:(("node",pid=4988,fd=10))
tcp    LISTEN     0      128                         :::80                                      :::*                   users:(("nginx",pid=11173,fd=11),("nginx",pid=786,fd=11))
tcp    LISTEN     0      128                         :::8054                                    :::*                   users:(("node",pid=11915,fd=12))

1.3、lsof

lsof是一個強大的命令行工具,提供了進程打開的文件的一些信息。因爲在Linux,一切皆文件。所以一個打開的套接字也可以認爲是一個文件。

羅列所有監聽的TCP端口:

lsof -nP -iTCP -sTCP:LISTEN
  • -n 不要轉換端口號爲端口名稱
  • -p 不要解析域名,顯示其IP地址
  • -iTCP -sTCP:LISTEN 顯示TCP狀態爲LISTEN的網絡文件

輸出如下:

COMMAND     PID        USER         FD          TYPE         DEVICE SIZE/OFF NODE NAME
nginx               786         root        9u            IPv4           13574      0t0  TCP *:443 (LISTEN)
nginx               786         root       10u            IPv4           13575      0t0  TCP *:80 (LISTEN)
nginx               786         root       11u            IPv6           13576      0t0  TCP *:80 (LISTEN)
sshd                884         root        3u              IPv4           14458      0t0  TCP *:22 (LISTEN)
mongod         1889         root       7u              IPv4           21178      0t0  TCP *:27017 (LISTEN)
node               4988         root      10u            IPv6           40123      0t0  TCP *:10000 (LISTEN)
nginx              11173 www-data    9u              IPv4           13574      0t0  TCP *:443 (LISTEN)
nginx             11173 www-data   10u              IPv4           13575      0t0  TCP *:80 (LISTEN)
nginx             11173 www-data   11u              IPv6           13576      0t0  TCP *:80 (LISTEN)
node              11915     root         12u              IPv6         7200966      0t0  TCP *:8054 (LISTEN)
node              23087     root         10u             IPv6         5497007      0t0  TCP *:8080 (LISTEN)

查找指定端口可以這樣:lsof -nP -iTCP:8054 -sTCP:LISTEN

COMMAND   PID USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
node    11915 root   12u  IPv6 7200966      0t0  TCP *:8054 (LISTEN)

好了,三個命令介紹到此爲止。這個時候問一下大家一個問題:

上述過濾的狀態都是LISTEN,那麽TCP有多少種狀態?狀態與狀態之間的變化是怎樣的?你能從某個狀態中就能推斷出當前TCP連接處于什麽階段嗎?

這個問題你自己心中有數的話,可以跳過下一小節

2、TCP狀態的轉移

下圖是從wiki上引用的TCP狀態轉移圖:

身爲前端,你不得不懂的一些HTTP知識(附贈3道面試題)

圖片來自:wiki

看著有點複雜,我們將其拆分成最熱門的兩個步驟:三次握手、四次揮手。後面附贈面試題答案哦~

2.1、三次握手

身爲前端,你不得不懂的一些HTTP知識(附贈3道面試題)

圖片來自:wiki

  • 客戶端向服務器發送TCP連接請求數據包,客戶端狀態從CLOSED變爲SYN_SENT,其中包含主機A的初始序列號seq(A)=x。(其中報文中同步標志位SYN=1,ACK=0,表示這是一個TCP連接請求數據報文;序號seq=x,表明傳輸數據時的第一個數據字節的序號是x);
  • 服務端收到請求後,會發回連接確認數據包。服務端狀態從LISTEN變爲SYN_RECEIVED,(其中確認報文段中,標識位SYN=1,ACK=1,表示這是一個TCP連接響應數據報文,並含服務端的初始序列號seq(B)=y,以及服務端對客戶端初始序列號的確認號ack(B)=seq(A)+1=x+1)
  • 客戶端收到服務端的確認報文後,還需作出Ack(此時這個數據包可以攜帶數據報文了),即發送一個序列號seq(A)=x+1;確認號爲ack(A)=y+1的報文,此時客戶端狀態轉爲ESTABLISHED,服務端收到這個ACK後,狀態也轉爲ESTABLISHED;

2.2、面試題:爲什麽需要三次握手?

此題需要從兩個點回答:

  • 首要原因是爲了解決客戶端多次發起請求的問題,你想想看,在網絡狀況不好的情況下,客戶端發起一個連接請求沒收到響應的話會繼續發送請求,如果最先發送的請求到服務端了,在用兩次握手的前提下,服務端就會用這個已經過期的請求的序列號建立連接,而客戶端卻認爲這個序列號是過期的,就會忽略掉,這樣雙方造成了很大的誤解。而如果用三次握手的話,客戶端就還有機會告訴服務端你的這個響應是過期的還是正常的,如果是過期的就可以發送RST消息告訴服務端斷掉這個連接,如果不是的話,就返回ACK建立連接。
  • 第二個原因是爲了同步雙方的序列號,兩次握手是做不到同步雙方的序列號的。

關于第一個原因可以參考下圖(截圖自RFC793的3.4節):

身爲前端,你不得不懂的一些HTTP知識(附贈3道面試題)

2.3、四次揮手

身爲前端,你不得不懂的一些HTTP知識(附贈3道面試題)

  • 第一次揮手(FIN=1,seq=x) 假設客戶端想要關閉連接,客戶端發送一個FIN標志位置爲1的包,表示自己已經沒有數據可以發送了,但是仍然可以接受數據。發送完畢後,客戶端進入FIN_WAIT_1狀態
  • 第二次揮手(ACK=1,ACKnum=x+1) 服務器端確認客戶端的FIN包,發送一個確認包,表明自己接受到了客戶端關閉連接的請求,但還沒有准備好關閉連接。 發送完畢後,服務器端進入CLOSE_WAIT狀態,客戶端接收到這個確認包之後,進入FIN_WAIT_2狀態,等待服務器端關閉連接。
  • 第三次揮手(FIN=1,seq=y) 服務器端准備好關閉連接時,向客戶端發送結束連接請求,FIN置爲1。發送完畢後,服務器端進入LAST_ACK狀態,等待來自客戶端的最後一個ACK。
  • 第四次揮手(ACK=1,ACKnum=y+1) 客戶端接收到來自服務器端的關閉請求,發送一個確認包,並進入TIME_WAIT狀態,等待可能出現的要求重傳的ACK包。 服務器端接收到這個確認包之後,關閉連接,進入CLOSED狀態。 客戶端等待了某個固定時間(兩個最大段生命周期,2MSL,2 Maximum Segment Lifetime)之後,沒有收到服務器端的ACK,認爲服務器端已經正常關閉連接,于是自己也關閉連接,進入CLOSED狀態。

爲什麽是2MSL?因爲TCP/IP協議規定了超過這個時間的數據包都是會被廢棄掉的,也就是一個數據包在網絡中存活的最大時間

2.4、面試題:爲什麽需要四次揮手?

答:第二次和第三次無法整合起來變成三次揮手是因爲服務端接收到FIN報文之後,手上可能還有數據需要發送給客戶端,所以ACK和FIN不能同時發送。

3、UDP協議探析

探究UDP我們使用netcat這個工具,我們先用netcat來新建一個UDP服務器:

nc -u -l 0.0.0.0 3000

然後使用nc來新建一個客戶端:

nc -u -p 3001 localhost 3000
  • -u 指定udp協議
  • -l 指定監聽的端口和ip
  • -p 指定客戶端的源端口

我們還需要使用tcpdump工具來dump數據包,或者可以使用wireshark來抓包:

─$ sudo tcpdump -ni lo0 'udp port 3001 or icmp'                                                       1 ↵
Password:
tcpdump: data link type PKTAP
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on pktap, link-type PKTAP (Apple DLT_PKTAP), capture size 262144 bytes
  • -n 不用將Ip地址解析爲域名
  • -i 指定抓包的網卡,我們這裏指定抓的是回環口

因爲UDP是無連接的,所以這會看不到任何的數據包

但是真的完全沒有“連接”嗎?其實並不是完全正確的,至少在客戶端這邊有這麽一個連接存在,

我們使用上面提到的命令lsof:

╰─$ lsof -nP -iUDP | grep 3000
nc        60738 linxiaowu    3u  IPv4 0x8ea59d14b13d38bf      0t0  UDP *:3000
nc        60744 linxiaowu    6u  IPv4 0x8ea59d14b13d208f      0t0  UDP 127.0.0.1:3001->127.0.0.1:3000

從上面可以看出,客戶端已經有了連接的概念,服務端還沒有意識有這麽一個連接存在。接著我們從客戶端發送一條消息:hi,此時我們再使用lsof可以看到服務端也有此連接了:

❯ lsof -nP -iUDP | grep 3000
nc        60738 linxiaowu    3u  IPv4 0x8ea59d14b13d38bf      0t0  UDP 127.0.0.1:3000->127.0.0.1:3001
nc        60744 linxiaowu    6u  IPv4 0x8ea59d14b13d208f      0t0  UDP 127.0.0.1:3001->127.0.0.1:3000

所以從這裏可以看到UDP的連接完全建立是在第一個數據包發送之後。tcpdump可以看到數據包:

17:18:17.419352 IP 127.0.0.1.3001 > 127.0.0.1.3000: UDP, length 3

這個時候我們關掉服務器,如果是TCP,那麽會有一系列的協商報文發送出去,而UDP就不會,再看端口:

lsof -nP -iUDP | grep 3000
nc        60744 linxiaowu    6u  IPv4 0x8ea59d14b13d208f      0t0  UDP 127.0.0.1:3001->127.0.0.1:3000

客戶端此時並不知道服務器down掉了,接著我們從客戶端發送消息hi?,此時netcat命令會自動退出,這個時候,它才知道連接斷開了,並且我們發現有個ICMP報文從服務端發送出來:

身爲前端,你不得不懂的一些HTTP知識(附贈3道面試題)

ICMP報文提示端口不可達,也就是服務端的端口關掉監聽了。

根據TCP/IP協議的規定,如果對應的服務不可用,那麽系統內核根據協議類型發送對應的響應報文,對于UDP應該發送一個“端口不可達”的ICMP報文,對于TCP應該發送一個TCP RST消息

所以UDP的連接斷開會延遲到其中一方發送報文收到端口不可達的時候:

17:22:05.710012 IP 127.0.0.1.3001 > 127.0.0.1.3000: UDP, length 4
17:22:05.710047 IP 127.0.0.1 > 127.0.0.1: ICMP 127.0.0.1 udp port 3000 unreachable, length 36

3.1、面試題:爲什麽DNS使用UDP協議?

這個問題其實是個僞命題。使用udp協議是以前舊有的規範定義的,現在的RFC是將TCP協議也一起寫進去的。因爲以前的網絡帶寬不高,使用UDP協議會比TCP協議的數據包小很多,並且以前的DNS包體一般都很小,很少超過512字節的,但是現在的DNS支持Ipv6、https,包體也變大了,這個時候如果還是使用udp協議,很容易因爲mtu之類的限制導致傳輸失敗,因爲tcp可以分包傳輸,所以對于大的包體,就大部分都是使用tcp協議。

原文:豆米播客


喜歡小編的可以點個贊關注小編哦,小編每天都會給大家分享文章。

我自己是一名從事了多年的前端老程序員,小編爲大家准備了新出的前端編程學習資料,免費分享給大家!

如果你也想學習前端,那麽幫忙轉發一下然後再關注小編後私信【1】可以得到我整理的這些前端資料了(私信方法:點擊我頭像進我主頁有個上面有個私信按鈕)

相關文章:

  • Flink 基礎學習(二)搭建一個 "Hello World" 程序
科技

發佈留言 取消回覆

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

©2025 快讀 | 服務協議 | DMCA | 聯繫我們