Go实验(4)net包阅读(Con结构体)

前文

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中初始化
image.png

看到这里我们也验证了上文的猜想,fd实际上就是创建的socket的文件句柄,再看看其他FD的方法源码也知道了底层实际上是进行系统调用

小结

所有的网络操作都以网络描述符 netFD 为中心实现。netFD 与底层 PollDesc 结构绑定,这里的netFD是netpoll的组件,后续学到多路复用或异步IO的时候再回头细看一遍吧

参考

1.go源码分析netpoll
2.socket编程
3.go socket编程

你可能感兴趣的:(Go实验(4)net包阅读(Con结构体))