selec 实现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]#从队列中移除连接

服务器端完整代码

#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]#从队列中移除连接
View Code

注:判断客户端是否断开时,要用捕获异常来处理,不能直接判断服务器接收的数据是否为空,因为此时客户端已经断开了,服务器无法接收数据了,会报ConnectionResetError

客户端完整代码

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())
View Code

猜你喜欢

转载自www.cnblogs.com/Aprilnn/p/9318306.html
今日推荐