select简单示例,有注释
全部都在代码中:
import select import socket import queue """ 简单的select 实现echo server 个人理解: select 编程思想,让select去去判断能不能读或能不能写。 如果能读或能写,select就会告知readable或writeable,这时就得让文件句柄去读或写, 在这里是scoket,recv()或scoket.send()去执行实际的操作 """ server = socket.socket() # 获取一个套接字 server.setblocking(False) # 设置为非阻塞模式 server.bind(('127.0.0.1',9001)) # 绑定ip和端口 server.listen(5) # 启动监听,到这步可以在netstat 看到监听状态,如果没有server.accept()的话,客户端的连接会被拒绝 inputs = [ server ] # 设置输入队列,当server的监听收到客户端连接时,select会认为这个server为可读,就得让server.accpet()去接收客户端连接 # 或者当socket有新的数据发送过来时,select会认为这个socket是可读的,就得去让socket的recv()去接收数据 outputs = [] # 当socket可以发送数据给对端时,select会认定这个socket是 message_queue = {} # 消息队列,key值为socket套接字 n = 0 # 体现while循环了多少次 while inputs: # inputs中是否存在值 print("waiting for connect..., %s"%n) readable , writeable ,exceptions = select.select(inputs,outputs,inputs) # select就像一个非常强大的监视器,监视各个文件句柄的是否可读可写。 for s in readable: # 循环可读的。 if s is server: # 如果是server套接字 print("server can read...") conn, addr = s.accept() # 就接收客户端连接 print("welcome (%s,%s) to connect..."%addr) inputs.append(conn) # 将客户端套连接接字放入inputs,而不是马上接收或发生数据。 # s.setblocking(False) else: # 如果不是server套接字 print("a scoket can read:" ,s.getpeername()) # 只有当有数据发送过来时才会把那个套接字放在readable里 try: data = s.recv(1024) # 就表明客户端连接套接字收到了客户端发送来的数据 except Exception as e: # 客户端进程被强制关闭,导致客户端发送rst包。 ConnectionResetError print(e) inputs.remove(s) # 如果客户端退出,就讲这个套接字从inputs中移除 if s in outputs: # 如果这个套接字存在于outputs中,也将这个套接字从outputs中移除,因为客户端都关闭了 # 还将这个套接字放在outputs中没有了,发不了数据了啊。 outputs.remove(s) s.close() # 记得关闭这个套接字 break # 这里要break,因为s.recv(1024)异常了,data根本就没有被定义,下面的if data就会报错。 if data: # 如果有数据 print("recv from %s:%s"%(s.getpeername(),data.decode())) message_queue[s] = queue.Queue() # 将收到的数据放入消息队列中 message_queue[s].put(data) outputs.append(s) # 因为是echo server,所有有道数据后就得原有返回数据,但是这里不立即返回,而是交由select. else: print("the client (%s,%s) is closed "%s.getpeername()) # 如果data为空,表示客户端正常关闭连接,客户端发送了fin. if s in outputs: # 客户端关闭了,这个套接字也就没必要放在outputs和inputs中了 outputs.remove(s) inputs.remove(s) del message_queue[s] # 消息队列也可以清除 s.close() # 关闭套接字 for s in writeable: # 如果可写,“好像ouputs有连接的套接字 存在就是可写的。” try: data = message_queue[s].get_nowait() # 获取要发送的消息 except queue.Empty: # 消息队列为空也是可写的。 print("outpt queue for (%s,%s) in empty"%s.getpeername()) outputs.remove(s) # 因为是echo server,所以消息队列为空时,说明已经回应了数据给对端,就不用一直去判断这个套接字是否可写了。 # s.close() # 没有数据可发送并不代表要关闭socket else: print("send data to client (%s,%s)"%s.getpeername()) s.send(data) # outputs.remove(s) # 发送完一条数据后,其实不能将他移除,因为有可能对端非常迅速的发了多条消息过来,而服务器一条消息都还没来得及回复, # 服务器的消息将会都放在队列中等待发送,所以这里的让队列为空时在把套接字从outputs中移除。 for s in exceptions: # 如果发生错误 print("expect happend on (%s,%s)"%s.getpeername()) inputs.remove(s) # 将这个套接字从inouts,outputs中移除 if s in outputs: outputs.remove(s) s.close() # 连接要关闭 del message_queue[s] # 消息队列也要关闭 n += 1 # 看看while循环可多少次
waiting for connect..., 0 server can read... welcome (127.0.0.1,3137) to connect... waiting for connect..., 1 server can read... welcome (127.0.0.1,3138) to connect... waiting for connect..., 2 a scoket can read: ('127.0.0.1', 3137) # 这里说明第一个客户端套接字在readable里,下面也没看到第二个客户端在readable里直到第二个客户端发送数据过来,也就是说select只是把可读的返回给了readable里。 recv from ('127.0.0.1', 3137):this is the 1st client waiting for connect..., 3 send data to client (127.0.0.1,3137) waiting for connect..., 4 outpt queue for (127.0.0.1,3137) in empty waiting for connect..., 5 a scoket can read: ('127.0.0.1', 3138) recv from ('127.0.0.1', 3138):this is the 2nd client waiting for connect..., 6 send data to client (127.0.0.1,3138) waiting for connect..., 7 outpt queue for (127.0.0.1,3138) in empty waiting for connect..., 8 a scoket can read: ('127.0.0.1', 3138) recv from ('127.0.0.1', 3138):2nd haha waiting for connect..., 9 send data to client (127.0.0.1,3138) waiting for connect..., 10 outpt queue for (127.0.0.1,3138) in empty waiting for connect..., 11 a scoket can read: ('127.0.0.1', 3137) recv from ('127.0.0.1', 3137):1st hello waiting for connect..., 12 send data to client (127.0.0.1,3137) waiting for connect..., 13 outpt queue for (127.0.0.1,3137) in empty waiting for connect..., 14 a scoket can read: ('127.0.0.1', 3138) recv from ('127.0.0.1', 3138):2nd wiil killed by os waiting for connect..., 15 send data to client (127.0.0.1,3138) waiting for connect..., 16 outpt queue for (127.0.0.1,3138) in empty waiting for connect..., 17 a scoket can read: ('127.0.0.1', 3138) [WinError 10054] 远程主机强迫关闭了一个现有的连接。 waiting for connect..., 18 a scoket can read: ('127.0.0.1', 3137) recv from ('127.0.0.1', 3137):1st wiil closed by socket.close() waiting for connect..., 19 send data to client (127.0.0.1,3137) waiting for connect..., 20 outpt queue for (127.0.0.1,3137) in empty waiting for connect..., 21 a scoket can read: ('127.0.0.1', 3137) the client (127.0.0.1,3137) is closed waiting for connect..., 22
客户端:
import socket s = socket.socket() s.connect(('127.0.0.1',9001)) while True: data = input(">> ").strip() if data == "": continue if data == "q": s.close() break s.send(bytes(data,encoding="utf-8")) data = s.recv(1024) print("recv:",data.decode())