select 实现server I/O多路复用通信
- 非阻塞模式下客户端发生异常或服务器未处理完数据,就会立刻返回错误,不用等待
服务器端
- 建立连接
- 设置为非阻塞模式
import select,queue,socket server=socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(('localhost',2223)) server.listen(5) server.setblocking(False)
- 使用select进行监测
inputs=[server,]#一开始要先监测server,知道是否建立连接 outputs=[] readable, writable, excetional = select.select(inputs, outputs, inputs) # select 进行监听
- 判断最开始inputs里是否是server,是的话表示建立了连接
if r is server:#表示建立连接 conn,addr=server.accept() conn.setblocking(False)#非阻塞 inputs.append(conn)#select监测连接过来的实例 msg_dic[conn]=queue.Queue()#初始化一个队列,用于存放每一个连接的数据 print('建立连接',addr)
- 不是代表客户端开始发送数据,服务器接收数据并放入之前初始化的队列中
data=r.recv(1024) print(data.decode()) msg_dic[r].put(data)#向队列中存入数据 if r not in outputs: outputs.append(r)
- 向客户端发送数据,发送完毕从队列中移除连接
for w in writable: try: next_msg = msg_dic[w].get_nowait()#从队列中取元素,不等待 except queue.Empty: # print('output queue for', w.getpeername(), 'is empty') outputs.remove(w) else: # print('sending "%s" to %s' % (next_msg, w.getpeername())) w.send(next_msg)
- 异常处理
for e in excetional:#发生异常就将连接移除 inputs.remove(e) if e in outputs: outputs.remove(e) e.close() del msg_dic[e]#从队列中移除连接
服务器端完整代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#select模拟socketserver import select,queue,socket server=socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(('localhost',2223)) server.listen(5) server.setblocking(False) inputs=[server,]#一开始要先监测server,知道是否建立连接 outputs=[] msg_dic={} while True: readable, writable, excetional = select.select(inputs, outputs, inputs) # select 进行监听 for r in readable: if r is server:#表示建立连接 conn,addr=server.accept() conn.setblocking(False)#非阻塞 inputs.append(conn)#select监测连接过来的实例 '''新建立的连接还没发数据过来,现在就接收数据的话是空的,会报错, 因此要让select监测每一个连接过来的连接,这样才知道客户端发送数据过来''' msg_dic[conn]=queue.Queue()#初始化一个队列,用于存放每一个连接的数据 print('建立连接',addr) else: try: data=r.recv(1024)#不可以直接判断数据是否为空,因为客户端断开时服务器已经接收不到数据了 #有数据发送过来 print(data.decode()) msg_dic[r].put(data) if r not in outputs: outputs.append(r) # 放入返回的连接队列里,下一次循环select检测的时候发给客户端 except ConnectionResetError as e:#处理没有数据的情况,即服务器断开 print(addr,' is close...') if r in outputs:#移除连接 outputs.remove(r) inputs.remove(r) r.close() del msg_dic[r] break for w in writable: try: next_msg = msg_dic[w].get_nowait()#从队列中取元素,不等待 except queue.Empty: # print('output queue for', w.getpeername(), 'is empty') outputs.remove(w) else: # print('sending "%s" to %s' % (next_msg, w.getpeername())) w.send(next_msg) # outputs.remove(w)#从outputs中移除旧的连接 for e in excetional:#发生异常就将连接移除 inputs.remove(e) if e in outputs: outputs.remove(e) e.close() del msg_dic[e]#从队列中移除连接
注:判断客户端是否断开时,要用捕获异常来处理,不能直接判断服务器接收的数据是否为空,因为此时客户端已经断开了,服务器无法接收数据了,会报ConnectionResetError
客户端完整代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket client=socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(('localhost',2223)) while True: cmd=input(">>>") if cmd=='': continue client.send(cmd.encode()) data=client.recv(1024) print(data.decode())