python编程之select模块的epoll异步I/O模型(一)

  Python 在 2.6 版中引入了用于处理Linux上的epoll模型,windows上则不支持此模型。在select模型篇里介绍了异步模型的好处,而epoll模型是一个比select模型还要高效的模型。

  在select模型中为了检测一个客户端是否连接,是否有数据流动及关闭,select模型每次都需要遍历所有的套接字,这其中可能有一部分或者很少的一部分连接是活动的,很显然,
  
  每次都循环检测一遍不是最佳的解决方案。而epoll模型的出现则解决了这个问题,epoll模型中程序不必像select那样循环遍历所有的套接字,而是依赖操作系统获知哪些套接字有
  
  需要被程序处理的事件,然后注册到事件对象中。这显然开销少了很多,效率自然就上来了。所以,epoll模型的服务器端程序在高并发多连接的场合下显得更高效。
  epoll在注册事件时有两种方式,边缘触发( edge-triggered ) 和水平触发( level-triggered )。在边缘触发模式下,创建epoll.poll()事件时,只会在处于事件中的套接字上检查
  
  返回一次,此后操作系统不再发出事件通知,程序须自己处理所有的与该事件相关的数据,完毕后再对该套接字进行操作将会产生异常。而在水平触发方式( level-triggered mode)
  
  下,只要数据没有被处理完,多次调用epoll.poll()将会一直发送事件相关的通知,直到所有与该事件相关的数据被处理完毕,在水平触发方式下,程序不会产生异常。
  
  简而言之,在边缘触发模式下,程序员需要自己处理一个事件的相关数据指导完毕,而水平模式下,程序员无需关心这些,数据的处理依然靠系统的不断通知来触发处理。
  epoll的注册事件类型和意义:
Constant Meaning
EPOLLIN Available for read
EPOLLOUT Available for write
EPOLLPRI Urgent data for read
EPOLLERR Error condition happened on the assoc. fd
EPOLLHUP Hang up happened on the assoc. fd
EPOLLET Set Edge Trigger behavior, the default is Level Trigger behavior
EPOLLONESHOT Set one-shot behavior. After one event is pulled out, the fd is internally disabled
EPOLLRDNORM Equivalent to EPOLLIN
EPOLLRDBAND Priority data band can be read.
EPOLLWRNORM Equivalent to EPOLLOUT
EPOLLWRBAND Priority data may be written.
EPOLLMSG Ignored.
下面是国外人的示例代码,用来模拟一个http协议的web服务器,关键部分我已经做好了注释,这个例子用来学习还是很不错的:

  代码官网链接:完整的实例

#! /usr/bin/env python
# -*- coding:utf-8 -*-
'''
Edit by zhulei on 2015年9月18日
Auther: zhulei
'''
import socket,select

#截断和BYTE字符串响应信息
EOL1 = b'\n\n'
EOL2 = b'\n\r\n'
response  = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Sep 2015 17:00:00 GMT\r\n'
response += b'Content-Type: text/plain\r\nContent-Length: 10\r\n\r\n'
response += b'epoll test'

#创建socket的套接字,绑定任意本地IP并监听9000端口,设置监听的backlog队列长度为200
server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(('0.0.0.0',9000))
server.listen(500)
server.setblocking(0)
#TCP_NODELAY 选项用来告知操作系统 socket.send() 发送的数据应该立即发送出去,而不是缓存起来
server.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)


#创建一个epoll对象
epoll = select.epoll()
#注册server套接字事件,即告知创建的epoll对象需要在server套接字上监听EPOLLIN事件【表示对应的文件描述符可以读】
epoll.register(server.fileno(), select.EPOLLIN)

try:
    connections = {}; requests = {}; responses = {}
    while 1:
        #查询epoll对象找出是否有事件发生,并返回一个元组集合【文件描述符和事件码】(fileno, event code)
        events = epoll.poll()
        #循环事件
        for fileno,event in events:
            #是否有新的连接,若有则新的scoket连接被接受、创建并被设置为非阻塞模式
            if fileno == server.fileno():
                connection, address = server.accept()
                connection.setblocking(0) 
                #注册新的连接的RPOLLIN(可以读的)事件
                epoll.register(connection.fileno(), select.EPOLLIN)
                #把新的连接的连接、请求、响应信息保存到字典里
                connections[connection.fileno()] = connection
                requests[connection.fileno()] = b''
                responses[connection.fileno()] = response
            #如果存在事件并且是可以读的,就表示有请求的数据过来,此时可以进行接受
            elif event & select.EPOLLIN:
                requests[fileno] += connections[fileno].recv(1024)
                #判断是否接受完毕,遇到回车换行符就认为数据接受完毕
                if EOL1 in requests[fileno] or EOL2 in requests[fileno]:
                    #一旦接受数据完成,就卸载已经注册的EPOLLIN(可以读的)事件,同时注册为EPOLLOUT(可以写的)事件,
                    #即把已经完成请求的连接事件注册为可以写的EPOLLOUT事件,此时可以发送数据给客户端
                    epoll.modify(fileno, select.EPOLLOUT)
                    #暂存要接受的信息,直到接收完成
                    #connections[fileno].setsockopt(socket.IPPROTO_TCP, socket.TCP_CORK, 1)
                    #输出客户端请求的数据
                    print('-'*40 + '\n' + requests[fileno].decode()[:-2])
            #如果存在的事件是(可以写的)EPOLLOUT事件,表示可以给客户端发送数据    
            elif event & select.EPOLLOUT:
                #byteswritten表示一次发送的大小
                byteswritten = connections[fileno].send(responses[fileno])
                #还有剩下的未发送的内容
                responses[fileno] = responses[fileno][byteswritten:]
                #发送完毕,禁用该连接的读或者写的事件,并通知客户端关闭socket连接
                if len(responses[fileno]) == 0:
                    epoll.modify(fileno, 0)
                    #暂存要发送的信息,直到被发送出去
                    #connections[fileno].setsockopt(socket.IPPROTO_TCP, socket.TCP_CORK, 0)
                    connections[fileno].shutdown(socket.SHUT_RDWR)
            #如果事件的状态是挂断的,卸载注册的事件,关闭客户端的连接,删除已经连接的socket信息   
            elif event & select.EPOLLHUP:
                epoll.unregister(fileno)
                connections[fileno].close()
                del connections[fileno]
except Exception,e:
    print e
finally:
    epoll.unregister(server.fileno())
    epoll.close()
    server.close()
运行截图:

服务端输出:

使用ab测试输出:

后续会开发一些使用epoll模型的程序,欢迎大家一起交流学习!

1432433850276575.png


Comments are closed.