全文閱讀大體3分鐘,學習本文能夠掌握如下知識:html
netcat
、ss
、lsof
命令的使用tcp
協議的三次握手和四次揮手udp
協議的基本表現過程以及icmp報文發送的緣由tcpdump
、nc
命令的使用在平時的開發中,出現listen EADDRINUSE: address already in use :::3000
這種錯誤的頻率很高,尤爲在windows系統下,殺死個進程都殺不完全。當遇到這種問題的時候,咱們第一反應就是查看系統是哪一個進程也在監聽一樣的端口。因而引出了咱們要介紹的如下三個命令。node
如下三個命令只在類UNI*系統上,系統之間的命令參數有一些細微差別,以系統提示爲準,下面說的都是指在linux系統上linux
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命令現在已通過時了,由於有新的命令替換-ss
。windows
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))
複製代碼
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鏈接處於什麼階段嗎?
這個問題你本身心中有數的話,能夠跳過下一小節
下圖是從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)ESTABLISHED
,服務端收到這個ACK後,狀態也轉爲ESTABLISHED
;此題須要從兩個點回答:
RST
消息告訴服務端斷掉這個鏈接,若是不是的話,就返回ACK創建鏈接。關於第一個緣由能夠參考下圖(截圖自RFC793的3.4節):
FIN
標誌位置爲1的包,表示本身已經沒有數據能夠發送了,可是仍然能夠接受數據。發送完畢後,客戶端進入FIN_WAIT_1
狀態FIN
包,發送一個確認包,代表本身接受到了客戶端關閉鏈接的請求,但尚未準備好關閉鏈接。 發送完畢後,服務器端進入CLOSE_WAIT
狀態,客戶端接收到這個確認包以後,進入FIN_WAIT_2
狀態,等待服務器端關閉鏈接。FIN
置爲1。發送完畢後,服務器端進入LAST_ACK
狀態,等待來自客戶端的最後一個ACK。TIME_WAIT
狀態,等待可能出現的要求重傳的ACK
包。 服務器端接收到這個確認包以後,關閉鏈接,進入CLOSED
狀態。 客戶端等待了某個固定時間(兩個最大段生命週期,2MSL,2 Maximum Segment Lifetime)以後,沒有收到服務器端的ACK
,認爲服務器端已經正常關閉鏈接,因而本身也關閉鏈接,進入CLOSED
狀態。爲何是2MSL?由於TCP/IP協議規定了超過這個時間的數據包都是會被廢棄掉的,也就是一個數據包在網絡中存活的最大時間
答:第二次和第三次沒法整合起來變成三次揮手是由於服務端接收到FIN報文以後,手上可能還有數據須要發送給客戶端,因此ACK和FIN不能同時發送。
探究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
複製代碼
這個問題實際上是個僞命題。使用udp協議是之前舊有的規範定義的,如今的RFC是將TCP協議也一塊兒寫進去的。由於之前的網絡帶寬不高,使用UDP協議會比TCP協議的數據包小不少,而且之前的DNS包體通常都很小,不多超過512字節的,可是如今的DNS支持Ipv六、https,包體也變大了,這個時候若是仍是使用udp協議,很容易由於mtu之類的限制致使傳輸失敗,由於tcp能夠分包傳輸,因此對於大的包體,就大部分都是使用tcp協議。