教程集 www.jiaochengji.com
教程集 >  Python编程  >  Python入门  >  正文 Python selectors模块用法:实现非阻塞式编程

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

发布时间:2021-12-03   编辑:jiaochengji.com
教程集为您提供Python selectors模块用法:实现非阻塞式编程等资源,欢迎您收藏本站,我们将为您提供最新的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 模块实现非阻塞式通信的服务器端:

<pre class="brush:html;toolbar:false">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)</pre>

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

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

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

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

<pre class="brush:html;toolbar:false">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)</pre>

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

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

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

您可能感兴趣的文章:
Python selectors模块用法:实现非阻塞式编程
如何在golang中关闭bufio.reader_golang 并发编程
Python之IO多路复用是什么
PHP-Socket-阻塞与非阻塞,同步与异步概念的理解
java中非阻塞算法深入分析
正在执行的goruntine发生阻塞,golang调度策略
Python sleep函数用法:线程睡眠
GO 互斥锁实现原理剖析
Python线程中的阻塞是什么?
django面试会问什么

[关闭]
~ ~