go 网络 network poller

这篇具有很好参考价值的文章主要介绍了go 网络 network poller。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

网络基础

协议架构

go 网络 network poller

tcp链接

go 网络 network poller

假如需要开发者去实现一套新的网络协议(例如 redis 的resp), 是基于TCP的, 那tcp这层的协议,是否需要开发者自己去实现?

这层如果自己实现, 其实很复杂, 会涉及很多算法相关.

因此, 出现了 socket 对传输层进行了抽象, 开发者不需要关注传输层具体的实现, 使用socket提供的接口, socket内部会实现,比如三次握手, 四次挥手.

Socket

  很多系统都提供 Socket 作为 TCP(也有UDP) 网络连接的抽象,

  Linux-> Internet domain socket -> SOCK STREAM

  Linux 中 Socket 以 “文件描述符〞FD 作为标识
go 网络 network poller

每建立一次连接接, sever都会创建一个新的 socket 专门和client通信, 原来监听的socket还是一直处于监听状态.

go 网络 network poller

socket 之前如何进行通信, 假设client 给 server发送消息, sever 没有回复, 那client是阻塞, 还是先执行别的工作, 这就是IO模型的范畴了.

IO模型

阻塞

非阻塞

多路复用

阻塞

go 网络 network poller
当read中没有数据过来, 线程(sever或者client都一样)会阻塞等待.

同步读写Socket时,线程陷入内核态

当读写成功后,切换回用户态,继续执行

优点:开发难度小,代码简单

缺点:内核态切换开销大

非阻塞

go 网络 network poller
如果暂时无法收发数据,会返回错误

应用会不断轮询,直到Socket可以读写

优点:不会陷入内核态,自由度高

缺点:需要自旋轮询

多路复用

go 网络 network poller

大厂面试经常喜欢问的 epoll就是 多路复用的一种实现, 还有 select, poll 都是, 这三个主要的区别: 在返回数据时候的读取方式.

业务把关注的事件注册到 event poll池中, 当epoll接受到对应事件后,通知业务.

注册多个Socket事件

调用epool,当有事件发生,返回

优点:提供了事件列表,不需要查询轮询各个Scoket

缺点:开发难度大,逻辑复杂

epoll 在 Mac: kqueue ; Windows: IOCP 

小结

操作系统提供了Socket作为TCP和UDP通信的抽象

IO模型指的是操作Socket的方案

阻塞模型最利于业务编写,但是性能差

多路复用性能好,但业务编写麻烦

go socket的实现

实现方法

go 网络 network poller
  1. 对系统的 epoll进行封装, 因为go是可以在多个系统上运行的, 底层需要对平台差异化封装.
  1. 如果用户去调用 封装好的 epoll方法, 需要开发者执行去关注 epoll的状态, 了解epoll的大致实现原理,肯定知道还需要去 解析epoll的返回, 然后 分析那些 socket 有事件来了. 这些工作都是通用性的,所以,go把这层一起封装在了它的 网络层中.
  1. 然后,go为了更加简化使用, 采用了 阻塞的思想, 一个协程对应一个 socket连接 , 当epoll 没有对应事件返回时候, 就休眠等待, 有事件来了,就唤醒处理. 这样就简化了上层开发者的使用.
go 网络 network poller

在底层使用操作系统的多路复用10

在协程层次使用阻塞模型

阻塞协程时,休眠协程

具体抽象 go代码相关

多路复用器

各个系统的多路复用都有以下功能: 系统原生

1. 新建多路复用器 epoll create()
2. 往多路复用器里插入需要监听的事件 epoll ctl()
3. 查询发生了什么事件 epoll wait()

Go Network Poller 多路复用器的抽象

epoll create() -> netpollinit()  // 新建

epoll cti0 -> netpollopen()   // 插入事件

epoll wait -> netpoll()        // 查询

netpollinit 新建多路复用器

func netpollinit() {
	var errno uintptr
    // 新建了 epoll 系统底层实现 
	epfd, errno = syscall.EpollCreate1(syscall.EPOLL_CLOEXEC)
	 // 这里的 epfd是个全局变量,初始化之后, 在操作时候就用这个 epfd

    // 新建管道 Linux的管道,用于多线程多进程间的通信, 用于结束这个epoll
	r, w, errpipe := nonblockingPipe()
	
	ev := syscall.EpollEvent{
		Events: syscall.EPOLLIN,
	}

	*(**uintptr)(unsafe.Pointer(&ev.Data)) = &netpollBreakRd
     // 管道中加入了事件 
	errno = syscall.EpollCtl(epfd, syscall.EPOLL_CTL_ADD, r, &ev)
	
	netpollBreakRd = uintptr(r)
	netpollBreakWr = uintptr(w)
}

归纳:

新建了一个底层 epoll对象  epfd

新建一个pipe管道用于中断Epoll

将“管道有数据到达〞 事件注册在Epoll中

netpollopen() 插入事件

func netpollopen(fd uintptr, pd *pollDesc) uintptr {
    // fd 是socket的对象  pd是 socket 和协程的对应关系

	var ev syscall.EpollEvent
	ev.Events = syscall.EPOLLIN | syscall.EPOLLOUT | syscall.EPOLLRDHUP | syscall.EPOLLET

	tp := taggedPointerPack(unsafe.Pointer(pd), pd.fdseq.Load())
	*(*taggedPointer)(unsafe.Pointer(&ev.Data)) = tp
    //  上面这两行代码 是在把 ev.Data 和  pd 联系起来,  后面epoll 返回时候, 就能直接和pd的协程联系起来

    // 将fd要关注的事件, 注册到 epoll中, 调用的是 EpollCtl 和上面也对上了
	return syscall.EpollCtl(epfd, syscall.EPOLL_CTL_ADD, int32(fd), &ev)
}


type pollDesc struct {
	_     sys.NotInHeap
	link  *pollDesc      // in pollcache, protected by pollcache.lock
	fd    uintptr        //  socket ID
	fdseq atomic.Uintptr // protects against stale pollDesc

	atomicInfo atomic.Uint32 // atomic pollInfo
    // 想要读的协程
	rg atomic.Uintptr // pdReady, pdWait, G waiting for read or pdNil
    // 想要写的协程
	wg atomic.Uintptr // pdReady, pdWait, G waiting for write or pdNil

	lock    mutex // protects the following fields
	closing bool
	user    uint32    // user settable cookie
	rseq    uintptr   // protects from stale read timers
	rt      timer     // read deadline timer (set if rt.f != nil)
	rd      int64     // read deadline (a nanotime in the future, -1 when expired)
	wseq    uintptr   // protects from stale write timers
	wt      timer     // write deadline timer
	wd      int64     // write deadline (a nanotime in the future, -1 when expired)
	self    *pollDesc // storage for indirect interface. See (*pollDesc).makeArg.
}

归纳:

传入一个Socket的FD,和pollDesc指针

pollDesc指针是Socket相关详细信息

pollIDesc中记录了哪个协程休眠在等待此Socket

将Socket可读、可写、断开事件注册到Epoll中

netpoll 查询

这里有必要讲下 epoll的使用的一些特点, 能更好理解go中的实现.

epoll_wait 是阻塞函数, 但是可以传一个 超时时间, 超过时间后就先返回, 不阻塞了。

int epoll_wait(int epfd,struct epoll_event * events, int maxevents,int timeout)

epfd epoll的id ,就是创建时候的 epfd.
epoll_event 是传回有事件的fd和even的记录
timeout 是 超时时间
返回值 是指示 epoll_event 中,前多个元素,是有值的, 避免遍历.

// 超时时间 delay
 func netpoll(delay int64) gList {

	var events [128]syscall.EpollEvent
retry:
  // 去调了系统的 EpollWait 然后 看参数, 第二个参数里面是包含了 有事件的even
	n, errno := syscall.EpollWait(epfd, events[:], int32(len(events)), waitms)
  // n 就表示 events 前多少个是有值的
	var toRun gList
	for i := int32(0); i < n; i++ {
	        var mode int32
		if ev.Events&(syscall.EPOLLIN|syscall.EPOLLRDHUP|syscall.EPOLLHUP|syscall.EPOLLERR) != 0 {
			mode += 'r'
		}
		if ev.Events&(syscall.EPOLLOUT|syscall.EPOLLHUP|syscall.EPOLLERR) != 0 {
			mode += 'w'
		}
		if mode != 0 {
			tp := *(*taggedPointer)(unsafe.Pointer(&ev.Data))
			pd := (*pollDesc)(tp.pointer())
			tag := tp.tag()
          // 通过ev.Data  和 pd关联了起来  参入事件的时候, 有讲过这个
			if pd.fdseq.Load() == tag {
				pd.setEventErr(ev.Events == syscall.EPOLLERR, tag)
				netpollready(&toRun, pd, mode) // 通过pd ,获取到了对应的 协程队列
			}
          }
	}
  return toRun // 返回的是 关注这个事件的协程队列
}

归纳:

调用epoll wait(),查询有哪些事件发生

根据Socket相关的pollDesc信息,返回哪些协程可以唤醒

Go Network Poller 如何工作

收发数据

场景 1:Socket 已经可读写

  1. netpoll 方法需要循环执行,这样才能及时获得那些事件有返回了, 才能通知对应的协程.
    谁来调这个方法, 入口方法放到了 gcStart , 因为 go会保证一段时间,一定会调下 gc, 在讲 go 内存,垃圾回收时候讲过.

再来看 netpoll()netpollready() 的实现:

func netpollready(toRun *gList, pd *pollDesc, mode int32) {
	var rg, wg *g
	if mode == 'r' || mode == 'r'+'w' { // 可读
		rg = netpollunblock(pd, 'r', true)
	}
	if mode == 'w' || mode == 'r'+'w' { // 可写
		wg = netpollunblock(pd, 'w', true)
	}
     // 通过上面的方法,标记好了 pdReady之后,
	if rg != nil {
		toRun.push(rg) //把对应的协程,加入到一个可执行的 队列中,一个链表
	}
	if wg != nil {
		toRun.push(wg)
	}
}

func netpollunblock(pd *pollDesc, mode int32, ioready bool) *g {
	gpp := &pd.rg // 默认是 可读协程
	if mode == 'w' { //如果是 可写 w, 就取可写的协程
		gpp = &pd.wg
	}
    // 下面的代码就是把 对应的 rg 或者 wg 这个字段改成了  pdReady, 标记下有事件来了.
	for {
        // 这里的old值 , 第一次取可能是一个等待协程的地址 ,通过下面的情景能看到
		old := gpp.Load()
		if old == pdReady {
			return nil
		}
		if old == pdNil && !ioready {
			return nil
		}
		var new uintptr
		if ioready {
			new = pdReady
		}
		if gpp.CompareAndSwap(old, new) { // 如果值相等,就写入新的值
			if old == pdWait {
				old = pdNil
			}
			return (*g)(unsafe.Pointer(old)) // 当old为一个协程的地址,这里返回的就是一个协程
		}
	}
}

上面方法就是把 pollDesc 的 rg或者wg置为  pdReady

当协程去调用 poll_runtime_pollWait() 方法 询问时候, 发现已经是 ready状态,就开始进行 读写

//go:linkname poll_runtime_pollWait internal/poll.runtime_pollWait
// 这种方式 可以调用声明包中的小写方法
func poll_runtime_pollWait(pd *pollDesc, mode int) int {
errcode := netpollcheckerr(pd, int32(mode))

for !netpollblock(pd, int32(mode), false) { // 这里就是判断这个 pd的rg或者wg 是否是 ready状态
	errcode = netpollcheckerr(pd, int32(mode))
	if errcode != pollNoError {
		return errcode
	}
}
return pollNoError

}

归纳:

runtime 循环调用 netpoll() 方法 (g0协程 的gcStart 垃圾回收开始方法)

发现Socket可读写时,给对应的rg或者wg置为pdReady(1)

协程调用poll_runtime_pollWait()

判断rg或者wg已经置为pdReady(1),返向0

场景 2:Socket 暂时无法读写

需要深入看上面那个判断 pdrg是否为ready状态的方法
当协程去调用 poll_runtime_pollWait()

会跳转到:

func netpollblock(pd *pollDesc, mode int32, waitio bool) bool {
	gpp := &pd.rg
	if mode == 'w' {
		gpp = &pd.wg
	}

	for {
		
		if gpp.CompareAndSwap(pdReady, pdNil) {
			return true
		}
		if gpp.CompareAndSwap(pdNil, pdWait) {
			break
		}
		if v := gpp.Load(); v != pdReady && v != pdNil {
			throw("runtime: double wait")
		}
	}

	// 如果不是ready ,那么协程就会休眠等待,  并把协程的地址,赋值给 pd的rg或wg,在`netpollblockcommit`中
	if waitio || netpollcheckerr(pd, mode) == pollNoError {
		gopark(netpollblockcommit, unsafe.Pointer(gpp), waitReasonIOWait, traceBlockNet, 5)
	}
	// be careful to not lose concurrent pdReady notification
	old := gpp.Swap(pdNil)
	if old > pdWait {
		throw("runtime: corrupted polldesc")
	}
	return old == pdReady
}

归纳:

runtime 循环调用netpoll()方法 (g0协程)
协程调用poll_runtime_pollWait()
发现对应的rg或者wg为0
给对应的rg或者wg置为协程地址
休眠等待

当有事件来的时候, netpoll 中返回了 对应事件的 glist, 然后,runtime就会通知这些协程开始工作.

runtime 循环调用 netpoll() 方法 (g0协程)
发现Socket可读写时,给对应的查看对应的rg或者wg
若为协程地址,返回协程地址
调度器开始调度对应协程

大体的结构如下:文章来源地址https://www.toymoban.com/news/detail-748243.html

go 网络 network poller

到了这里,关于go 网络 network poller的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【计算机网络】网络基础--协议/网络协议/网络传输流程/地址管理

    网络的发展分为一下几个阶段: 独立模式: 计算机之间相互独立: 此时计算机之间是相互独立的,每个人在执行任务的时候是独立的,需要等待前一个将任务完成之后,自己才能进行执行任务,是串行执行的,效率很低。 网络互联: 多台计算机连接在一起, 完成数据共享:

    2024年02月03日
    浏览(50)
  • 网络基础与网络协议

    抽象语言——电脑(加工)——二进制——抽象语言 应用层: 跟人进行交互(人机交互)——我们给他输入抽象语言——编码——后台程序 表示层: 将“编码”转化为电脑可以识别的二进制 介质访问控制层(MAC): MAC地址是网卡决定的,是固定的。 物理层: 人类最早的

    2024年02月22日
    浏览(71)
  • 网络基础知识:了解网络协议的组成和常见的网络协议

    网络基础知识,了解网络协议的组成和常见的网络协议 1、协议及协议栈的基本概念 1.1、什么是协议 协议是网络中计算机或设备之间进行通信的一系列规则的集合。常用协议有IP、TCP、HTTP、POP3、SMTP等。 1.2、什么是协议栈 在网络中,为了完成通信,必须使用多层上的多种协

    2024年02月07日
    浏览(71)
  • 网络基础:通信原理及网络协议

    集线器:一个口收到的信号原封不动地转发给其他所有口,其他口上的设备自己决定是否接收信号。有点类似广播,但必广播更纯粹。由于hub只是单纯地转发,所以工作在物理层(OSI第一层) 类似于广播模式,纯硬件 网桥:工作在数据链路层(OSI第二层)。以太网中,数据

    2024年02月21日
    浏览(48)
  • 认识协议【网络基础】

    在网络通信中,协议(Protocol)是指计算机或设备之间进行通信的一系列规则的集合。 不管是网络还是生活中,协议是一种事先约定好的规则,通信的参与方按照同一份规则进行通信,如连接方式,如何识别等等。只有事先约定好了规则,才能保证后续通信时的效率和一定的

    2024年02月07日
    浏览(54)
  • 计算机网络基础--网络层协议分析实验

    一、实验目的 1、掌握网络数据包嗅探器Wireshark的使用; 2、理解IP协议,掌握IP分组格式和IP分片; 3、理解ICMP协议。 二、实验内容 (主要包括实验设计、实验环境、实验步骤、测试数据和实验结果) 1、通过使用ping命令,截获报文,分析IP数据报的格式和IP分片; 2、通过使

    2024年02月04日
    浏览(59)
  • 【传输层】网络基础 -- UDP协议 | TCP协议

    端口号(Port)标识了一个主机上进行通信的不同的应用程序 在TCP/IP协议中,用 “源IP”, “源端口号”, “目的IP”, “目的端口号”, “协议号” 这样一个五元组来标识一个通信(可以通过 netstat -n 查看) 0 - 1023:知名端口号,HTTP,FTP,SSH等这些广为使用的应用层协议,他

    2024年02月09日
    浏览(52)
  • Linux 网络基础(1)基础知识、IP地址、端口、协议、网络字节序

    网络发展背景: 网络的划分:局域网(覆盖范围在1000m以内)、城域网(覆盖范围在20km以内)、广域网(更大范围) 组网方式:以太网、令牌环网.... 日常名词:互联网,因特网----说的是一个网络,就是国际化的广域网 网卡:实现数字信号与电信号之间的转换 中继器:信号

    2024年02月05日
    浏览(93)
  • 网络基础 二 OSI七层模型与网络协议

    OSI/RM------开放式系统互联参考模型 数据链路层:介质访问控制层MAC+逻辑链路控制层LLC 逻辑链路控制层LLC:对数据惊醒校验,只保障数据完整性;同时增加FCS(校验核),校验数据完整性。 应用层:抽象语言----编码 表示层:编码---二进制 网络层:IP 互联网协议   数据链路

    2024年02月19日
    浏览(54)
  • 信息网络协议基础_绪论

    电话交换网 数据报交换 虚电路交换 如何理解间隙性? 断断续续的连接:间隙性指的是网络连接的断断续续,即网络中的节点不是始终相互连接,而是时有时无。 等待传输机会:在DTN中,数据可能需要在网络节点上等待一段时间,直到下一个传输机会出现。例如,一个在深

    2024年02月19日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包