• 11 非阻塞套接字与IO多路复用(进阶)


    1、非阻塞套接字

    第一部分 基本IO模型

    1.普通套接字实现的服务端的缺陷

    一次只能服务一个客户端!

    2.普通套接字实现的服务端的瓶颈!!!

    accept阻塞!

    在没有新的套接字来之前,不能处理已经建立连接的套接字的请求。

    recv 阻塞!

    在没有接受到客户端请求数据之前,

    不能与其他客户端建立连接!

    3.普通服务器的IO模型

    第二部分 非阻塞套接字

    1.非阻塞套接字与普通套接字的区别
    >>> import socket
    >>> server = socket.socket()
    # 讲socket设置成非阻塞
    >>> server.setblocking(False)   # 注意!这必须要在其他操作之前!
    >>> server.bind(('',8080))
    >>> server.listen(5)
    ​
    >>> server.accept()     # 没有连接就引发BlockingIOError
    Traceback (most recent call last):
      File "<pyshell#5>", line 1, in <module>
        server.accept()
      File "E:pythonlibsocket.py", line 205, in accept
        fd, addr = self._accept()
    BlockingIOError: [WinError 10035] 无法立即完成一个非阻止性套接字操作。
    ​
    # 使用一个客户端(普通的就行,不需要非阻塞)连接过来
    >>> conn,addr = server.accept()     # 有连接则正确返回
    >>> conn.recv(1024)     # 没有连接数据就引发BlockingIOError
    Traceback (most recent call last):
      File "<pyshell#9>", line 1, in <module>
        conn.recv(1024)
    BlockingIOError: [WinError 10035] 无法立即完成一个非阻止性套接字操作。
    2.使用非阻塞套机字实现阻塞的服务端
    # 原来的recv
    while True:
        try:
            recv_data = conn.recv(1024)
            break
        except BlockingIOError:
            pass
            
    # 原来的accept
    while True:
        try:
            conn,addr = server.accept()
            break
        except:BlockingIOError:
            pass
    3。非阻塞客户端套接的注意点

    connect操作一定会引发BlockingIOError异常

    如果连接没有建立,那么send操作引发OSError异常

    第三部分 非阻塞IO模型

    第四部分 使用非阻塞套接字实现并发

    1.整体思路

    吃满 CPU !宁可用 whileTrue ,也不要阻塞发呆!

    只要资源没到,就先做别的事!将代码顺序重排,避开阻塞!

    2.实现了什么?

    并发服务多个客户端!

    3.编程范式
    import socket
    server = socket.socket()        # 生成套接字
    server.setblocking(False)       # 非阻塞
    server.bind(('',7788))
    server.listen(1000)
    ​
    # 我们现在生成的非阻塞套接字,非阻塞套接字在执行accept跟recv的时候不会阻塞,但是会报错,
    # 所以我们写非阻塞的并发服务器需要用到异常处理
    ​
    all_conn = []   # 用来保存我们所有的已经生成的套接字(这个客户端还在连接着)
    while True:
        # 处理连接,生成对等连接套接字
        try:
            conn,addr = server.accept()
            conn.setblocking(False)     # conn是新生成的,需要给它设置一下非阻塞
            all_conn.append(conn)   # 这一行代码的前提是,上面一行代码正常返回
        except BlockingIOError:
            pass
        for conn in all_conn:
            try:    # 只负责接受数据
                recv_data = conn.recv(1024)
    ​
                if recv_data:
                    res = recv_data.decode()
                    print(res)
                    conn.send(recv_data)
                else:
                    conn.close()
                    all_conn.remove(conn) # 客户端关闭连接,就把它移除
            except BlockingIOError:
                pass

    2、IO多路复用

    第一部分 不完美的CPU利用率

    关键一: 任何Python操作都是需要花费CPU资源的 !

    关键二: 如果资源还没有到达,那么

    ​ accept、recv以及

    ​ send(在connect没有完成时)

    ​ 操作都是无效的CPU花费 !

    关键三: 对应BlockingIOError的异常处理

    ​ 也是无效的CPU花费 !

    第二部分 epoll是真正的答案!

    IO多路复用技术

    我们把socket交给操作系统去监控

    2.epoll是惰性的事件回调

    惰性事件回调是由用户进程自己调用的。

    操作系统只起到通知的作用。

    3.为什么是epoll ?

    目前Linux上效率最高的IO多路复用 技术 !

    第三部分 IO多路复用选择器

    1.注册惰性事件回调
    >>> import socket
    >>> import selectors
    >>> server = socket.socket()
    >>> server.bind(('',9000))
    >>> server.listen(1000)
    >>> selector = selectors.EpollSelector()    # 实例化一个 epoll 选择器
    >>> def create_conn(server):
    ...     conn,addr = server.accept()
    ...     return conn
    ... 
    # 套接字、事件、回调函数
    >>> selector.register(server,selectors.EVENT_READ,create_conn)  
    SelectorKey(fileobj=<socket.socket fd=3,    # 生成一个打包对象
    family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 9000)>,   # fileobj是对应套接字
    fd=3,
    events=1,   # 事件(1 表示EVENT_READ)
    data=<function create_conn at 0xb70b7b24>)  # data是对应的回掉函数
    2.事件回调
    events = selector.select()      # 查询,返回所有已经准备好的资源打包对象
    print(enents)       # 是一个 ‘二元组’ 的列表
    [(SelectorKey(fileobj=<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 8888)>, fd=4, events=1, data=<function accept at 0xb71f292c>), 1)]
    # 我们只需要关心,每个元祖的第一项(即打包对象,其中包含了对应的套接字与回掉函数)
    # 接下来并不需要关心是什么套接字,什么事件,只需要调用对应的对调函数即可
    callbeck = events[0][0].data
    sock = events[0][0].fileojb
    callbeck(sock)
    3.编程范式
    # 使用EpollSelector,实现一个并发的服务器
    import socket
    import selectors    # IO多路复用选择器的模块,接口,调用epoll
    ​
    epoll_selector = selectors.EpollSelector()  # 创建一个用来和epoll通信的选择器
    ​
    server = socket.socket()
    server.bind(('',8888))
    server.listen(1000)
    ​
    def read(conn):
        recv_data = conn.recv(1024)
        if recv_data:
            print(recv_data.decode())
            conn.send(recv_data)
        else:
            epoll_selector.unregister(conn)     # 现在数据已经传输完了,那我现在就不用再去监控它了,所以# 关闭监控
            conn.close()    # 关闭连接
    def accept(server):
        conn, addr = server.accept()    # 生成一个对等连接套接字
        # 要准备接受数据
        epoll_selector.register(conn, selectors.EVENT_READ, read)
    ​
    epoll_selector.register(server,selectors.EVENT_READ, accept)   # 注册事件在可以读的时候的回调函数
    # 事件循环(主动去问epoll,哪些socket可以回调了,如果有了,那我就回调)
    while True:
        events = epoll_selector.select()     # 查询所有的已经准备好的事件,返回一个列表(二元组列表)
        # a, b = events
        for key, mask in events:    # 第一项是我们需要用的
            callback = key.data    # 从key里面把回掉函数拿出来
            sock = key.fileobj     # 从key里面把我们注册的那个socket拿出来
            callback(sock)
  • 相关阅读:
    WebAssembly学习(四):AssemblyScript
    Ramda
    React—生命周期
    网络拓扑图
    手机端的meta信息
    面试题
    Bootstrap路径导航
    Bootstrap 分页翻页
    Bootstrap栅格系统
    Bootstrap 屏幕类型
  • 原文地址:https://www.cnblogs.com/zcmq/p/9275875.html
Copyright © 2020-2023  润新知