python scoket使用epoll模型

select模型虽好,却有一个缺陷,只能对1024个文件描述符进行监视,虽然可以通过重新编译内核获得更大的监视数量,但这样做还不如将目光投向更高级的epoll模型。select模型中,每一次都需要遍历所有处于监视中的文件描述符,判断他们哪个可写,哪个可读,这样一来,你监视的越多,速度越慢,而在epoll模型中,所有添加到epoll中的事件都会网卡驱动程序建立起回调关系,简言之,如果有一个连接可写,那么这个可写的事件就会报告给你,而你不需要挨个询问他们哪个连接可写,哪个连接可读, tornado框架就使用了epoll模型。

强调一点,本文所用代码只能在linux环境下执行,因为只有linux系统支持epoll模型。

1. 服务端代码

import socket
import select


def start_server(port):
    serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    serversocket.bind(('0.0.0.0', port))
    #accept队列大小为100
    serversocket.listen(100)
    serversocket.setblocking(0)

    epoll = select.epoll()
    #注册一个in事件,等待有数据可读
    epoll.register(serversocket.fileno(), select.EPOLLIN)

    try:
        #保存连接,请求,和响应信息
        connections = {}
        while True:
            #最多等待1秒钟时间,有事件返回事件列表
            events = epoll.poll(1)
            for fileno, event in events:
                #事件的句柄是server
                if fileno == serversocket.fileno():
                    connection, address = serversocket.accept()
                    #设置为非阻塞的
                    connection.setblocking(0)
                    #新建的连接也注册读事件
                    epoll.register(connection.fileno(), select.EPOLLIN)
                    connections[connection.fileno()] = connection
                    #不是server,那就是建立的连接,现在连接可读
                elif event & select.EPOLLIN:
                    data = connections[fileno].recv(1024)
                    if data:
                        epoll.modify(fileno, select.EPOLLOUT)
                    else:
                        epoll.modify(fileno, 0)
                        connections[fileno].shutdown(socket.SHUT_RDWR)
                        del connections[fileno]

                elif event & select.EPOLLOUT:
                    #可写的事件被触发
                    clientsocket = connections[fileno]
                    clientsocket.send('收到数据'.encode(encoding='utf-8'))

                    #需要回写的数据已经写完了,再次注册读事件
                    epoll.modify(fileno, select.EPOLLIN)
                elif event & select.EPOLLHUP:
                    #被挂起了,注销句柄,关闭连接,这时候,是客户端主动断开了连接
                    epoll.unregister(fileno)
                    if fileno in connections:
                        connections[fileno].close()
                        del connections[fileno]
    finally:
        epoll.unregister(serversocket.fileno())
        epoll.close()
        serversocket.close()


if __name__ == '__main__':
    start_server(8801)

本文所采用的是epoll模型的边缘触发,除此以外,还有一个水平触发。

1.1 events

epoll.poll()返回的是所有可操作的文件描述符和事件类型,具体是哪个事件,需要你自己使用if语句逐个进行判断,此外,还需要你自己来保存文件描述符与socket文件之间的映射关系。

1.2 注册

获得客户端连接后,需要将这个socket注册读事件,这样,当这个socket发送数据后,下一次调用epoll.poll()就会获得该socket。

2. 客户端示例代码

客户端示例代码与上一篇一致

import os
import time
import socket

def start_client(addr, port):
    PLC_ADDR = addr
    PLC_PORT = port
    s = socket.socket()
    s.connect((PLC_ADDR, PLC_PORT))
    count = 0
    while True:
        msg = '进程{pid}发送数据'.format(pid=os.getpid())
        msg = msg.encode(encoding='utf-8')
        s.send(msg)
        recv_data = s.recv(1024)
        print(recv_data.decode(encoding='utf-8'))
        time.sleep(3)
        count += 1
        if count > 20:
            break

    s.close()

if __name__ == '__main__':
    start_client('127.0.0.1', 8801)

启动服务端后,同时启动多个客户端,观察实验效果。

扫描关注, 与我技术互动

QQ交流群: 211426309

加入知识星球, 每天收获更多精彩内容

分享日常研究的python技术和遇到的问题及解决方案