python中 基于select 的poll、epoll 回写服务代码

poll 回写模式

  • 采用select机制的问题
  1. 每次调用select,都需要把fd_set集合从用户态拷贝到内核态,如果fd_set集合很大时,那这个开销也很大
  2. 同时每次调用select都需要在内核遍历传递进来的所有fd_set,如果fd_set集合很大时,那这个开销也很大
  3. 为了减少数据拷贝带来的性能损坏,内核对被监控的fd_set集合大小做了限制,并且这个是通过宏控制的,大小不可改变(限制为1024)

poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。也就是说,poll只解决了上面的问题3,并没有解决问题1,2的性能开销问题。

# -*- coding:utf-8 -*-
"""
File Name: poll_svr
Author: 82405
Data: 2022/6/2 15:43
-----------------------
Info:
    基于poll实现的回写服务
-----------------------
Change Activity:
    2022/6/2: create
"""
import socket
import select
import queue


def poll_svr():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('127.0.0.1', 1234))
    sock.listen(100)
    sock.setblocking(False)  # 非阻塞模式

    sock_que = {}  # 登记新的socket对象的消息队列
    sk_fd_map = {sock.fileno(): sock}
    p = select.poll()
    p.register(sock, select.POLLIN | select.POLLERR)
    while True:
        events = p.poll()
        for sk_fd, event in events:
            if sk_fd == sock.fileno():
                conn, addr = sk_fd_map[sk_fd].accept()  # 生成新的读写套接字对象和 客户端地址
                conn.setblocking(False)  # 非阻塞
                p.register(conn, select.POLLIN | select.POLLOUT | select.POLLERR)
                sk_fd_map[conn.fileno()] = conn
                sock_que[conn.fileno()] = queue.Queue()
            else:
                if event & select.POLLIN:
                    data = sk_fd_map[sk_fd].recv(1024)
                    if data:
                        print(f'{sk_fd}接收消息:{data}')
                        sock_que[sk_fd].put(data)
                        p.modify(sk_fd_map[sk_fd], select.POLLOUT | select.POLLERR)
                    else:
                        print(f'{sk_fd}关闭连接')
                        p.unregister(sk_fd)
                        sk_fd_map[sk_fd].close()
                        del sk_fd_map[sk_fd]
                        del sock_que[sk_fd]
                        continue
                if event & select.POLLOUT:
                    try:
                        next_msg = sock_que[sk_fd].get_nowait()
                        print(f'{sk_fd}回写消息: {next_msg}')
                        sk_fd_map[sk_fd].send(next_msg)
                        p.modify(sk_fd_map[sk_fd], select.POLLIN | select.POLLERR)
                    except queue.Empty:
                        print(f'************{sk_fd} 回写消息为空******************')
            if event & select.POLLERR:
                p.unregister(sk_fd)
                sk_fd_map[sk_fd].close()
                del sock_que[sk_fd]
                del sk_fd_map[sk_fd]


poll_svr()

epoll 会写模式

epoll在Linux2.6内核正式提出,是基于事件驱动的I/O方式,相对于select来说,epoll没有描述符个数限制,使用一个文件描述符管理多个描述符,将用户关心的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

  • epoll 中水平触发和边缘触发
    1.水平触发level trigger LT(状态达到)
      当被监控的文件描述符上有可读写事件发生时,会通知用户程序去读写,如果用户一次读写没取完数据,他会一直通知用户,如果这个描述符是用户不关心的,它每次都返回通知用户,则会导致用户对于关心的描述符的处理效率降低。
      复用型IO中的select和poll都是使用的水平触发方式。
    2.边缘触发edge trigger ET(状态改变)
      当被监控的文件描述符上有可读写事件发生时,会通知用户程序去读写,它只会通知用户进程一次,这需要用户一次把内容读取玩,相对于水平触发,效率更高。如果用户一次没有读完数据,再次请求时,不会立即返回,需要等待下一次的新的数据到来时才会返回,这次返回的内容包括上次未取完的数据。
      信号驱动型IO使用的是边缘触发方式。
      epoll既支持水平触发也支持边缘触发,默认是水平触发。
    3.比较
      水平触发是状态达到后,可以多次取数据。这种模式下要注意多次读写的情况下,效率和资源利用率情况。
    边缘触发是状态改变一次,取一次数据。这种模式下读写数据要注意一次是否能读写完成。
# -*- coding:utf-8 -*-
"""
*filename: epoll_svr
*author: wabi
*datetime: 2022/6/222:10
*info:
    基于epoll实现的回写服务
"""
import socket
import select
import queue


def epoll_svr():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('127.0.0.1', 1234))
    sock.listen(100)
    sock.setblocking(False)  # 非阻塞模式

    ep = select.epoll()
    ep.register(sock.fileno(), select.EPOLLIN | select.EPOLLERR)
    sk_fd_map = {sock.fileno(): sock}
    sock_que = {}  # 登记新的socket对象的消息队列
    while True:
        events = ep.poll(10)
        if not events:
            print('超时,重新轮询')
            continue
        for sk_fd, event in events:
            if sk_fd is sock.fileno():
                conn, addr = sk_fd_map[sk_fd].accept()  # 生成新的读写套接字对象和 客户端地址
                conn.setblocking(False)  # 非阻塞
                ep.register(conn, select.EPOLLIN | select.EPOLLERR)
                sk_fd_map[conn.fileno()] = conn
                sock_que[conn.fileno()] = queue.Queue()
            else:
                if event & select.EPOLLIN:
                    data = sk_fd_map[sk_fd].recv(1024)
                    if data:
                        print(f'{sk_fd}接收消息:{data}')
                        sock_que[sk_fd].put(data)
                        ep.modify(sk_fd_map[sk_fd], select.EPOLLOUT | select.EPOLLERR)
                if event & select.EPOLLOUT:
                    try:
                        next_msg = sock_que[sk_fd].get_nowait()
                        print(f'{sk_fd}回写消息: {next_msg}')
                        sk_fd_map[sk_fd].send(next_msg)
                        ep.modify(sk_fd_map[sk_fd], select.EPOLLIN | select.EPOLLERR)
                    except queue.Empty:
                        print(f'************{sk_fd} 回写消息为空******************')
                if event & select.EPOLLHUP:
                    print('conn close')
                    ep.unregister(sk_fd)
                    sk_fd_map[sk_fd].close()
                    del sk_fd_map[sk_fd]
                    del sock_que[sk_fd]

epoll_svr()

  • 参考
  1. https://www.cnblogs.com/Zhangyq-yard/p/10165092.html
  2. https://www.itqiankun.com/article/select-poll-epoll
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值