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

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

 

1、select机制web

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

  2. 优势:几乎全部平台都支持,跨平台支持性较好。数组

  3. 缺点:服务器

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

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

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

  4. 水平触发:tcp

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

 

2、epoll

  epoll能够当作是select/poll(本质就是select)的增强版,打破了不少select的约束,以及添加了一些其余的功能!

  1. 为何epoll效率很高呢?

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

  2.原理:

    

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

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

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

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

  3. 优势:

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

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

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

  4. 误区:

    并非在任何状况下,epoll都要比select/poll高效,只有当不少链接请求到来时才会很高效!

 

3、epoll编程模型

  (1). 建立1个epoll对象

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

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

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

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

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

  (7). 销毁epoll对象

  

 

4、代码实现

 1 """
 2 利用非阻塞和epoll来实现一个服务器  3 """
 4 import socket  5 
 6 import select  7 
 8 
 9 class WebServer: 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     def run(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         while True: 32             # 4.监听epoll中哪一个fd发生了什么事件
33             epoll_list = epoll.poll() 34             for fd, event in epoll_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()] = client 43                 else: 44                     # 有客户端发送数据过来,可是该如何去得到这个客户端呢?
45                     data = client_dict[fd].recv(1024).decode('utf-8') 46                     if data: 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 in client_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 def main(): 69     #1.初始化一个TCP服务器
70     server = WebServer() 71     #2.运行一个服务器
72  server.run() 73 
74 
75 if __name__ == '__main__': 76     main()
相关文章
相关标签/搜索