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


      在高级篇二中,我们讲解了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.原理:

        

        (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对象

      

    四、代码实现

     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('我已经收到你的数据了!
    '.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()
  • 相关阅读:
    第二十八节-3d 盒子(transform transition )炫酷操作
    第二十七节-动画animation以及与transform的冲突
    第二十六节-transform
    transition的属性与使用,绝对定位初始值要设0,以及淡入淡出,消失
    阿里图标与iframe框架
    第二十二节-表格
    第二十一节-表单元素2以及input一些使用习惯和伪类 点击按钮换图片且有淡入淡出的效果
    第二十节-重要表单(form 与 input) 、label 标签
    案例-京东小按钮
    复合写法需要注意的
  • 原文地址:https://www.cnblogs.com/fangtaoa/p/9055985.html
Copyright © 2020-2023  润新知