Python实现socket的非阻塞式编程
阻塞模式与非阻塞模式
阻塞模式 程序碰到了一些耗时操作,无法继续向下走。
例如在socket编程中,例如在send()
即发送信息过程中,可能对方已经断开,可能网络等原因导致信息传递不通畅;在客户端的connect()
函数中,可能地址不可达等原因。这些情况在阻塞模式中会造成线程中断等待,导致无法进行下一步操作,等超过一个固定时间还没有完成之后会产生异常。但是这种阻塞通常用于确定的几个连接地址并且必须准确连接上。
非阻塞模式 当程序碰到耗时操作,分发给别的线程,主线程继续执行。
例如在socket编程中,在send()
或connect()
函数中,程序会抛出异常10035,在非阻塞模式下无法完成耗时操作,但是程序会继续走下去,不会阻塞到当前的程序。那么,怎么判断什么时候程序完成这些耗时操作呢?select
闪亮登场。
基于select的网络编程
在python中,select
函数是一个队底层操作系统直接访问的接口,它用来监控sockets
、files
和pipse
,等待IO完成。当有可读、可写或者是异常事件产生时,select
函数可以监控到。
r, w, e, = select.select(rlist, wlist, xlist[, timeout]) 传递三个参数,一个为输入而观察的文件对象列表,一个为输出而观察的文件对象列表和一个观察错误异常的文件列表。第四个是一个可选参数,表示超时秒数。这个就是系统级别的阻塞,如果监控到事件会直接传递到这里。
具体的客户端非阻塞连接如下,使用的是
python3.6
# -*- coding: UTF-8 -*-
# python使用select进行非阻塞模式编程,客户端程序
import socket
import select
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 生成socket
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 不经过WAIT_TIME,直接关闭
sock.setblocking(False) # 设置非阻塞编程
try:
# sock.connect(("google.com", 80))
sock.connect(("192.168.1.106", 789))
except Exception as e:
print(e)
r_inputs = set()
r_inputs.add(sock)
w_inputs = set()
w_inputs.add(sock)
e_inputs = set()
e_inputs.add(sock)
while True:
try:
r_list, w_list, e_list = select.select(r_inputs, w_inputs, e_inputs, 1)
print("r") # 产生了可读事件,即服务端发送信息
for event in r_list:
try:
data = event.recv(1024)
except Exception as e:
print(e)
if data:
print(data)
print("收到信息")
else:
print("远程断开连接")
r_inputs.clear()
print("w")
if len(w_list) > 0: # 产生了可写的事件,即连接完成
print(w_list)
w_inputs.clear() # 当连接完成之后,清除掉完成连接的socket
print("e")
if len(e_list) > 0: # 产生了错误的事件,即连接错误
print(e_list)
e_inputs.clear() # 当连接有错误发生时,清除掉发生错误的socket
except OSError as e:
print(e)
非阻塞服务端代码如下,同样使用python3.6
# -*- coding: UTF-8 -*-
import socket
import select
sock = socket.socket()
sock.bind(('192.168.1.106', 789))
sock.setblocking(False)
sock.listen()
inputs = [sock, ]
while True:
r_list, w_list, e_list = select.select(inputs, [], [], 1)
for event in r_list:
if event == sock:
print("新的客户端连接")
new_sock, addr = event.accept()
inputs.append(new_sock)
else:
data = event.recv(1024)
if data:
print("接收到客户端信息")
print(data)
event.send(b'\x31')
else:
print("客户端断开连接")
inputs.remove(event)
python的select注意事项
除了select,还有什么提高效率的神器
通过上述文章可知效率对比 epoll > poll > select
但是在windows系统上只有select
,所以常常用以下判断
if hasattr(select, 'epoll'):
self._impl = select.epoll()
model = 'epoll'
elif hasattr(select, 'kqueue'):
self._impl = KqueueLoop()
model = 'kqueue'
elif hasattr(select, 'select'):
self._impl = SelectLoop()
model = 'select'
切记如果在非阻塞情况下缠上了10035
的错误,那是正常反应,我们只需用select
进行获取即可。(我不会告诉你我被这个坑折腾了好几天)