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

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

内容简介:欢迎访问我的个人网站获取更佳阅读排版本篇文章部分内容涉及到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操作都会返回错误。


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

查看所有标签

猜你喜欢:

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

Hit Refresh

Hit Refresh

Satya Nadella、Greg Shaw / HarperBusiness / 2017-9-26 / USD 20.37

Hit Refresh is about individual change, about the transformation happening inside of Microsoft and the technology that will soon impact all of our lives—the arrival of the most exciting and disruptive......一起来看看 《Hit Refresh》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

SHA 加密
SHA 加密

SHA 加密工具

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

RGB CMYK 互转工具