golang 網絡編程之如何正確關閉tcp連接以及管理它的生命周期


歡迎訪問我的個人網站獲取更佳閱讀排版 golang 網絡編程之如何正確關閉tcp連接以及管理它的生命周期 | yoko blog (https://pengrl.com/p/47401/)

本篇文章部分內容涉及到tcp協議以及socket編程的通用底層知識。討論的tcp連接對象皆為golang的net.conn對象。如果存在錯誤,請一定指正,謝謝。

先上結論

  1. Read方法返回EOF錯誤,表示本端感知到對端已經關閉連接(本端已接收到對端發送的FIN)。此后如果本端不調用Close方法,只釋放本端的連接對象,則連接處於非完全關閉狀態(CLOSE_WAIT)。即文件描述符發生泄漏。
  2. Write方法返回broken pipe錯誤,表示本端感知到對端已經關閉連接(本端已接收到對端發送的RST)。此后本端可不調用Close方法。連接處於完全關閉狀態。
  3. 由於golang里net.conn內部對文件描述符的所有io操作都有狀態保護,所以即使在對端或本端關閉了連接之后,依然可以任意次數調用Read、Write、Close方法。

個人認為正確、簡單、語義清晰、高效的做法:應該在Read或Write返回錯誤后調用Close。不論是主動關閉還是被動關閉,調用Close后,不應該再Read或Write,並盡快釋放net.conn對象。

部分demo測試與分析

我的測試環境: go version go1.11.4 darwin/amd64

第三方工具: netstatwireshark

驗證結論一

假設我們有兩個demo程序——server和client。

client主動連接上server后不做任何操作,直接關閉net.conn對象。用於模擬主動關閉端。偽代碼如下:

conn, _ := net.Dial("tcp", "127.0.0.1:8081")
conn.Close()

server在accept新連接后,在新連接的處理函數中調用Read方法,Read返回io.EOF后不調用Close方法,直接退出處理函數,釋放連接對象。偽代碼如下:

func handleConn(conn net.Conn) {
    buf := make([]byte, 1024)
    n, err := conn.Read(buf)
    log.Println(n, err)
    //conn.Close()
}

啟動server后,再啟動client,server打印出0 EOF

用netstat查看連接情況:

# 在shell中輸入
$netstat -an | grep 8081

tcp4       0      0  127.0.0.1.8081         127.0.0.1.62871        CLOSE_WAIT
tcp4       0      0  127.0.0.1.62871        127.0.0.1.8081         FIN_WAIT_2
tcp46      0      0  *.8081                 *.*                    LISTEN

client處於FIN_WAIT_2狀態,說明client發送了FIN,並收到了對應的ACK。

server處於CLOSE_WAIT狀態,說明server收到了FIN,並發送了對應的ACK。

用wireshark抓包:

再測試一遍,發現client發送了FIN,server回復了對應的ACK。但是server並沒有發送FIN。與netstat顯示的狀態相符合。

修改server代碼,在Read返回EOF后,調用conn.Close()

重新測試,再使用netstat和wireshark分析,發現server也發送了FIN,兩端都正常關閉。

驗證結論二

修改server代碼。偽代碼如下:

	buf := make([]byte, 1024)
	time.Sleep(5 * time.Second)
	n, err := conn.Write(buf)
	log.Println(n, err)
	time.Sleep(5 * time.Second)
	n, err = conn.Write(buf)
	log.Println(n, err)

server輸出如下:

2019/04/17 16:08:17 1024 <nil>
2019/04/17 16:08:18 0 write tcp 127.0.0.1:8081->127.0.0.1:64124: write: broken pipe

server的第一次Sleep 5秒是為了確保在第一次Write之前client已關閉連接。

用netstat觀察:

我們發現在5秒內,server處於CLOSE_WAIT狀態,client處於FIN_WAIT_2狀態。

5秒之后,兩端都進入完全關閉狀態。

用wireshark抓包:

發現5秒后,server向client發送第一次1024字節數據后,client向server回復了RST包。

10秒后,server並不會再發送第二次的1024字節數據。

server的第二次Sleep 5秒是為了確保在第一次Write之后,server接收到了RST包。如果去掉第二次的Sleep,可能出現server連續發送兩次數據給client,client回復兩次RST給server。

驗證結論三

場景一

對端關閉后,本端一直Read,則一直得到EOF錯誤。

這是由於系統調用Read會一直返回0。

場景二

對端關閉后,本端一直Write,則一直得到如下錯誤:

write tcp 127.0.0.1:8081->127.0.0.1:63520: write: broken pipe

這是由於系統調用Write會一直返回EPIPE。

場景三

本端關閉后,本端繼續調用Read或Write或Close,則一直得到如下錯誤:

127.0.0.1:63482->127.0.0.1:8081: use of closed network connection
127.0.0.1:63448->127.0.0.1:8081: use of closed network connection

這是由fd_mutex.go中的mutexClosed標志決定的,當文件描述符被關閉后,該標志會被設置,之后所有io操作都會返回錯誤。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM