python使用select和epoll实现IO多路复用实现并发服务器

在select模块中, 有三种方法实现IO多路复用并发服务器

  • select
  • poll
  • epoll

select的原理: 在多路复用的模型中,比较常用的有select模型和epoll模型。这两个都是系统接口,由操作系统提供。当然,Python的select模块进行了更高级的封装。

网络通信被Unix系统抽象为文件的读写,通常是一个设备,由设备驱动程序提供,驱动可以知道自身的数据是否可用。支持阻塞操作的设备驱动通常会实现一组自身的等待队列,如读/写等待队列用于支持上层(用户层)所需的block或non-block操作。设备的文件的资源如果可用(可读或者可写)则会通知进程,反之则会让进程睡眠,等到数据到来可用的时候,再唤醒进程。

这些设备的文件描述符被放在一个数组中,然后select调用的时候遍历这个数组,如果对于的文件描述符可读则会返回改文件描述符。当遍历结束之后,如果仍然没有一个可用设备文件描述符,select让用户进程则会睡眠,直到等待资源可用的时候在唤醒,遍历之前那个监视的数组。每次遍历都是依次进行判断的。

例如使用select实现echo(回显)服务器

import select
import socket
import sys


server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('', 7788))
server.listen(5)

inputs = [server, sys.stdin]

running = True

while True:

    # 调用 select 函数,阻塞等待
    readable, writeable, exceptional = select.select(inputs, [], [])

    # 数据抵达,循环
    for sock in readable:

        # 监听到有新的连接
        if sock == server:
            conn, addr = server.accept()
            # select 监听的socket
            inputs.append(conn)

        # 监听到键盘有输入
        elif sock == sys.stdin:
            cmd = sys.stdin.readline()
            running = False
            break

        # 有数据到达
        else:
            # 读取客户端连接发送的数据
            data = sock.recv(1024)
            if data:
                sock.send(data)
            else:
                # 移除select监听的socket
                inputs.remove(sock)
                sock.close()

    # 如果检测到用户输入敲击键盘,那么就退出
    if not running:
        break

server.close()

但是在底层原理中, select和epoll 都是使用轮询原理来实现的. epoll是触发通知机制

epoll的优点:

  • 没有最大并发连接的限制,能打开的FD(指的是文件描述符,通俗的理解就是套接字对应的数字编号)的上限远大于1024
  • 效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;即epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,epoll的效率就会远远高于select和poll。
import socket
import select

# 创建套接字
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 可重复绑定
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# 绑定本机地址端口
s.bind(("", 7788))

# 变为被动服务器
s.listen(1024)

# 创建一个epoll对象
epoll = select.epoll()

# 在epoll中注册s套接字(注意此处不是直接用是s, 而是使用s的fileno)
epoll.register(s.fileno(), select.EPOLLIN|select.EPOLLET)


# 创建两个字典, 来保存fileno和与其对应的套接字和地址
connections = {}
addresses = {}

# 开始等待客户端发送来的信息
while True:
    
    # 对epoll中的套接字进行扫描
    epollList = epoll.poll()

    # 对扫描到的事件进行判断
    for fd,events in epollList:
        
        # 如果判断是s套接字
        if fd == s.fileno():
            conn,addr = s.accept()

            print("有新的客户端到来...%s"%str(addr))
            connections[conn.fileno()] = conn
            addresses[conn.fileno()] = addr

            epoll.register(conn.fileno(), selecte.EPOLLIN|select.EPOLLET)

        # 如果是接收到了数据
        elif events == select.EPOLLIN:
            recvData = connections[fd].recv(1024)

            if len(recvData) > 0:
                print("recvData: %s"%recvData)

            else:
                epoll.unregister(fd)
                connections[fd].close()

                print("%s....offline....."%str(addresses[fd]))

引:https://www.jianshu.com/p/cdfddb026db0




原文地址:https://www.cnblogs.com/070727sun/p/11065992.html