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-748235.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

领红包