python高级网络编程_Python高级网络编程系列之第三篇

本文详细介绍了网络编程中的IO模型,包括select机制及其局限性,以及更高效的epoll机制。select作为监听器,能跨平台且监听多个文件描述符,但存在最大文件描述符限制和线性扫描问题。epoll则是select的增强版,解决了这些问题,通过事件监听提高效率,支持无限制的连接数,并利用共享内存优化了资源消耗。文章还提供了使用Python实现epoll的服务器代码示例。
摘要由CSDN通过智能技术生成

在高级篇二中,我们讲解了5中常用的IO模型,理解这些常用的IO模型,对于编写服务器程序有很大的帮助,可以提高我们的并发速度!因为在网络中通信主要的部分就是IO操作。在这一篇当中我们会重点讲解在第二篇当中提到的IO复用模型,即select机制。其实select机制有一些缺陷,后来产生了一种更加高效的机制epoll,稍后会讲解!

一、select机制

1. 原理:select可以理解成一个监听器,可以监听多个文件描述符。当某个文件描述符的状态发生改变了(可读/可写),操作系统就会发送消息给应用程序,去处理数据。

2. 优点:几乎所有平台都支持,跨平台支持性较好。

3. 缺点:

(1). 当个进程/线程可监视的文件描述符数量有限制。

(2). 对文件描述符的扫描是线性的,采用轮询的方式,每次都是从头一直扫描到结尾,当文件描述符的列表变大时,会相当浪费时间和CPU

(3). 把包含大量文件描述符的数组从内核空间拷贝到用户空间,当数组小的时候可能还好,但是随着数组的增大会变得很浪费资源。

4. 水平触发:

当select()把状态发生变化的文件描述符报告给进程之后,如果进程没有进行任何处理,那么下次select()还会报告这些文件描述符。

二、epoll

epoll可以看成是select/poll(本质就是select)的加强版,打破了很多select的约束,以及添加了一些其他的功能!

1. 为什么epoll效率很高呢?

epoll最大的特点是只告诉服务器有哪些文件描述符(fd)发生了变化。如果服务器不去处理相应的fd,那么操作系统就会把这个fd丢弃,不再给服务器发送消息(边缘触发)!除此之外,epoll是采用事件监听的方式通知,这也是epoll的魅力所在!

2.原理:

1299353-20180518152225785-1693662190.png

(1). 注册在epoll中的文件描述符,操作系统的事件监听会去监听文件描述符集合(fd_set)

(2). 如果有fd发生了变化,那么事件监听会向操作系统报告发生变化的fd

(3). 操作系统会给服务器发送消息,通知它你关注的fd有变化,去处理吧

(4). 此时服务器就去共享内存中读取数据了!

3. 优点:

(1). 没有最大连接数的限制。

(2). 不采用轮询的方式去处理fd,而是采用事件监听的方式,即哪个fd有事件发生,OS通知服务器使用相应的回调函数来处理fd

(3). 内存拷贝:当有数据到来时,操作系统会给服务器发送通知去处理数据。通过采用共享内存的方式加快用户空间与内核空间消息的传递速度。

4. 误区:

并不是在任何情况下,epoll都要比select/poll高效,只有当很多连接请求到来时才会很高效!

三、epoll编程模型

(1). 创建1个epoll对象

(2). 告诉epoll对象,在指定的fd上监听指定的事件

(3). 询问epoll对象,自从上次查询后,哪些fd上发生了哪些事件

(4). 在这些fd上执行一些操作

(5). 告诉epoll对象,修改fd列表或注册事件,并监控

(6). 重复步骤3-5,直到完成

(7). 销毁epoll对象

1299353-20180518153751709-24052242.png

四、代码实现

1 """

2 利用非阻塞和epoll来实现一个服务器3 """

4 importsocket5

6 importselect7

8

9 classWebServer:10 """定义一个web服务器"""

11

12 def __init__(self):13 #1.创建TCP 服务器

14 self.tcp_server =socket.socket(socket.AF_INET, socket.SOCK_STREAM)15 #复用端口

16 self.tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)17 #2.绑定端口

18 self.tcp_server.bind(('', 6767))19 #3.设为被动套接字

20 self.tcp_server.listen(128)21

22 defrun(self):23 """运行一个服务器"""

24 #1.把服务器设置为非阻塞模式

25 self.tcp_server.setblocking(False)26 #2.创建一个epoll对象并为服务器注册一个可接受连接的事件

27 epoll =select.epoll()28 epoll.register(self.tcp_server.fileno(), select.EPOLLIN)29 client_dict = dict() #让fd与client建立关联

30 #3.服务器接受客户端的请求

31 whileTrue:32 #4.监听epoll中哪个fd发生了什么事件

33 epoll_list =epoll.poll()34 for fd, event inepoll_list:35 if fd ==self.tcp_server.fileno():36 #有客户端来连接被动套接字服务器

37 client, addr =self.tcp_server.accept()38 #print(addr)

39 #把客户端注册到epoll中

40 epoll.register(client.fileno(), select.EPOLLIN)41 #把客户端和客户端对应的fd添加到client字典中去

42 client_dict[client.fileno()] =client43 else:44 #有客户端发送数据过来,但是该如何去获得这个客户端呢?

45 data = client_dict[fd].recv(1024).decode('utf-8')46 ifdata:47 #说明客户端发送数据过来了

48 print(data)49 client_dict[fd].send('我已经收到你的数据了!\n'.encode('utf-8'))50 else:51 #说明客户端已经关闭了

52 client_dict[fd].close()53

54 client_dict.popitem()55 #需要把该客户端注册的事件取消掉

56 epoll.unregister(fd)57

58 #遍历client字典中每个客户端对应的fd

59 for item inclient_dict.items():60

61 print('fd:{}--->addr:{}'.format(item[0], item[1]))62 print('-'*50)63 #关闭服务器

64 self.tcp_server.close()65

66

67

68 defmain():69 #1.初始化一个TCP服务器

70 server =WebServer()71 #2.运行一个服务器

72 server.run()73

74

75 if __name__ == '__main__':76 main()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值