epoll版服务器实现原理类似于select版服务器,都是通过某种方式对套接字进行检验其是否能收发数据等。但是epoll版的效率要更高,同时没有上限。且epoll版服务器只能在Linux系统中运行。
在select、poll中的检验,是一种被动的轮询检验,而epoll中的检验是一种主动地事件通知检测,即:当有套接字符合检验的要求,便会主动通知,从而进行操作。这样的机制自然效率会高一点。
同时在epoll中要用到文件描述符,所谓文件描述符实质上是数字,可以在ipython下利用fileno()方法查看文件描述符。代码如下:
>>> import sys
>>> sys.stdin.fileno()
0
>>> sys.stdout.fileno()
1
>>> sys.stderr.fileno()
2
>>> f = open("xxx.txt", "w")
>>> f.fileno()
3
>>>
分别是标准输入、标准输出、标准错误所对应的文件描述符。之后便开始进行服务器的编写,代码如下:
#-*- coding:utf-8 -*-
from socket import *
import select
#单进程服务器 epoll版
serSocket = socket(AF_INET, SOCK_STREAM)
localAddr = ('', 7788)
serSocket.bind(localAddr)
#建立epoll对象
epoll = select.epoll()
print(serSocket.fileno())
#将创建的套接字添加到epoll的事件监听中
epoll.register(serSocket.fileno(), select.EPOLLIN|select.EPOLLET)
#select.EPOLLIN表示监听是否能够读取信息
#select.EPOLLET表示边缘触发
connections = {}
adress = {}
while True:
epoll_list = epoll.epoll()
# epoll 进行 fd 扫描的地方 扫描出能满足条件的套接字,添加进列表中
for fd, events in epoll_list:
#对epoll列表中的文件描述符、事件进行扫描
if fd == serSocket.fileno():
#表示有客户端连接了服务器套接字
conn, addr = serSocket.accept()
print('there has been a new cilent form [%s]'%str(addr))
connections[conn.fileno()] = conn
adress[addr.fileno()] = addr
epoll.register(conn.fileno(), select.EPOLLIN|select.EPOLLET)
elif events == select.EPOLLIN:
#表示可以收取信息
recvData = connections[fd].recv(1024)
if len(recvData) > 0:
print('massage from [%s] is %s'%(str(addr), recvData.decode('utf-8')))
else:
print('client[%d] was closed'%str(addr))
epoll.unregister(fd)
connections[fd].colse()
值得说明的是:
EPOLLIN (可读)
EPOLLOUT (可写)
EPOLLET (ET模式)
epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:
LT模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll时,会再次响应应用程序并通知此事件。
ET模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll时,不会再次响应应用程序并通知此事件。
同时重要的一点便是对于epoll机制的理解,epoll的主要用处在于:
epoll_list = epoll.epoll()
如果进程在处理while循环中的代码时,一些套接字对应的客户端如果发来了数据,那么操作系统底层会自动的把这些套接字对应的文件描述符写入该列表中,当进程再次执行到epoll时,就会得到了这个列表,此时这个列表中的信息就表示着哪些套接字可以进行收发了。因为epoll没有去依次的查看,而是直接拿走已经可以收发的fd,所以效率高!