epoll并发服务器
单进程非阻塞服务器和select服务器都以轮询的方式获取就绪套接字,当服务器程序中的连接数较多时,一趟轮询便会耗费大量的时间,服务器的效率也因此而逐渐降低。
在信息科技飞速发展的今天,一个小型网站的并发量已远远超过1024,显然单进程非阻塞服务器和select服务器都很难满足服务器搭建的实际需求。为了克服select服务器连接数量的限制,人们研发了一种名为poll的服务器,然而,这种服务器除了能解决连接上限的问题外,其它方面与select基本没有区别,它的工作效率同样会随着连接数量的增高而降低。
那么是否有兼顾连接数量与效率的服务器呢?答案是肯定的。
epoll服务器是Linux系统中常用的一种高效服务器,这种服务器采用事件通知机制,事先为要建立连接的socket注册事件,一旦该socket就绪,注册事件将被触发,socket将被加入epoll的就绪套接字列表,而服务器无需主动监测所有套接字状态,只需直接获取就绪套接字列表,对其中的套接字进行处理即可。
Python中的epoll模式定义在select模块中,select中包含一个名为epoll的类,用户可先在程序中创建epoll对象,再通过epoll对象的方法实现epoll模式。
在程序中定义epoll对象的方法如下:
import select
epoll = select.epoll()
epoll模式中包含了两个重要操作,一是事件注册,二是就绪套接字获取,这两个重要操作分别通过epoll对象的register()方法和poll()方法实现。
1. register()方法
register()方法的功能是为其参数fd创建注册事件,该方法的语法格式如下:
register(fd[, eventmask])
register()方法中的第一个参数fd是一个文件描述符,文件描述符是Linux系统中定义的,用于在进程或主机中标识一个唯一确定的已打开文件的符号,程序打开文件时,内核会向进程返回一个文件描述符。
Linux系统将套接字视为一种特殊文件,使用套接字方法fileno()可获取套接字的文件描述符,文件描述符本质上是一个整数。调用register()方法可为文件描述符fd指向的套接字注册事件。
register()方法的第二个参数eventmask是可选参数,该参数用于设置epoll要监控的事件和epoll的工作模式,事件是由epoll常量组成的位,其默认值为EPOLLIN | EPOLLOUT | EPOLLPRI,表示同时监控fd的读事件、写事件和紧急可读事件。该参数可用的epoll常量及其表示的含义分别如下:
EPOLLERR表示监控fd的错误事件;
EPOLLHUP表示监控fd的挂断事件;
EPOLLET表示将epoll设置为边缘触发(Edge Triggered)模式;
EPOLLONESHOT表示只监听一次事件,当此次事件监听完成后,若要再次监听该fd,需将其再次添加到epoll队列中。
register()方法没有返回值。使用epoll中的unregister()方法可以将套接字从监听列表中移除。
2. poll()方法
poll()方法的功能是查询epoll对象,判断是否有epoll关注的事件被触发。poll()方法的语法格式如下:
poll([timeout=-1[, maxevents=-1]])
poll()中的参数都是可缺省参数,其中timeout用于设置等待时长,其默认值-1表示无限等待。若在调用poll()方法前已有epoll监测的事件发生,此次查询会立刻返回一个列表,该列表中的元素为形如(fd,event code)的元组。
以大小写转换为例,搭建基于epoll模式的TCP服务器,服务器的具体代码如下。
1 import socket
2 import select
3 def main():
4 # 创建套接字
5 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
6 # 绑定本机信息
7 server_socket.bind(("", 8080))
8 # 重复使用绑定的信息
9 server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
10 # 变为被动
11 server_socket.listen(10)
12 # 设置套接字为非阻塞模式
13 server_socket.setblocking(False)
14 # 创建一个epoll对象
15 epoll = select.epoll()
16 # 为服务器端套接字server_socket的文件描述符注册事件
17 epoll.register(server_socket.fileno(),
18 select.EPOLLIN | select.EPOLLET)
19 new_socket_list = {} # 第19行
20 client_address_list = {} # 第20行
21 # 循环等待数据到达
22 while True:
23 # 检测并获取epoll监控的已触发事件
24 epoll_list = epoll.poll()
25 # 对事件进行处理
26 for fd, events in epoll_list:
27 # 如果有新的连接请求递达
28 if fd == server_socket.fileno():
29 new_socket, client_address = server_socket.accept()
30 print('有新的客户端到来%s'%str(client_address))
31 # 保存新客户端的套接字信息和地址信息。第31行
32 new_socket_list[new_socket.fileno()] = new_socket
33 client_address_list[new_socket.fileno()] = client_address
34 # 为新套接字的文件描述符注册读事件
35 epoll.register(new_socket.fileno(),
36 select.EPOLLIN | select.EPOLLET)
37 elif events == select.EPOLLIN:
38 # 从new_socket触发的事件
39 recv_data = new_socket_list[fd].recv(1024)
40 # 若数据长度大于0,处理数据
41 if len(recv_data) > 0:
42 print('待处理数据%s'%recv_data.decode('gb2312'))
43 recv_data = recv_data.upper()
44 new_socket.send(recv_data)
45 # 若数据长度为0,关闭连接
46 else:
47 # 从epoll中移除fd
48 epoll.unregister(fd)
49 # 关闭服务器端为该连接创建的套接字
50 new_socket_list[fd].close()
51 print("%s---offline---" % str(client_address_list[fd]))
52 if __name__ == '__main__':
53 main()
以上程序的第19、20两行代码创建了字典new_socket_list和client_address_list,分别用于存储与客户端交互的套接字信息和客户端地址;第32、33两行代码以套接字的文件描述符作为key值,将新客户端的套接字和地址信息存储到了new_socket_list和client_address_list中。
为了测试该程序的功能,下面实现一个可向服务器发送数据,并能接收服务器反馈信息的客户端程序,客户端程序代码如下。
from socket import *
def main():
client_socket = socket(AF_INET, SOCK_STREAM)
client_socket.connect(('192.168.255.144', 8080))
while True:
data = input('------待处理数据------\n')
client_socket.send(data.encode('gb2312'))
recv_info = client_socket.recv(1024).decode('gb2312')
print('------处理结果------\n%s'%recv_info)
server_socket.close()
if __name__ == '__main__':
main()
启动服务器之后再启动客户端,服务器中将打印客户端的地址信息,本次测试共启动了4个客户端,客户端都启动后,服务器中打印的信息如下:
有新的客户端到来:('192.168.255.144', 10268)
有新的客户端到来:('192.168.255.144', 10269)
有新的客户端到来:('192.168.255.144', 10275)
有新的客户端到来:('192.168.255.144', 10281)
由此可知,本小节搭建的基于epoll模型的TCP服务器可同时与多个客户端建立连接。
使用客户端向服务器中发送数据,客户端中打印的信息如下所示:
-----待处理数据------
hello itheima
------处理结果-------
HELLO ITHEIMA
-----待处理数据------
由此可知,本小节搭建的基于epoll模型的TCP服务器可成功接收客户端发送的数据,并将数据的处理结果返回给客户端。
综上所述,可知基于epoll模型的TCP并发服务器实现成功。
Windows系统中利用IOCP(完成端口)可实现与Linux下epoll相同的功能,有兴趣的读者可自行查阅资料学习。