1. epoll的优点
1)没有最大并发连接的限制,能打开的FD(指的是文件描述符,通俗的理解就是套接字对应的数字编号)的上限远大于1024。
2)效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;即epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,epoll的效率就会远远高于select和poll。
3)epoll采用的事件通知机制
2. 文件描述符
1)linux内核(kernel)利用文件描述符(file descriptor)来访问文件。文件描述符是非负整数。打开现存文 件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。
2)文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进 程 打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述 符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往 只适用于UNIX、Linux这样的操作系统。Window没有。
3)习惯上,标准输入(standard input)的文件描述符是 0,标准输出(standard output)是 1,标准错误 ( standard error)是 2。尽管这种习惯并非Unix内核的特性,但是因为一些 shell 和很多应用程序都使用这种习 惯,因此,如果内核不遵循这种习惯的话,很多应用程序将不能使用。
3.水平触发 / 边缘触发
水平触发:将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用epoll时将再次报 告 这些文件描述符,这种方式称为水平触发.
边缘触发:只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将 不 会再次告知,这种方式称为边缘触发。
理论上边缘触发的性能要更高一些,但是代码实现相当复杂。
4. ET / LT
epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:
LT模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll时,会再次响应应用程序并通知此事件。直到你出来为止。
ET模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。下次将不会在通知。
ET要比LT效率高
5 . epoll使用代码
from socket import *
import select
def main():
#创建tcp服务器套接字
server_socket = socket(AF_INET,SOCK_STREAM)
#设置端口可以重用
server_socket.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
#绑定端口
server_socket.bind(("",9999))
#设置监听
server_socket.listen(5)
#用epoll设置监听收数据
epoll = select.epoll()
#把server_socket注册到epoll的事件监听中,如果已经注册过会发生异常
epoll.register(server_socket.fileno(),select.EPOLLIN|select.EPOLLET)
#装socket列表
socket_lists = {}
#装socket对应的地址
socket_address = {}
while True:
#返回套接字列表[(socket的文件描述符,select.EPOLLIN)],
# 如果有新的链接,有数据发过来,断开链接等都会解除阻塞
print("epoll.poll--111")
epoll_list = epoll.poll()#[(文件描述符,注册的事件)]
print("epoll.poll--222")
print(epoll_list)
for fd,event in epoll_list:
#有新的链接
if fd == server_socket.fileno():
print("新的客户fd==%s" % fd)
new_sokect,new_address = server_socket.accept()
#往字典添加数据
socket_lists[new_sokect.fileno()] = new_sokect
socket_address[new_sokect.fileno()] = new_address
#注册新的socket也注册到epoll的事件监听中
epoll.register(new_sokect.fileno(), select.EPOLLIN | select.EPOLLET)
elif event ==select.EPOLLIN:
print("收到数据了")
#根据文件操作符取出对应socket
new_sokect = socket_lists[fd]
address = socket_address[fd]
recv_data = new_sokect.recv(1024)
if len(recv_data) > 0:
print("已经收到[%s]:%s" % (str(address),recv_data.decode("gb2312")))
else:
#客户端端口,取消监听
epoll.unregister(fd)
#关闭链接
new_sokect.close()
print("[%s]已经下线" % str(address))
#关闭套接字链接
server_socket.close()
if __name__ == "__main__":
main()