go源码解析之TCP连接系列基于go源码1.16.5

连接关闭

上一章我们通过跟踪TCPConn的Write方法,了解了发送数据的过程以及fd的读写锁,本章将通过TCPConn的Close方法的跟踪来了解连接关闭的过程。

1. conn的Close方法

从上一章了解到TCPConn继承自conn,它的Close方法就是conn的Close,代码如下:

src/net/net.go

1
2
3
4
5
6
func (c *conn) Close() error {
	...
	err := c.fd.Close()
	...
	return err
}

同样conn的Close调用了netFD的Close方法:

src/net/fd_posix.go

1
2
3
4
func (fd *netFD) Close() error {
	runtime.SetFinalizer(fd, nil)
	return fd.pfd.Close()
}

2. 回顾SetFinalizer

回顾一下SetFinalizer,它设置参数一被回收时需要执行的清理方法,Close这里将fd的清理方法删除,我们再回顾一下fd的清理方法是在什么时候被设置的:

src/net/fd_posix.go

1
2
3
4
5
func (fd *netFD) setAddr(laddr, raddr Addr) {
	fd.laddr = laddr
	fd.raddr = raddr
	runtime.SetFinalizer(fd, (*netFD).Close)
}

src/net/fd_unix.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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
}
  1. increfAndClose方法将fd设置为关闭状态并唤醒其他所有等待锁的协程,在上一章有讲解。
  2. evict方法将阻塞等待io消息的协程唤醒,这块将在后续go语言实现io多路复用的章节详细讲解。
  3. decref与incref成对出现,如果fd的引用数量为0,将执行destory调用系统方法关闭fd
  4. runtime_Semacquire,如果fd是非阻塞模式,则需要请求信号量csema,等待destory方法完成并发送信号量后,再被唤醒

destory方法:

src/internal/poll/fd_unix.go

1
2
3
4
5
6
7
8
9
func (fd *FD) destroy() error {
	...

	err := CloseFunc(fd.Sysfd)

	fd.Sysfd = -1
	runtime_Semrelease(&fd.csema)
	return err
}
  1. CloseFunc进行系统调用关闭fd
  2. runtime_Semrelease释放信号量csema

4. 小结

关闭方法不会第一时间调用系统方法销毁系统fd,系统fd的销毁需要在不再有引用时执行。