在底层使用操作系统的多路复用IO,在协程使用阻塞模型。
epoll抽象层用于抽象linux,windows,mac下的网络多路复用。
netpollinit()->epoll_create()。新建epoll,拿到文件描述符,新建一个pipe用于中断epoll,有管道数据到达事件注册到epoll中。
epoll_ctl()->netpollopen()(监听事件)。传入socket的fd, pollDesc指针,pollDesc指针是socket相关详细信息,pollDesc指针记录了哪个 协程休眠等待这个socket,将socket可读可写断开事件注册到epoll中
epoll_wait()->netpoll()。(查询哪些事件发生),根据socket相关的pollDesc信息返回哪些协程可以唤醒的列表
go network Poller用于多路复用的抽象和适配。更上一层。
poll_runtime_pollServerInit(),调用netpollinit()但使用原子操作保证只初始化一次。
pollCache struct {}带锁的链表头
PollDesc struct {}链表成员,是Socket的详细描述(rg,wg 等待协程的地址)
poll_runtime_pollOpen()在pollCache链表中分配一个pollDesc,初始化pollDesc(用于监听Socket),调用netpollopen()。
gcStart调用netpoll()。因为垃圾回收循环查询(G0协程),于是在这放了个netpoll()钩子可以循环调用。场景1:发现Socket可以读写,则对应的rg 或者wg设置为 1。协程调用poll_runtime_pollWait()判断rg 或 wg是否为1.
场景2:协程调用poll_runtime_pollWait()后发现g 或 wg为0则给对应的g 或 wg设置为协程地址,休眠等待。当netpoll发现socket可读写时,查看对应的g 或 wg,若为协程地址则返回协程地址,调度器开始调度这个协程。
GO是如何抽象socket?是net包
net包实现TCP UDP HTTP。
net.listen()新建Socket,并执行bind操作。新建一个FD(net包对socket的描述)返回TCP listener对象。将TCP listener的FD加入监听。TCP listener本质是一个listen状态的Socket。所以这方法我们完成下图第一步新建socket并加入监听:
TCP Listener.Accept()直接调用Socket的accept(),如果失败则休眠等待。成功,将生成新的socket包装成TCPConn变量返回,将TCPConn的FD信息加入监听。TCPConn本质上是一个ESTABLISHED状态的Socket。至此完成第二步
TCPConn.Read()/Write()直接调用Socket原生读写方法,如果失败,休眠等待可读可写,被唤醒后调用系统Socket。
net包是直接操作socket的如果操作不了,则把自己记录在pollDesc中。
go-per-connection编程风格:一个协程一个连接。主协程监听listener,每个conn使用一个新协程处理。