前文
go实验3net包阅读(listen/Dial)
环境
centos7
go1.18
1.Conn
从之前的Dial源码阅读知道,如果输入的是参数是tcp,最终生成的是TCPConn,结构体源码在这
//tcpsock.go
type TCPConn struct {
conn
}
//net.go
type conn struct {
fd *netFD
}
内嵌了一个conn类型,conn只有一个成员netFD他是网络文件描述符(看到fd就能猜到这大概就是一个已经连接好的套接字句柄)
conn实现了Conn接口的方法,Conn接口如下,该接口同时也实现了io.Writer/Reader/Closer接口
type Conn interface {
Read(b []byte) (n int, err error)
Write(b []byte) (n int, err error)
Close() error
LocalAddr() Addr
RemoteAddr() Addr
SetDeadline(t time.Time) error
SetReadDeadline(t time.Time) error
SetWriteDeadline(t time.Time) error
}
conn.Read方法从conn的网络文件中读取字节流写入[]byte对象中,同理Write方法和Close方法,实际上都是对fd文件的操作
func (c *conn) Read(b []byte) (int, error) {
if !c.ok() {
return 0, syscall.EINVAL
}
n, err := c.fd.Read(b)
if err != nil && err != io.EOF {
err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
}
return n, err
}
func (fd *netFD) Read(p []byte) (n int, err error) {
n, err = fd.pfd.Read(p)
runtime.KeepAlive(fd)
return n, wrapSyscallError(readSyscallName, err)
}
1.1 netFD
对Conn接口的操作就是对底层netFD的操作
// fd_posix.go
type netFD struct {
pfd poll.FD
// immutable until Close
family int
sotype int
isConnected bool // handshake completed or use of association with peer
net string
laddr Addr
raddr Addr
}
netFD包含一个pll.FD的数据结构,而poll.FD又包含了Sysfd(真正的系统文件描述符)和pollDesc(对底层事件驱动的封装)两个重要的数据结构
//internal/poll/fd_unix.go
// FD is a file descriptor. The net and os packages use this type as a
// field of a larger type representing a network connection or OS file.
type FD struct {
// Lock sysfd and serialize access to Read and Write methods.
fdmu fdMutex
// System file descriptor. Immutable until Close.
Sysfd int
// I/O poller.
pd pollDesc
// Writev cache.
iovecs *[]syscall.Iovec
// Semaphore signaled when file is closed.
csema uint32
// Non-zero if this file has been set to blocking mode.
isBlocking uint32
// Whether this is a streaming descriptor, as opposed to a
// packet-based descriptor like a UDP socket. Immutable.
IsStream bool
// Whether a zero byte read indicates EOF. This is false for a
// message based socket connection.
ZeroReadIsEOF bool
// Whether this is a file rather than a network socket.
isFile bool
}
type pollDesc struct {
runtimeCtx uintptr
}
这里pollDesc的 struct 只包含了一个指针,而通过 pollDesc 的 init 方法,我们可以找到它具体的定义是在runtime.pollDesc这里:
func (pd *pollDesc) init(fd *FD) error {
serverInit.Do(runtime_pollServerInit)
ctx, errno := runtime_pollOpen(uintptr(fd.Sysfd))
if errno != 0 {
if ctx != 0 {
runtime_pollUnblock(ctx)
runtime_pollClose(ctx)
}
return syscall.Errno(errno)
}
pd.runtimeCtx = ctx
return nil
}
// Network poller descriptor.
//
// No heap pointers.
//
//go:notinheap
type pollDesc struct {
link *pollDesc
objects.
lock mutex // protects the following fields
fd uintptr
closing bool
everr bool // marks event scanning error happened
user uint32 // user settable cookie
rseq uintptr // protects from stale read timers
rg uintptr // pdReady, pdWait, G waiting for read or nil
rt timer // read deadline timer (set if rt.f != nil)
rd int64 // read deadline
wseq uintptr // protects from stale write timers
wg uintptr // pdReady, pdWait, G waiting for write or nil
wt timer // write deadline timer
wd int64 // write deadline
}
可以看到pollDesc包含了自身类型的一个指针(说明了这是一个链表结构),所有的runtime.pollDesc保存在runtime.pollCache结构中
type pollCache struct {
lock mutex
first *pollDesc
}
实际上在前文lisnten函数中,在调用(favoriteAddrFamily调用)syscall.socket方法创建fd分配给listener并用来初始化listener的netFD,接着调用 netFD 的listenStream方法完成对 socket 的 bind&listen 操作以及对 netFD 的初始化(主要是对 netFD 里的 pollDesc 的初始化)
//sock_cloexec.go sysSocket s, err := socketFunc(family, sotype|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, proto)
// socketFunc 调用 syscall.Socket
// 在ListenConfig.Listen中初始化
看到这里我们也验证了上文的猜想,fd实际上就是创建的socket的文件句柄,再看看其他FD的方法源码也知道了底层实际上是进行系统调用
小结
所有的网络操作都以网络描述符 netFD 为中心实现。netFD 与底层 PollDesc 结构绑定,这里的netFD是netpoll的组件,后续学到多路复用或异步IO的时候再回头细看一遍吧
参考
1.go源码分析netpoll
2.socket编程
3.go socket编程