0)),它們都源于 TCP 的協議設計。">

国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

腳本之家,腳本語言編程技術及教程分享平臺!
分類導航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務器之家 - 腳本之家 - Golang - Go 中如何強制關閉 TCP 連接

Go 中如何強制關閉 TCP 連接

2021-09-27 00:49Golang技術分享機器鈴砍菜刀 Golang

本文我們介紹了 TCP 默認關閉與強制關閉兩種方式(其實還有種折中的方式:SetLinger(sec > 0)),它們都源于 TCP 的協議設計。

Go 中如何強制關閉 TCP 連接

在《Go 網絡編程和 TCP 抓包實操》一文中,我們編寫了 Go 版本的 TCP 服務器與客戶端代碼,并通過 tcpdump 工具進行抓包獲取分析。在該例中,客戶端代碼通過調用 Conn.Close() 方法發起了關閉 TCP 連接的請求,這是一種默認的關閉連接方式。

默認關閉需要四次揮手的確認過程,這是一種”商量“的方式,而 TCP 為我們提供了另外一種”強制“的關閉模式。

如何強制性關閉?具體在 Go 代碼中應當怎樣實現?這就是本文探討的內容。

默認關閉

相信每個程序員都知道 TCP 斷開連接的四次揮手過程,這是面試八股文中的股中股。我們在 Go 代碼中調用默認的 Conn.Close() 方法,它就是典型的四次揮手。

Go 中如何強制關閉 TCP 連接

以客戶端主動關閉連接為例,當它調用 Close 函數后,就會向服務端發送 FIN 報文,如果服務器的本端 socket 接收緩存區里已經沒有數據,那服務端的 read 將會得到一個 EOF 錯誤。

發起關閉方會經歷 FIN_WAIT_1 -> FIN_WAIT_2 -> TIME_WAIT -> CLOSE 的狀態變化,這些狀態需要得到被關閉方的反饋而更新。

強制關閉

默認的關閉方式,不管是客戶端還是服務端主動發起關閉,都要經過對方的應答,才能最終實現真正的關閉連接。那能不能在發起關閉時,不關心對方是否同意,就結束掉連接呢?

答案是肯定的。TCP 協議為我們提供了一個 RST 的標志位,當連接的一方認為該連接異常時,可以通過發送 RST 包并立即關閉該連接,而不用等待被關閉方的 ACK 確認。

SetLinger() 方法

在 Go 中,我們可以通過 net.TCPConn.SetLinger() 方法來實現。

  1. // SetLinger sets the behavior of Close on a connection which still 
  2. // has data waiting to be sent or to be acknowledged. 
  3. // 
  4. // If sec < 0 (the default), the operating system finishes sending the 
  5. // data in the background. 
  6. // 
  7. // If sec == 0, the operating system discards any unsent or 
  8. // unacknowledged data. 
  9. // 
  10. // If sec > 0, the data is sent in the background as with sec < 0. On 
  11. // some operating systems after sec seconds have elapsed any remaining 
  12. // unsent data may be discarded. 
  13. func (c *TCPConn) SetLinger(sec int) error {} 

函數的注釋已經非常清晰,但是需要讀者有 socket 緩沖區的概念。

  • socket 緩沖區

當應用層代碼通過 socket 進行讀與寫的操作時,實質上經過了一層 socket 緩沖區,它分為發送緩沖區和接受緩沖區。

緩沖區信息可通過執行 netstat -nt 命令查看

  1. $ netstat -nt 
  2. Active Internet connections 
  3. Proto Recv-Q Send-Q  Local Address          Foreign Address        (state) 
  4. tcp4       0      0  127.0.0.1.57721        127.0.0.1.49448        ESTABLISHED 

其中,Recv-Q 代表的就是接收緩沖區,Send-Q 代表的是發送緩沖區。

Go 中如何強制關閉 TCP 連接

默認關閉方式中,即 sec < 0 。操作系統會將緩沖區里未處理完的數據都完成處理,再關閉掉連接。

當 sec > 0 時,操作系統會以與默認關閉方式運行。但是當超過定義的時間 sec 后,如果還沒處理完緩存區的數據,在某些操作系統下,緩沖區中未完成的流量可能就會被丟棄。

而 sec == 0 時,操作系統會直接丟棄掉緩沖區里的流量數據,這就是強制性關閉。

示例代碼與抓包分析

我們通過示例代碼來學習 SetLinger() 的使用,并以此來分析強制關閉的區別。

服務端代碼

以服務端為主動關閉連接方示例

  1. package main 
  2.  
  3. import ( 
  4.  "log" 
  5.  "net" 
  6.  "time" 
  7.  
  8. func main() { 
  9.  // Part 1: create a listener 
  10.  l, err := net.Listen("tcp"":8000"
  11.  if err != nil { 
  12.   log.Fatalf("Error listener returned: %s", err) 
  13.  } 
  14.  defer l.Close() 
  15.  
  16.  for { 
  17.   // Part 2: accept new connection 
  18.   c, err := l.Accept() 
  19.   if err != nil { 
  20.    log.Fatalf("Error to accept new connection: %s", err) 
  21.   } 
  22.  
  23.   // Part 3: create a goroutine that reads and write back data 
  24.   go func() { 
  25.    log.Printf("TCP session open"
  26.    defer c.Close() 
  27.  
  28.    for { 
  29.     d := make([]byte, 100) 
  30.  
  31.     // Read from TCP buffer 
  32.     _, err := c.Read(d) 
  33.     if err != nil { 
  34.      log.Printf("Error reading TCP session: %s", err) 
  35.      break 
  36.     } 
  37.     log.Printf("reading data from client: %s\n", string(d)) 
  38.  
  39.     // write back data to TCP client 
  40.     _, err = c.Write(d) 
  41.     if err != nil { 
  42.      log.Printf("Error writing TCP session: %s", err) 
  43.      break 
  44.     } 
  45.    } 
  46.   }() 
  47.  
  48.   // Part 4: create a goroutine that closes TCP session after 10 seconds 
  49.   go func() { 
  50.    // SetLinger(0) to force close the connection 
  51.    err := c.(*net.TCPConn).SetLinger(0) 
  52.    if err != nil { 
  53.     log.Printf("Error when setting linger: %s", err) 
  54.    } 
  55.  
  56.    <-time.After(time.Duration(10) * time.Second
  57.    defer c.Close() 
  58.   }() 
  59.  } 

服務端代碼根據邏輯分為四個部分

第一部分:端口監聽。我們通過 net.Listen("tcp", ":8000")開啟在端口 8000 的 TCP 連接監聽。

第二部分:建立連接。在開啟監聽成功之后,調用 net.Listener.Accept()方法等待 TCP 連接。Accept 方法將以阻塞式地等待新的連接到達,并將該連接作為 net.Conn 接口類型返回。

第三部分:數據傳輸。當連接建立成功后,我們將啟動一個新的 goroutine 來處理 c 連接上的讀取和寫入。本文服務器的數據處理邏輯是,客戶端寫入該 TCP 連接的所有內容,服務器將原封不動地寫回相同的內容。

第四部分:強制關閉連接邏輯。啟動一個新的 goroutine,通過 c.(*net.TCPConn).SetLinger(0) 設置強制關閉選項,并于 10 s 后關閉連接。

客戶端代碼

以客戶端為被動關閉連接方示例

  1. package main 
  2.  
  3. import ( 
  4.  "log" 
  5.  "net" 
  6.  
  7. func main() { 
  8.  // Part 1: open a TCP session to server 
  9.  c, err := net.Dial("tcp""localhost:8000"
  10.  if err != nil { 
  11.   log.Fatalf("Error to open TCP connection: %s", err) 
  12.  } 
  13.  defer c.Close() 
  14.  
  15.  // Part2: write some data to server 
  16.  log.Printf("TCP session open"
  17.  b := []byte("Hi, gopher?"
  18.  _, err = c.Write(b) 
  19.  if err != nil { 
  20.   log.Fatalf("Error writing TCP session: %s", err) 
  21.  } 
  22.  
  23.  // Part3: read any responses until get an error 
  24.  for { 
  25.   d := make([]byte, 100) 
  26.   _, err := c.Read(d) 
  27.   if err != nil { 
  28.    log.Fatalf("Error reading TCP session: %s", err) 
  29.   } 
  30.   log.Printf("reading data from server: %s\n", string(d)) 
  31.  } 

客戶端代碼根據邏輯分為三個部分

第一部分:建立連接。我們通過 net.Dial("tcp", "localhost:8000")連接一個 TCP 連接到服務器正在監聽的同一個 localhost:8000 地址。

第二部分:寫入數據。當連接建立成功后,通過 c.Write() 方法寫入數據 Hi, gopher? 給服務器。

第三部分:讀取數據。除非發生 error,否則客戶端通過 c.Read() 方法(記住,是阻塞式的)循環讀取 TCP 連接上的內容。

tcpdump 抓包結果

tcpdump 是一個非常好用的數據抓包工具,在《Go 網絡編程和 TCP 抓包實操》一文中已經簡單介紹了它的命令選項,這里就不再贅述。

  • 開啟 tcpdump 數據包監聽
  1. tcpdump -S -nn -vvv -i lo0 port 8000 
  • 運行服務端代碼
  1. $ go run main.go 
  2. 2021/09/25 20:21:44 TCP session open 
  3. 2021/09/25 20:21:44 reading data from client: Hi, gopher? 
  4. 2021/09/25 20:21:54 Error reading TCP session: read tcp 127.0.0.1:8000->127.0.0.1:59394: use of closed network connection 

服務器和客戶端建立連接之后,從客戶端讀取到數據 Hi, gopher? 。在 10s 后,服務端強制關閉了 TCP 連接,阻塞在 c.Read 的服務端代碼返回了錯誤: use of closed network connection。

  • 運行客戶端代碼
  1. $ go run main.go 
  2. 2021/09/25 20:21:44 TCP session open 
  3. 2021/09/25 20:21:44 reading data from server: Hi, gopher? 
  4. 2021/09/25 20:21:54 Error reading TCP session: read tcp 127.0.0.1:59394->127.0.0.1:8000: readconnection reset by peer 

客戶端和服務器建立連接之后,發送數據給服務端,服務端返回相同的數據 Hi, gopher? 回來。在 10s 后,由于服務器強制關閉了 TCP 連接,因此阻塞在 c.Read 的客戶端代碼捕獲到了錯誤:connection reset by peer。

  • tcpdump 的抓包結果
  1. $ tcpdump -S -nn -vvv -i lo0 port 8000 
  2. tcpdump: listening on lo0, link-type NULL (BSD loopback), capture size 262144 bytes 
  3. 20:21:44.682942 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 64, bad cksum 0 (->3cb6)!) 
  4.     127.0.0.1.59394 > 127.0.0.1.8000: Flags [S], cksum 0xfe34 (incorrect -> 0xfa62), seq 3783365585, win 65535, options [mss 16344,nop,wscale 6,nop,nop,TS val 725769370 ecr 0,sackOK,eol], length 0 
  5. 20:21:44.683042 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 64, bad cksum 0 (->3cb6)!) 
  6.     127.0.0.1.8000 > 127.0.0.1.59394: Flags [S.], cksum 0xfe34 (incorrect -> 0x23d3), seq 1050611715, ack 3783365586, win 65535, options [mss 16344,nop,wscale 6,nop,nop,TS val 725769370 ecr 725769370,sackOK,eol], length 0 
  7. 20:21:44.683050 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 52, bad cksum 0 (->3cc2)!) 
  8.     127.0.0.1.59394 > 127.0.0.1.8000: Flags [.], cksum 0xfe28 (incorrect -> 0x84dc), seq 3783365586, ack 1050611716, win 6379, options [nop,nop,TS val 725769370 ecr 725769370], length 0 
  9. 20:21:44.683055 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 52, bad cksum 0 (->3cc2)!) 
  10.     127.0.0.1.8000 > 127.0.0.1.59394: Flags [.], cksum 0xfe28 (incorrect -> 0x84dc), seq 1050611716, ack 3783365586, win 6379, options [nop,nop,TS val 725769370 ecr 725769370], length 0 
  11. 20:21:44.683302 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 63, bad cksum 0 (->3cb7)!) 
  12.     127.0.0.1.59394 > 127.0.0.1.8000: Flags [P.], cksum 0xfe33 (incorrect -> 0x93f5), seq 3783365586:3783365597, ack 1050611716, win 6379, options [nop,nop,TS val 725769370 ecr 725769370], length 11 
  13. 20:21:44.683311 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 52, bad cksum 0 (->3cc2)!) 
  14.     127.0.0.1.8000 > 127.0.0.1.59394: Flags [.], cksum 0xfe28 (incorrect -> 0x84d1), seq 1050611716, ack 3783365597, win 6379, options [nop,nop,TS val 725769370 ecr 725769370], length 0 
  15. 20:21:44.683499 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 152, bad cksum 0 (->3c5e)!) 
  16.     127.0.0.1.8000 > 127.0.0.1.59394: Flags [P.], cksum 0xfe8c (incorrect -> 0x9391), seq 1050611716:1050611816, ack 3783365597, win 6379, options [nop,nop,TS val 725769370 ecr 725769370], length 100 
  17. 20:21:44.683511 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 52, bad cksum 0 (->3cc2)!) 
  18.     127.0.0.1.59394 > 127.0.0.1.8000: Flags [.], cksum 0xfe28 (incorrect -> 0x846e), seq 3783365597, ack 1050611816, win 6378, options [nop,nop,TS val 725769370 ecr 725769370], length 0 
  19. 20:21:54.688350 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40, bad cksum 0 (->3cce)!) 
  20.     127.0.0.1.8000 > 127.0.0.1.59394: Flags [R.], cksum 0xfe1c (incorrect -> 0xcd39), seq 1050611816, ack 3783365597, win 6379, length 0 

我們重點關注內容 Flags [],其中 [S] 代表 SYN 包,用于建立連接;[P] 代表 PSH 包,表示有數據傳輸;[R]代表 RST 包,用于重置連接;[.] 代表對應的 ACK 包。例如 [S.] 代表 SYN-ACK。

搞懂了這幾個 Flags 的含義,那我們就可以分析出本次服務端強制關閉的 TCP 通信全過程。

Go 中如何強制關閉 TCP 連接

我們和《Go 網絡編程和 TCP 抓包實操》一文中,客戶端正常關閉的通信過程進行比較

Go 中如何強制關閉 TCP 連接

可以看到,當通過設定 SetLinger(0) 之后,主動關閉方調用 Close() 時,系統內核會直接發送 RST 包給被動關閉方。這個過程并不需要被動關閉方的回復,就已關閉了連接。主動關閉方也就沒有了默認關閉模式下 FIN_WAIT_1 -> FIN_WAIT_2 -> TIME_WAIT -> CLOSE 的狀態改變。

總結

本文我們介紹了 TCP 默認關閉與強制關閉兩種方式(其實還有種折中的方式:SetLinger(sec > 0)),它們都源于 TCP 的協議設計。

在大多數的場景中,我們都應該選擇使用默認關閉方式,因為這樣才能確保數據的完整性(不會丟失 socket 緩沖區里的數據)。

當使用默認方式關閉時,每個連接都會經歷一系列的連接狀態轉變,讓其在操作系統上停留一段時間。尤其是服務器要主動關閉連接時(大多數應用場景,都應該是由客戶端主動發起關閉操作),這會消耗服務器的資源。

如果短時間內有大量的或者惡意的連接涌入,我們或許需要采用強制關閉方式。因為使用強制關閉,能立即關閉這些連接,釋放資源,保證服務器的可用與性能。

當然,我們還可以選擇折中的方式,容忍一段時間的緩存區數據處理時間,再進行關閉操作。

這里給讀者朋友留一個思考題。如果在本文示例中,我們將 SetLinger(0) 改為 SetLinger(1) ,抓包結果又會是如何?

最后,讀者朋友們在項目中,有使用過強制關閉方式嗎?

原文鏈接:https://mp.weixin.qq.com/s/Lbv4myGcvH6fIfNoQfyqQA

延伸 · 閱讀

精彩推薦
  • GolangGolang通脈之數據類型詳情

    Golang通脈之數據類型詳情

    這篇文章主要介紹了Golang通脈之數據類型,在編程語言中標識符就是定義的具有某種意義的詞,比如變量名、常量名、函數名等等,Go語言中標識符允許由...

    4272021-11-24
  • GolangGolang中Bit數組的實現方式

    Golang中Bit數組的實現方式

    這篇文章主要介紹了Golang中Bit數組的實現方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    天易獨尊11682021-06-09
  • Golanggo語言制作端口掃描器

    go語言制作端口掃描器

    本文給大家分享的是使用go語言編寫的TCP端口掃描器,可以選擇IP范圍,掃描的端口,以及多線程,有需要的小伙伴可以參考下。 ...

    腳本之家3642020-04-25
  • Golanggolang json.Marshal 特殊html字符被轉義的解決方法

    golang json.Marshal 特殊html字符被轉義的解決方法

    今天小編就為大家分享一篇golang json.Marshal 特殊html字符被轉義的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧 ...

    李浩的life12792020-05-27
  • Golanggolang如何使用struct的tag屬性的詳細介紹

    golang如何使用struct的tag屬性的詳細介紹

    這篇文章主要介紹了golang如何使用struct的tag屬性的詳細介紹,從例子說起,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看...

    Go語言中文網11352020-05-21
  • Golanggolang的httpserver優雅重啟方法詳解

    golang的httpserver優雅重啟方法詳解

    這篇文章主要給大家介紹了關于golang的httpserver優雅重啟的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,...

    helight2992020-05-14
  • Golanggo日志系統logrus顯示文件和行號的操作

    go日志系統logrus顯示文件和行號的操作

    這篇文章主要介紹了go日志系統logrus顯示文件和行號的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    SmallQinYan12302021-02-02
  • Golanggolang 通過ssh代理連接mysql的操作

    golang 通過ssh代理連接mysql的操作

    這篇文章主要介紹了golang 通過ssh代理連接mysql的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    a165861639710342021-03-08
主站蜘蛛池模板: 日韩在线 中文字幕 | 精品国产欧美 | 国产一区二区三区免费看 | 日韩一区精品 | 日韩一二三区视频 | 欧美1区2区3区 | 久久久久久久国产 | 玖玖精品| 亚洲精品视频在线免费播放 | 久久久天堂 | 国产精品亚洲第一区在线暖暖韩国 | 国产日韩视频 | 午夜影院在线 | 久草成人网 | 亚洲精选国产 | 国产高清在线精品一区二区三区 | 免费av在线网站 | 免费看黄a | 日韩国产精品一区二区三区 | 日韩欧美网站 | 中文色视频 | 中文字幕视频在线 | 毛片高清| 中文在线一区二区 | 日韩电影免费观看 | 欧美成人h版在线观看 | 国产精品亚洲综合 | 精品国精品国产自在久不卡 | 成人在线免费观看 | 国产区在线| 99热这里有精品 | 亚洲国产成人av好男人在线观看 | 亚洲视频免费在线观看 | 欧美色综合天天久久综合精品 | 自拍第一页 | 亚洲一区二区视频 | 国产大片在线观看 | 黄色成人在线观看视频 | 久久中文在线观看 | 在线视频一区二区三区 | 亚洲中午字幕 |