python epoll 并发_Python使用epoll实现高并发服务器

一、什么是epoll

我们在 Python多种方式实现并发的Web Server 的最后使用单进程+单线程+非阻塞+长连接实现了一个可并发处理客户端连接的服务器。他的原理可以用以下的图来描述:

20191129230003805536.png

解释:

1.HTTP服务器是我们使用 单进程+单线程+非阻塞+长连接实现 的web服务器。

2.在实现的时候,我们创建了一个存放已接受Socket连接的列表,该列表是在应用程序的内存空间中的。如图中深蓝色部分

3.当有3个客户端接入的时候,列表中一共存在3个对应的socket句柄,分别对应三个小黄框。

4.灰色小框代表服务器接收请求的socket。

5.我们在进行无限循环的时候,首先是检查是否有新的客户端接入,相当于检查灰色小框是否有数据到达。然后轮询3个小黄框对应socket是否有数据到达。轮询的效率是很低的。

6.服务器在使用accept和recv时,实际上是委托操作系统帮他检查是否有数据到达,由于这个列表的socket都处于用户内存空间,所以需要将其复制到内核空间。操作系统检查完毕后,如果有数据就返回数据给应用程序,如果没有数据就以异常的方式通知应用程序。而且不光这样,操作系统可能还同时在运行其他的应用程序,这样效率会非常低。

我们再来看epoll的图:

20191129230004472553.png

解释:

1.我们可以看到,在结构上,最大的区别在于,存放socket的列表不处于应用程序内部。在epoll中,这个存放socket的列表处于一个特殊的内存空间,这个内存空间是应用程序与内核共享的空间。也就是说,当应用程序委托操作系统检查是否有数据到达时,无需将复制数据给内核空间,操作系统可以直接进行检查。

2.操作系统检查到某个socket有数据到达,使用事件通知的形式,直接告诉应用程序,而不是以轮询的方式。打个比方,一个厨师挨个问50个人饿了没,如果饿了就给他东西吃,这是轮询。而50个人中,谁饿了谁举手,厨师就给吃的,这叫事件通知。很明显,事件通知的效率会特别高。

实现代码:

importsocketimportreimportselectdefhandle_request(new_socket, recv_msg):#从请求中解析出URI

recv_lines =recv_msg.splitlines()#使用正则表达式提取出URI

ret = re.match(r"[^/]+(/[^ ]*)", recv_lines[0])ifret:#获取URI字符串

file_name = ret.group(1)#如果URI是/,则默认返回index.html的内容

if file_name == "/":

file_name= "/index.html"

try:#根据请求的URI,读取相应的文件

fp = open("." + file_name, "rb")except:#找不到文件,响应404

response_msg = "HTTP/1.1 404 NOT FOUND\r\n"response_msg+= "\r\n"response_msg+= "

----file not found----

"new_socket.send(response_msg.encode("utf-8"))else:

html_content=fp.read()

fp.close()

response_body=html_content#响应正确 200 OK

response_header = "HTTP/1.1 200 OK\r\n"response_header+= "Content-Length:%d\r\n" %len(response_body)

response_header+= "\r\n"response= response_header.encode("utf-8") +response_body#返回响应数据

new_socket.send(response)defmain():#创建TCP SOCKET实例

tcp_server_socket =socket.socket(socket.AF_INET, socket.SOCK_STREAM)## 设置重用地址

#tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

#绑定地址(默认本机IP)和端口

tcp_server_socket.bind(("", 7890))#监听

tcp_server_socket.listen(128)#将accept设置为非阻塞,这里设置一次,后面不管调多少次accept都是非阻塞的

tcp_server_socket.setblocking(False)#创建一个epoll对象

epl =select.epoll()#将监听套接字对应的fd注册到epoll中,并让其监听有没有数据进来,所以使用EPOLLIN

epl.register(tcp_server_socket.fileno(), select.EPOLLIN)#定义一个字典,用于存放fd和套接字的对应关系,因为操作系统在事件通知的时候,使用的是fd,而不是套接字,我们需要使用fd来找到对应

#的套接字,从而可以调用accept和recv

fd_event_dict =dict()#循环接收客户端连接

whileTrue:#使用一个列表来接受操作系统的事件通知,poll()是阻塞的,当有数据到达时,poll才会解开阻塞

fd_event_list =epl.poll()#操作系统的事件通知返回一个列表(可能同时有多个套接字有数据进入),这个列表中的元素都是元组(fd,event)

for fd, event infd_event_list:#首先判断事件通知中的fd是否对应监听套接字(监听套接字调用accept)

if fd ==tcp_server_socket.fileno():

new_socket, client_addr=tcp_server_socket.accept()#监听到一个新的客户端连接,将new_socket也注册到epoll中

epl.register(new_socket.fileno(), select.EPOLLIN)#并且将这个socket加入fd_event_dict字段,方便以后通过fd来获取套接字

fd_event_dict[new_socket.fileno()] =new_socketelif event == select.EPOLLIN: #如果不是监听套接字,那么都是客户端对应的套接字

#接收数据

recv_data = fd_event_dict[fd].recv(1024).decode("utf-8")#如果有数据

ifrecv_data:#处理数据

handle_request(fd_event_dict[fd], recv_data)else: #如果没有数据,则表示客户端断开连接

#关闭fd对应的socket

fd_event_dict[fd].close()#从epoll中踢出已经断开的fd

epl.unregister(fd)#从字典中删除fd对应的记录

delfd_event_dict[fd]#关闭整个SOCKET

tcp_server_socket.close()if __name__ == "__main__":

main()

解释:

1.首先创建epoll对象

2.将监听套接字对应fd注册到epoll,并设置监听数据的IN。

3.调用poll()函数,如果没有数据到达,则处于阻塞状态,如果有数据到达,则操作系统会返回一个事件通知列表。

4.遍历列表,如果发现fd是监听套接字对应fd,则使用监听套接字调用accept,并将接收到的新的客户端连接对应socket也注册到epoll中,并将其存放到字典fd_event_dict中(方便后续使用fd获取socket)。

5.如果不是监听套接字,则直接从fd_event_dict中通过fd获取对应的socket,然后调用recv来接收数据。

6.如果接收到的数据有内容,则调用请求处理逻辑。

7.如果接收到的数据为空,则表示客户端主动调用了close,想要断开连接。此时从fd_event_dict中通过fd获取对应socket,然后调用socker.close()来关闭连接。

8.关闭连接后,将该socket从epoll中剔除,并且从fd_event_dict中删除。

注意:该代码无法在windows上运行,因为epoll是Linux2.6内核增加的新功能,windows并不支持。

原文:https://www.cnblogs.com/leokale-zz/p/11960731.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值