事情由来
在golang最近经常写http或者grpc服务,带着这种习惯写了tcp服务,最后看了下代码,tcp连接没有调用fd.Close()方法,竟然没有报错。是测试童鞋没有测试出来,还是另外有玄机?
来个demo模拟
服务端代码,保存为server.go
package main
import (
"fmt"
"log"
"net"
)
func main() {
addr := "0.0.0.0:8787"
tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
log.Fatalf("net.ResovleTCPAddr fail:%s", addr)
}
listener, err := net.ListenTCP("tcp", tcpAddr)
if err != nil {
log.Fatalf("listen %s fail: %s", addr, err)
}
log.Println("listening", addr)
for {
conn, err := listener.Accept()
if err != nil {
log.Println("listener.Accept error:", err)
continue
}
fmt.Printf("%p\n", conn)
}
}
客户端代码,保存为client.go
package main
import (
"fmt"
"net"
"os"
)
func main() {
for i := 0; i < 1000; i++ {
conn, err := net.Dial("tcp", "127.0.0.1:8787")
if err != nil {
fmt.Println("dial failed:", err)
os.Exit(1)
}
conn.Close()
}
}
开始模拟
go run server.go
go run client.go
lsof -i:8787 #观察当前tcp状态,肯定是一堆close wait
# 等待1-2分钟
lsof -i:8787 #观察当前tcp状态,应该没有close wait
原因
通过参考https://studygolang.com/articles/7680 文章得知
,net/fd_unix.go文件里面setAddr调用了runtime.SetFinalizer
方法,该方法,就是在对象没有引用时,调用netFD传的回调函数(*netFD).Close
,现在明朗了。就是gc可以关闭tcp连接的
func (fd *netFD) setAddr(laddr, raddr Addr) {
fd.laddr = laddr
fd.raddr = raddr
runtime.SetFinalizer(fd, (*netFD).Close)
}