go源码解析之TCP连接系列基于go源码1.16.5
连接关闭
上一章我们通过跟踪TCPConn的Write方法,了解了发送数据的过程以及fd的读写锁,本章将通过TCPConn的Close方法的跟踪来了解连接关闭的过程。
1. conn的Close方法
从上一章了解到TCPConn继承自conn,它的Close方法就是conn的Close,代码如下:
src/net/net.go
func (c *conn) Close() error {
...
err := c.fd.Close()
...
return err
}
同样conn的Close调用了netFD的Close方法:
src/net/fd_posix.go
func (fd *netFD) Close() error {
runtime.SetFinalizer(fd, nil)
return fd.pfd.Close()
}
2. 回顾SetFinalizer
回顾一下SetFinalizer,它设置参数一被回收时需要执行的清理方法,Close这里将fd的清理方法删除,我们再回顾一下fd的清理方法是在什么时候被设置的:
src/net/fd_posix.go
func (fd *netFD) setAddr(laddr, raddr Addr) {
fd.laddr = laddr
fd.raddr = raddr
runtime.SetFinalizer(fd, (*netFD).Close)
}
src/net/fd_unix.go
func (fd *netFD) accept() (netfd *netFD, err error) {
d, rsa, errcall, err := fd.pfd.Accept()
...
if netfd, err = newFD(d, fd.family, fd.sotype, fd.net); err != nil {
poll.CloseFunc(d)
return nil, err
}
if err = netfd.init(); err != nil {
netfd.Close()
return nil, err
}
lsa, _ := syscall.Getsockname(netfd.pfd.Sysfd)
netfd.setAddr(netfd.addrFunc()(lsa), netfd.addrFunc()(rsa))
return netfd, nil
}
可以看到在初始化netFD之后调用的setAddr方法中设置了清理方法。清理方法指定为netFD的Close。在Close方法中将清理方法删除,防止netFD被回收时重复执行Close。
3. poll.FD的Close方法
src/internal/poll/fd_unix.go
func (fd *FD) Close() error {
if !fd.fdmu.increfAndClose() {
return errClosing(fd.isFile)
}
fd.pd.evict()
err := fd.decref()
if fd.isBlocking == 0 {
runtime_Semacquire(&fd.csema)
}
return err
}
- increfAndClose方法将fd设置为关闭状态并唤醒其他所有等待锁的协程,在上一章有讲解。
- evict方法将阻塞等待io消息的协程唤醒,这块将在后续go语言实现io多路复用的章节详细讲解。
- decref与incref成对出现,如果fd的引用数量为0,将执行destory调用系统方法关闭fd
- runtime_Semacquire,如果fd是非阻塞模式,则需要请求信号量csema,等待destory方法完成并发送信号量后,再被唤醒
destory方法:
src/internal/poll/fd_unix.go
func (fd *FD) destroy() error {
...
err := CloseFunc(fd.Sysfd)
fd.Sysfd = -1
runtime_Semrelease(&fd.csema)
return err
}
- CloseFunc进行系统调用关闭fd
- runtime_Semrelease释放信号量csema
4. 小结
关闭方法不会第一时间调用系统方法销毁系统fd,系统fd的销毁需要在不再有引用时执行。