使用tcpdump,netstat簡單看下tcp連線握手與揮手的過程和核心緩衝區的變化
package main
import (
"fmt"
"net"
)
func main() {
listener, err := net.Listen("tcp", "127.0.0.1:8080")
if err != nil {
fmt.Printf("listen failed err: %v", err)
return
}
var a int
// 阻塞在這裡
fmt.Scanf("%d", &a)
fmt.Println("start accept")
for {
conn, err := listener.Accept()
if err != nil {
fmt.Printf("accept failed err: %v", err)
break
}
go HandleConn(conn)
}
}
func HandleConn(conn net.Conn) {
defer conn.Close()
buff := make([]byte, 1024)
for {
n, err := conn.Read(buff)
if err != nil {
fmt.Printf("read failed conn:%v err:%v\n", conn, err)
break
}
msg := string(buff[0:n])
fmt.Println(msg)
}
}
核心緩衝區的變化
上面的程式碼listen了一個埠之後,並沒有去accept,因為被scanf阻塞住了。我們在linux下面跑上面程式碼之後,用netstat -antp 檢視下網路狀態:
tcp 0 0 127.0.0.1:8080 0.0.0.0:* LISTEN 36535/server
然後我們用tcpdump抓個包,使用nc連線下服務(也可使用telnet),命令分別如下:
tcpdump -nn -i lo port 8080
nc 127.0.0.1 8080
此時看到tcpdump抓的包為:
00:22:53.528171 IP 127.0.0.1.47150 > 127.0.0.1.8080: Flags [S], seq 4152235494, win 43690, options [mss 65495,sackOK,TS val 44901801 ecr 0,nop,wscale 7], length 0
09:56:09.032928 IP 127.0.0.1.8080 > 127.0.0.1.47150: Flags [S.], seq 3160960910, ack 4152235495, win 43690, options [mss 65495,sackOK,TS val 44901801 ecr 44901801,nop,wscale 7], length 0
00:22:53.528194 IP 127.0.0.1.47150 > 127.0.0.1.8080: Flags [.], ack 1, win 342, options [nop,nop,TS val 44901801 ecr 44901801], length 0
表示三次握手成功,使用netstat -antp檢視網路狀態結果為:
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.1:8080 127.0.0.1:47150 ESTABLISHED -
我們可以看到雖然我們的程式並沒有accept,但是連線已經在核心裡面建立好,但是連線的資源還沒交給程式(PID為空)。
我們隨便傳輸幾個字元(例如:123456),發現網路狀態變為:
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program namel
tcp 7 0 127.0.0.1:8080 127.0.0.1:47151 ESTABLISHED -
發現Recv-Q為7,表示核心緩衝區有7個位元組的資料沒有被讀出去(網上看一些資料說,如果核心緩衝被打滿,會有資料包丟失,我本地測下來發現,緩衝區滿了之後,接收端會告訴傳送端 win為0,此時傳送端不在傳送攜帶資料的包,但是這個點還是值得注意下的,業務處理的能力很慢,導致資料包無法及時處理,會有包丟失的情況)。
使用lsof 命令可以看到程式開啟的檔案描述符,命令為 lsof -p ${PID},結果為:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
server 37671 kang cwd DIR 253,2 22 105990016 /home/kang/test-tcp/server
server 37671 kang rtd DIR 253,0 4096 128 /
server 37671 kang txt REG 253,0 2252800 135546735 /tmp/go-build605900202/b001/exe/server
server 37671 kang mem REG 253,0 2107816 201328826 /usr/lib64/libc-2.17.so
server 37671 kang mem REG 253,0 142296 201404660 /usr/lib64/libpthread-2.17.so
server 37671 kang mem REG 253,0 164432 201328819 /usr/lib64/ld-2.17.so
server 37671 kang 0u CHR 136,0 0t0 3 /dev/pts/0
server 37671 kang 1u CHR 136,0 0t0 3 /dev/pts/0
server 37671 kang 2u CHR 136,0 0t0 3 /dev/pts/0
server 37671 kang 3u IPv4 113803 0t0 TCP localhost:webcache (LISTEN)
server 37671 kang 5u a_inode 0,9 0 6835 [eventpoll]
server 37671 kang 6r FIFO 0,8 0t0 113804 pipe
server 37671 kang 7w FIFO 0,8 0t0 113804 pipe
看到37671 這個程式並沒有接收socket連線,這時我們讓程式開始accpet,輸出結果為:
start accept
123456
然後netstat看下網路狀態為:
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.1:8080 127.0.0.1:47151 ESTABLISHED 37671/server
發現連線已經交給了 37671這個程式,並且核心的Recv-Q資料也被程式讀出
執行lsof -p 37671結果為:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
server 37671 kang cwd DIR 253,2 22 105990016 /home/kang/test-tcp/server
server 37671 kang rtd DIR 253,0 4096 128 /
server 37671 kang txt REG 253,0 2252800 816394 /tmp/go-build283231499/b001/exe/server
server 37671 kang mem REG 253,0 2107816 201328826 /usr/lib64/libc-2.17.so
server 37671 kang mem REG 253,0 142296 201404660 /usr/lib64/libpthread-2.17.so
server 37671 kang mem REG 253,0 164432 201328819 /usr/lib64/ld-2.17.so
server 37671 kang 0u CHR 136,0 0t0 3 /dev/pts/0
server 37671 kang 1u CHR 136,0 0t0 3 /dev/pts/0
server 37671 kang 2u CHR 136,0 0t0 3 /dev/pts/0
server 37671 kang 3u IPv4 115369 0t0 TCP localhost:webcache (LISTEN)
server 37671 kang 4u IPv4 115372 0t0 TCP localhost:webcache->localhost:47151 (ESTABLISHED)
server 37671 kang 5u a_inode 0,9 0 6835 [eventpoll]
server 37671 kang 6r FIFO 0,8 0t0 115370 pipe
server 37671 kang 7w FIFO 0,8 0t0 115370 pipe
close wait狀態的出現
我們把上述程式碼 defer conn.Close() 註釋掉,使用nc發起一個連線,主動關閉,使用tcpdump抓包情況如下:
01:26:31.145725 IP 127.0.0.1.47153 > 127.0.0.1.8080: Flags [F.], seq 1, ack 1, win 342, options [nop,nop,TS val 48719418 ecr 48713789], length 0
01:26:31.146179 IP 127.0.0.1.8080 > 127.0.0.1.47153: Flags [.], ack 2, win 342, options [nop,nop,TS val 48719419 ecr 48719418], length 0
這時候我們發現 127.0.0.1.8080(被動關閉的一端)收到FIN的包之後,回了ack但是並沒有傳送FIN包告訴主動關閉的一端可以關閉了,然後通過netstat看到狀態後發現,被動關閉的一端處於 close wait的狀態:
tcp 0 0 127.0.0.1:8080 127.0.0.1:47153 CLOSE_WAIT 39634/server
主動關閉的一端處於FIN_WAIT2的狀態
tcp 0 0 127.0.0.1:47153 127.0.0.1:8080 FIN_WAIT2 -
close wait的狀態代表,收到了對方的關閉請求,自己這邊沒有主動發FIN包的導致的。
time wait的狀態
我們把上述程式碼 defer conn.Close() 註釋掉,按上述流程抓到tcpdump的包為:
02:25:50.976745 IP 127.0.0.1.47161 > 127.0.0.1.8080: Flags [.], ack 1, win 342, options [nop,nop,TS val 52279249 ecr 52279249], length 0
02:25:54.424235 IP 127.0.0.1.47161 > 127.0.0.1.8080: Flags [F.], seq 1, ack 1, win 342, options [nop,nop,TS val 52282697 ecr 52279249], length 0
02:25:54.424466 IP 127.0.0.1.8080 > 127.0.0.1.47161: Flags [F.], seq 1, ack 2, win 342, options [nop,nop,TS val 52282697 ecr 52282697], length 0
02:25:54.424476 IP 127.0.0.1.47161 > 127.0.0.1.8080: Flags [.], ack 2, win 342, options [nop,nop,TS val 52282697 ecr 52282697], length 0
發現資料又完整的四次揮手的狀態,此時使用netstat 看下狀態發現發起關閉端處於time wait的狀態
tcp 0 0 127.0.0.1:47161 127.0.0.1:8080 TIME_WAIT -
這個time wait的狀態出現在發起關閉的一端,是為了防止對方可能因為未收到最後發出的ack包而重發fin,主動發起的一端好進行重發ack,而time wait的持續時長為2MSL。
為什麼需要四次揮手
這是由tcp的半關閉造成的,既然一個tcp連線時全雙工的(即資料在兩個方向上能同時傳遞),因此每個方向都必須單獨地進行關閉揮手過程中(被動關閉的一方的 fin跟ack是可以放到一起同時傳送的,也就是四次揮手流程中的第二次跟第三次的資料包可以合併為一個資料包)。需要三次握手同理。
TCP三次握手與四次揮手流程圖:
兩端同時開啟
兩個應用程式同時彼此執行主動開啟的情況是有可能的,儘管發生的可能性極小。例如,主機A中的一個應用程式使用本地埠7777,並於主機B的埠8888執行主動開啟。主機B中的應用程式則使用本地埠8888,並與主機A的埠7777執行主動開啟。TCP特意設計為了可以處理同時開啟,對於同時開啟它僅建立一條連線而不是兩條連線。連線流程圖如下:
兩端同時關閉
兩端同時關閉的流程圖如下:
相關文章
- TCP協議的3次握手與4次揮手過程詳解TCP協議
- tcp三次握手、四次揮手過程解析TCP
- TCP-三次握手和四次揮手簡單理解TCP
- 一看就懂的TCP握手和揮手TCP
- TCP的四次揮手過程TCP
- 簡述TCP三次握手和四次揮手TCP
- TCP的三次握手與四次揮手TCP
- 【計算機網路】TCP連線三次握手和四次揮手計算機網路TCP
- TCP 三次握手 與 四次揮手TCP
- TCP三次握手與四次揮手TCP
- 簡單說說TCP三次握手、四次揮手機制TCP
- Socket和TCP連線過程解析TCP
- TCP三次握手和四次揮手TCP
- 注意!是TCP不是HTTP的3次握手與4次揮手(#...#)TCPHTTP
- TCP的三次握手與四次揮手詳解TCP
- TCP的三次握手過程TCP
- TCP協議的三次握手和四次揮手TCP協議
- 說說TCP的三次握手和四次揮手TCP
- 圖解TCP的三次握手和四次揮手圖解TCP
- 簡單總結nodejs處理tcp連線的核心流程NodeJSTCP
- TCP協議中的三次握手與四次揮手TCP協議
- 通俗易懂的TCP“三次握手”與“四次揮手”TCP
- TCP三次握手和四次揮手理解TCP
- 看圖理解TCP的三次握手和四次揮手TCP
- 談談TCP協議的三次握手和四次揮手TCP協議
- 詼諧的談談TCP三次握手和四次揮手TCP
- TCP 三次握手四次揮手TCP
- TCP三次握手四次揮手TCP
- 複述 tcp 3次握手 4次揮手TCP
- TCP三次握手&四次揮手TCP
- 做個試驗:簡單的緩衝區溢位
- 在Linux中,三次握手和四次揮手的過程是什麼?Linux
- 計算機網路-tcp的三次握手與四次揮手計算機網路TCP
- TCP 三次握手原理以及半連線和全連線TCP
- TCP 、 UDP、三次握手、四次揮手TCPUDP
- 簡述Socket連線的過程
- 實戰:tcpdump抓包分析三次握手四次揮手TCP
- TCP協議特點和三次握手/四次揮手TCP協議