最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Python selectors模块用法:实现非阻塞式编程

    正文概述    2020-01-03   274

    Python selectors模块用法:实现非阻塞式编程

    前面介绍的 socket 都是采用阻塞方式进行通信的,当程序调用 recv() 方法从 socket 中读取数据时,如果没有读取到有效的数据,当前线程就会被阻塞。为了解决这个问题,上面程序采用了多线程并发编程,即服务器端为每个客户端连接都启动一个单独的线程,不同的线程负责对应的 socket 的通信工作。

    通过 selectors 模块允许 socket 以非阻塞方式进行通信,selectors 相当于一个事件注册中心,程序只要将 socket 的所有事件注册给 selectors 管理,当 selectors 检测到 socket 中的特定事件之后,程序就调用相应的监听方法进行处理。

    selectors 主要支持两种事件:

    selectors.EVENT_READ:当 socket 有数据可读时触发该事件。当有客户端连接进来时也会触发该事件。

    selectors.EVENT_WRITE:当 socket 将要写数据时触发该事件。

    使用 selectors 实现非阻塞式编程的步骤大致如下:

    创建 selectors 对象。

    通过 selectors 对象为 socket 的 selectors.EVENT_READ 或 selectors.EVENT_WRITE 事件注册监听器函数。每当 socket 有数据需要读写时,系统负责触发所注册的监昕器函数。

    在监听器函数中处理 socket 通信。

    下面程序使用 selectors 模块实现非阻塞式通信的服务器端:

    import selectors, socket
    
    # 创建默认的selectors对象
    sel = selectors.DefaultSelector()
    # 负责监听“有数据可读”事件的函数
    def read(skt, mask):
        try:
            # 读取数据
            data = skt.recv(1024)
            if data:
                # 将读取的数据采用循环向每个socket发送一次
                for s in socket_list:
                    s.send(data)  # Hope it won't block
            else:
                # 如果该socket已被对方关闭,关闭该socket,
                # 并从socket_list列表中删除
                print('关闭', skt)
                sel.unregister(skt)
                skt.close()
                socket_list.remove(skt)
        # 如果捕捉到异常, 将该socket关闭,并从socket_list列表中删除
        except:
            print('关闭', skt)
            sel.unregister(skt)
            skt.close()
            socket_list.remove(skt)
    socket_list = []
    # 负责监听“客户端连接进来”事件的函数
    def accept(sock, mask):
        conn, addr = sock.accept()
        # 使用socket_list保存代表客户端的socket
        socket_list.append(conn)
        conn.setblocking(False)
        # 使用sel为conn的EVENT_READ事件注册read监听函数
        sel.register(conn, selectors.EVENT_READ, read)    #②
    sock = socket.socket()
    sock.bind(('192.168.1.88', 30000))
    sock.listen()
    # 设置该socket是非阻塞的
    sock.setblocking(False)
    # 使用sel为sock的EVENT_READ事件注册accept监听函数
    sel.register(sock, selectors.EVENT_READ, accept)    #①
    # 采用死循环不断提取sel的事件
    while True:
        events = sel.select()
        for key, mask in events:
            # key的data属性获取为该事件注册的监听函数
            callback = key.data
            # 调用监听函数, key的fileobj属性获取被监听的socket对象
            callback(key.fileobj, mask)

    上面程序中定义了两个监听器函数 accept() 和 read(),其中 accept() 函数作为“有客户端连接进来”事件的监听函数,主程序中的 ① 号代码负责为 socket 的 selectors.EVENT_READ 事件注册该函数;read() 函数则作为“有数据可读”事件的监听函数,如 accept() 函数中的 ② 号代码所示。

    通过上面这种方式,程序避免了采用死循环不断地调用 socket 的 accept() 方法来接受客户端连接,也避免了采用死循环不断地调用 socket 的 recv() 方法来接收数据。socket 的 accept()、recv() 方法调用都是写在事件监听函数中的,只有当事件(如“有客户端连接进来”事件、“有数据可读”事件)发生时,accept() 和 recv() 方法才会被调用,这样就避免了阻塞式编程。

    为了不断地提取 selectors 中的事件,程序最后使用一个死循环不断地调用 selectors 的 select() 方法“监测”事件,每当监测到相应的事件之后,程序就会调用对应的事件监听函数。

    下面是该示例的客户端程序。该客户端程序更加简单,客户端程序只需要读取 socket 中的数据,因此只要使用 selectors 为 socket 注册“有数据可读”事件的监听函数即可。

    import selectors, socket, threading
    
    # 创建默认的selectors对象
    sel = selectors.DefaultSelector()
    # 负责监听“有数据可读”事件的函数
    def read(conn, mask):
        data = conn.recv(1024)  # Should be ready
        if data:
            print(data.decode('utf-8'))
        else:
            print('closing', conn)
            sel.unregister(conn)
            conn.close()
    # 创建socket对象
    s = socket.socket()
    # 连接远程主机
    s.connect(('192.168.1.88', 30000))
    # 设置该socket是非阻塞的
    s.setblocking(False)
    # 使用sel为s的EVENT_READ事件注册read监听函数
    sel.register(s, selectors.EVENT_READ, read)    # ①
    # 定义不断读取用户键盘输入的函数
    def keyboard_input(s):
        while True:
            line = input('')
            if line is None or line == 'exit':
                break
            # 将用户的键盘输入内容写入socket
            s.send(line.encode('utf-8'))
    # 采用线程不断读取用户的键盘输入
    threading.Thread(target=keyboard_input, args=(s, )).start()
    while True:
        # 获取事件
        events = sel.select()
        for key, mask in events:
            # key的data属性获取为该事件注册的监听函数
            callback = key.data
            # 调用监听函数, key的fileobj属性获取被监听的socket对象
            callback(key.fileobj, mask)

    上面程序中的 ① 号代码为 socket 的 EVENT_READ 事件注册了 read() 监听函数,这样每当 socket 中有数据可读时,程序就会触发 read() 函数来读取 socket 中的数据。

    程序最后也采用死循环不断地调用 selectors 的 select() 方法“监测”事件,每当监测到相应的事件之后,程序就会调用对应的事件监听函数。

    先运行上面的服务器端程序,该程序运行后只是作为服务器,看不到任何输出信息。再运行多个客户端程序(相当于启动多个聊天室客户端登录该服务器)。接下来可以在任何一个客户端通过键盘输入一些内容,然后按回车键,即可在所有客户端(包括自己)的控制台上接收到刚刚输入的内容。这也是一个粗略的 C/S 结构的聊天室应用。


    起源地下载网 » Python selectors模块用法:实现非阻塞式编程

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元