golang 网络编程之如何正确关闭tcp连接以及管理它的生命周期

栏目: Go · 发布时间: 5年前

内容简介:欢迎访问我的个人网站获取更佳阅读排版本篇文章部分内容涉及到tcp协议以及socket编程的通用底层知识。讨论的tcp连接对象皆为golang的net.conn对象。如果存在错误,请一定指正,谢谢。个人认为正确、简单、语义清晰、高效的做法:应该在Read或Write返回错误后调用Close。不论是主动关闭还是被动关闭,调用Close后,不应该再Read或Write,并尽快释放net.conn对象。

欢迎访问我的个人网站获取更佳阅读排版 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操作都会返回错误。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

剑指Offer:名企面试官精讲典型编程题(第2版)

剑指Offer:名企面试官精讲典型编程题(第2版)

何海涛 / 电子工业出版社 / 2017-5 / 65.00

《剑指Offer:名企面试官精讲典型编程题(第2版)》剖析了80个典型的编程面试题,系统整理基础知识、代码质量、解题思路、优化效率和综合能力这5个面试要点。《剑指Offer:名企面试官精讲典型编程题(第2版)》共分7章,主要包括面试的流程,讨论面试每一环节需要注意的问题;面试需要的基础知识,从编程语言、数据结构及算法三方面总结程序员面试知识点;高质量的代码,讨论影响代码质量的3个要素(规范性、完整......一起来看看 《剑指Offer:名企面试官精讲典型编程题(第2版)》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具