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

全文閱讀大體3分鐘,學習本文能夠掌握如下知識:html

  1. netcatsslsof命令的使用
  2. tcp協議的三次握手和四次揮手
  3. udp協議的基本表現過程以及icmp報文發送的緣由
  4. tcpdumpnc命令的使用
  5. 三道關於TCP/IP協議的面試題答案

一、從查看系統端口監據說起

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

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

1.一、netstat

netstat命令提供了一些關於網絡鏈接的信息,能夠用它來羅列全部監聽的TCP端口或UDP端口,以及對應的套接字狀態,以下:nginx

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命令現在已通過時了,由於有新的命令替換-sswindows

1.二、ss

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

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.三、lsof

lsof是一個強大的命令行工具,提供了進程打開的文件的一些信息。由於在Linux,一切皆文件。因此一個打開的套接字也能夠認爲是一個文件。網絡

羅列全部監聽的TCP端口:ssh

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鏈接處於什麼階段嗎?

這個問題你本身心中有數的話,能夠跳過下一小節

二、TCP狀態的轉移

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

圖片來自: wiki

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

2.一、三次握手

圖片來自: 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.二、面試題:爲何須要三次握手?

此題須要從兩個點回答:

  • 首要緣由是爲了解決客戶端屢次發起請求的問題,你想一想看,在網絡情況很差的狀況下,客戶端發起一個鏈接請求沒收到響應的話會繼續發送請求,若是最早發送的請求到服務端了,在用兩次握手的前提下,服務端就會用這個已通過期的請求的序列號創建鏈接,而客戶端卻認爲這個序列號是過時的,就會忽略掉,這樣雙方形成了很大的誤解。而若是用三次握手的話,客戶端就還有機會告訴服務端你的這個響應是過時的仍是正常的,若是是過時的就能夠發送RST消息告訴服務端斷掉這個鏈接,若是不是的話,就返回ACK創建鏈接。
  • 第二個緣由是爲了同步雙方的序列號,兩次握手是作不到同步雙方的序列號的。

關於第一個緣由能夠參考下圖(截圖自RFC793的3.4節):

2.三、四次揮手

  • 第一次揮手(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.四、面試題:爲何須要四次揮手?

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

三、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報文從服務端發送出來:

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.一、面試題:爲何DNS使用UDP協議?

這個問題實際上是個僞命題。使用udp協議是之前舊有的規範定義的,如今的RFC是將TCP協議也一塊兒寫進去的。由於之前的網絡帶寬不高,使用UDP協議會比TCP協議的數據包小不少,而且之前的DNS包體通常都很小,不多超過512字節的,可是如今的DNS支持Ipv六、https,包體也變大了,這個時候若是仍是使用udp協議,很容易由於mtu之類的限制致使傳輸失敗,由於tcp能夠分包傳輸,因此對於大的包體,就大部分都是使用tcp協議。

參考

  1. ICMP : Port unreachable error even if port is open
  2. Transmission Control Protocol
  3. Creating a UDP connection with netcat
  4. RFC793
相關文章
相關標籤/搜索