深入解析多路复用IO:select/epoll原理与事件驱动IO的工作流程

IO复用(IO复用)

深入解析多路复用IO:select/epoll原理与事件驱动IO的工作流程

这种IO方式就是事件驱动IO(eventdrivenIO)。

我们都知道select/epoll的优点是单个进程可以同时处理多个网络连接的IO。它的基本原理是select/epoll函数会不断轮询它负责的所有socket。当数据到达某个套接字时,用户进程将收到通知。其流程如下:

select 是一种多路复用

当用户进程调用select时,整个进程会被阻塞,同时内核会“监视”所有负责select的socket。

当任意一个socket中的数据准备好后,select就会返回。这时用户进程调用read操作将数据从内核复制到用户进程。

这个图其实和阻塞IO图没有太大区别。事实上,情况甚至更糟。因为这里需要用到两个系统调用(select和recvfrom),

阻塞IO只调用一个系统调用\(recvfrom\)。不过,使用select的好处是可以同时处理多个连接。

多路复用 IO 与阻塞 IO 模型相比:

1.阻塞IO经历两个阶段:等待数据、复制数据

2.复用三个阶段:等待数据、准备复制数据、复制数据

单连接socket通信阻塞IO效率高

多路复用IO select可以代理多个socket连接,多路socket通信,多路复用IO效率高。

强调:

1、如果处理的连接数不是很高,使用select/epoll的Web服务器不一定比使用多线程+阻塞IO的Web服务器表现更好,延迟可能会更大。 select/epoll的优点不是可以更快地处理单个连接,而是可以处理更多的连接。

2.在复用模型中,每个socket一般都设置为非阻塞。然而,如上图所示,整个用户进程实际上一直处于阻塞状态。只不过进程是被select函数阻塞的,而不是被socket IO阻塞的。

结论:select的优点是可以处理多个连接,性能较高,可以同时检测多个socket IO行为。它不适合单个连接。

选择网络IO模型示例

select检测多个socket IO行为accept、recv

IO 行为有两种类型:

1.别人给我发数据

2. 向他人发送数据

timeout是超时时间

每隔0.5秒询问操作系统数据是否准备好。

def select(rlist, wlist, xlist, timeout=None): 
  pass
# [] 传的空列表是出异常的列表
# 返回值3个列表 收列表,发列表,异常列表
rl,wl,xl = select.select(rlist, wlist, [], 0.5)

客户:

from socket import *
client = socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8000))
while True:
  msg = input(">>>:").strip()
  if not msg:continue
  client.send(msg.encode("utf-8"))
  data = client.recv(1024)
  print(data.decode("utf-8"))
client.close()

服务器代码:

from socket import *
import select
server = socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8000))
server.listen(5)
# 设置socket接口为 非阻塞IO接口
# 默认是True 为阻塞
server.setblocking(False)
# 专门存着收消息套接字
rlist = [server,]
# 存放发送消息套接字
wlist = []
# 存放发送的数据
wdata = {}
while True:
  # 返回值3个列表 收列表,发列表,异常列表
  rl,wl,xl = select.select(rlist, wlist, [], 0.5)
  print("rl",rl)
  print("wl",wl)
  for sock in rl:
    if sock == server:
      conn,addr = sock.accept()
      rlist.append(conn)
    else:
      try:
        data = sock.recv(1024)
        if not data:
          sock.close()
          rlist.remove(sock)
          continue
        # 收的套接字加到列表
        wlist.append(sock)
        # 把数据加到字典 做一个 套接字对应数据
        wdata[sock] = data.upper()
      except Exception:
        sock.close()
        rlist.remove(sock)
  # 发送数据
  for sock in wl:
    sock.send(wdata[sock])
    wlist.remove(sock)
    wdata.pop(sock)
server.close()

基于select模块检测socket IO行为,达到并发效果

select监控fd变化的过程分析:

用户进程创建一个socket对象,并将监控的fd复制到内核空间。每个fd都会对应一个系统文件表。内核空间的fd响应数据后,

将向用户进程发送数据已到达的信号;

用户进程然后发送系统调用,如(accept)将内核空间中的数据复制到用户空间,同时作为接收数据端清除内核空间中的数据。

这样,重新监听时,如果有新的数据,fd就可以再次响应(发送方是基于TCP协议的,所以需要收到响应后才清除)。

该模型的优点:

它可以同时检测多个socket,比阻塞IO和非阻塞IO效率更高。

与其他模型相比,使用select()的事件驱动模型仅使用单个线程(进程)执行,占用资源较少,不会消耗太多CPU,并且可以同时为多个客户端提供服务。

如果你正在尝试构建一个简单的事件驱动服务器程序,这个模型有一定的参考价值。

该模型的缺点:

需要通过循环列表来一一检测代理的套接字列表中的多个套接字。

在代理套接字较少的情况下,循环速度更快。然而,当select代理的socket很多时,select的效率会随着列表的增加而变得越来越慢。

首先,select()接口并不是实现“事件驱动”的最佳选择。因为当需要检测的句柄值较大时,select()接口本身需要花费大量的时间轮询每个句柄。

很多操作系统都提供了更高效的接口,比如Linux提供了epoll,BSD提供了kqueue,Solaris提供了/dev/poll,...

如果需要实现更高效的服务器程序,更推荐使用epoll这样的接口。不幸的是,不同操作系统提供的epoll接口有很大不同。

因此,使用类似于epoll的接口来实现具有良好跨平台能力的服务器会比较困难。

其次,该模型混合了事件检测和事件响应。一旦事件响应的执行体很大,对于整个模型来说将是灾难性的。

epoll是异步实现的。提交套接字时,每个套接字都会绑定一个回调函数。当套接字准备好时,会触发回调函数,并将其索引放置在单独的列表中。对于 select 来说,只需要根据准备好的列表中的索引来获取套接字即可,所以不需要遍历每个列表。

epoll不支持windows系统

以上就是本文的全部内容。希望对大家的学习有所帮助。也希望大家支持易速云。

你可能想看:
« 上一篇
下一篇 »

相关推荐

python爬虫教程PDF,Python爬虫书PDF

2026年09月02日

31阅读

外国教程python,python编程教学视频

2026年09月02日

25阅读

python简明教程沈洁元,python详细基础教程

2026年09月02日

21阅读

python库包教程,python 包

2026年09月02日

27阅读

python教程大全集,python教程600集

2026年09月02日

26阅读

python入门教程完整版,python零基础入门全套教程视频

2026年09月02日

22阅读