草庐IT

从Go编程看IO多路复用Select

LxFly 2023-03-28 原文

  IO多路复用通过某种机制使进程监听某些文件描述符,当文件描述符中有读或写就绪时,进程能够收到系统内核发送的相应通知从而进行相应的IO操作;IO多路复用有:select、poll、epoll等模式,这里主要介绍select;select本质上也是同步IO,调用时阻塞自己,IO事件就绪后被唤醒返回负责读写操作;

在Go中其函数定义如下:

func Select(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout 
*Timeval) (n int, err error)

FdSet定义:

type FdSet struct {
  Bits [16]int64
}

select函数实现IO多路复用,通过其参数通知内核:

   1、关注的文件描述符
   2、关心的文件描述符的哪种状态:可读、可写还是异常
   3、等待时间,无限等待阻塞或是固定超时时间

函数参数

  通过上面的介绍可以知道我们需要有这么几种参数传递给select函数,所关注的描述符,所关注的状态、等待时间;

函数参数具体含义:

  nfd(maxfd): 文件描述符集合中要监听的文件描述符个数,0-(maxfd-1)为需要检测的文件描述符;
  r(readfds): 读监控文件描述符集,监控文件描述符集的读变化,如文件描述符集中有文件可读即通过该参数回传有变化的描述符,清空无变化的描述符;
  w(writefds): 写监控文件描述符集,监控文件描述符集的写变化,如文件描述符集中有文件可写即通过该参数回传有变化的描述符,清空无变化的描述符;
  e(exceptfds): 异常监控文件描述符集,监控文件描述符集的异常,如文件描述符集中有文件异常即通过该参数回传有变化的描述符,清空无变化的描述符;
  timeout参数: 传入nil时函数无限阻塞等待,整数值为超时时间;

  上面三个文件描述符集合如无需关注某一类状态可传入nil,则select将不监控文件描述符的读、写或异常;
  tcp连接中可只需关注是否可读即可;

函数返回:

  通过函数返回可知这么两类信息:
  1、准备好的文件描述符个数
  2、具体哪些文件描述符处于就绪可读、可写或异常状态

函数值:

  -1 发生错误
  0 函数超时,当设置了超时时间,该时间内未有状态变化时
  大于0 有满足读、写、异常的文件描述符,需检查文件描述符集

特别关注

  每次函数返回时都会将文件描述符集FdSet中未发生任何事件的fd清空,每次调用select时都需将所关注的fd重新加入FdSet中;
  可监控文件描述符个数取决于 FdSet中Bits的位长度,每个bit代表一个文件描述符,默认情况下Go中的定义为:Bits [16]int64,也就是一个8字节整数数组,数组长度为16,第一个数组元素可存储的文件描述符为:0-63,第二个为:64-127依次类推;此时最多可以监听的文件描述符数为1024个;

Select的相关问题:

  1、内核将消息传递到用户空间需要执行系统拷贝,如监听了大量fd会导致性能下降
  2、每次调用select都需要从用户态拷贝fd集合到内核态
  3、每次调用select内核态都需要遍历传进来的所有fd集合
  4、默认select支持的fd集合过小,只有1024;
  5、轮询效率低,每次调用select、内核通知都需要轮询整个fd集合

Go中的代码实现:

func SelectIO(fd int) {
//读文件描述符集
fdReadSet := &syscall.FdSet{}
connect = &Connect{maxFd: fd, childsMap: map[string]int{}}
for {
	FD_ZERO(fdReadSet)
	FD_SET(fd, fdReadSet) //socket文件描述符
	for _, child := range connect.childsMap {
		FD_SET(child, fdReadSet) //连接监听
		if child > connect.maxFd {
			connect.maxFd = child
		} else {

		}
	}
	n, err := syscall.Select(connect.maxFd+1, fdReadSet, nil, nil, nil)
	if err != nil {
		log.Println(err)
		if err == syscall.EAGAIN { //非阻塞模型下资源限制或不满足条件返回eagain 异常 Resource temporarily unavailable
			continue
		}
	}
	log.Printf("n:%v,fdReadSet:%v", n, FD_ISSET(fd, fdReadSet))
	//-1 出错  >0就绪的文件描述符数  0 超时
	if n > 0 && FD_ISSET(fd, fdReadSet) {
		//接受连接
		nfd, naddr, err := syscall.Accept(fd) //阻塞模式下在此方法会阻塞
		if err != nil {
			log.Printf("接受连接出错:%v,%v", fd, err)
			continue
		}
		//设置非阻塞
		if err := syscall.SetNonblock(nfd, true); err != nil {
			log.Fatal(err)
		}
		var addr = naddr.(*syscall.SockaddrInet4)
		var ip = fmt.Sprintf("%d.%d.%d.%d:%d", addr.Addr[0], addr.Addr[1],
			addr.Addr[2], addr.Addr[3], addr.Port)
		log.Printf("新连接:%v", ip)
		processConn(nfd, ip)
	} else {
		readMsg(fdReadSet)
		}
	}
}

文章首发地址:https://mp.weixin.qq.com/s/1co33_BaJEwqrSX4GxkYEA

有关从Go编程看IO多路复用Select的更多相关文章

  1. ruby-on-rails - rspec should have_select ('cars' , :options => ['volvo' , 'saab' ] 不工作 - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion在首页我有:汽车:VolvoSaabMercedesAudistatic_pages_spec.rb中的测试代码:it"shouldhavetherightselect"dovisithome_pathit{shouldhave_select('cars',:options=>['volvo','saab','mercedes','audi'])}end响应是rspec./spec/request

  2. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  3. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  4. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  5. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  6. 网络编程套接字 - 2

    网络编程套接字网络编程基础知识理解源`IP`地址和目的`IP`地址理解源MAC地址和目的MAC地址认识端口号理解端口号和进程ID理解源端口号和目的端口号认识`TCP`协议认识`UDP`协议网络字节序socket编程接口`sockaddr``UDP`网络程序服务器端代码逻辑:需要用到的接口服务器端代码`udp`客户端代码逻辑`udp`客户端代码`TCP`网络程序服务器代码逻辑多个版本服务器单进程版本多进程版本多线程版本线程池版本服务器端代码客户端代码逻辑客户端代码TCP协议通讯流程TCP协议的客户端/服务器程序流程三次握手(建立连接)数据传输四次挥手(断开连接)TCP和UDP对比网络编程基础知识

  7. ruby-on-rails - 事件记录 : Select max of limit - 2

    我正在尝试将以下SQL查询转换为ActiveRecord,它正在融化我的大脑。deletefromtablewhereid有什么想法吗?我想做的是限制表中的行数。所以,我想删除少于最近10个条目的所有内容。编辑:通过结合以下几个答案找到了解决方案。Temperature.where('id这给我留下了最新的10个条目。 最佳答案 从您的SQL来看,您似乎想要从表中删除前10条记录。我相信到目前为止的大多数答案都会如此。这里有两个额外的选择:基于MurifoX的版本:Table.where(:id=>Table.order(:id).

  8. ruby - 我正在学习编程并选择了 Ruby。我应该升级到 Ruby 1.9 吗? - 2

    我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or

  9. ruby - 为什么不能使用类IO的实例方法noecho? - 2

    print"Enteryourpassword:"pass=STDIN.noecho(&:gets)puts"Yourpasswordis#{pass}!"输出:Enteryourpassword:input.rb:2:in`':undefinedmethod`noecho'for#>(NoMethodError) 最佳答案 一开始require'io/console'后来的Ruby1.9.3 关于ruby-为什么不能使用类IO的实例方法noecho?,我们在StackOverflow上

  10. ruby-on-rails - rails 上的 ruby : radio buttons for collection select - 2

    我有一个集合选择:此方法的单选按钮是什么?谢谢 最佳答案 Rails3中没有这样的助手。在Rails4中,它是collection_radio_buttons. 关于ruby-on-rails-rails上的ruby:radiobuttonsforcollectionselect,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/18525986/

随机推荐