使用select异步IO实现socketserver服务器 源码剖析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | #_*_coding:utf-8_*_ #这是一个echo server,客户端消息,服务端回复相同的消息 import select, socket, sys, queue # Create a TCP/IP socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #对socket进行实例化,拿到server句柄 server.setblocking( False ) #设置server为不阻塞 # Bind the socket to the port server_address = ( 'localhost' , 10000 ) #将主机名,端口号赋值给server_address print (sys.stderr, 'starting up on %s port %s' % server_address) #如有错误信息,输出,并打印strting up... server.bind(server_address) #绑定服务器ip端口号给socket实例化后的句柄 # Listen for incoming connections server.listen( 5 ) #允许最大监听链接5 # Sockets from which we expect to read inputs = [ server ] #设置客户端和服务端的socket句柄列表,后续都会append到其中 # Sockets to which we expect to write outputs = [ ] #设置echo给客户端的socket句柄,后续会append到其中 message_queues = {} #设置消息队列字典,后续存放 while inputs: #维护所有服务端和客户端过来的连接 # Wait for at least one of the sockets to be ready for processing print ( '\nwaiting for the next event' ) readable, writable, exceptional = select.select(inputs, outputs, inputs) #执行select.select方法,将输入的句柄、输出的句柄和错误信息,分别赋值给readable、writeable、exceptional # Handle inputs for s in readable: #遍历服务端和客户端请求过来的socket句柄 if s is server: #如果是客户端连接的句柄 # A "readable" server socket is ready to accept a connection connection, client_address = s.accept() #接收连接请求 print ( 'new connection from' , client_address) #打印新请求连接信息 connection.setblocking( False ) #设置为连接非阻塞,否则即使将会hang住 inputs.append(connection) #将客户端连接的句柄append到inputs列表,下一次select的时候能够检测这个连接 # Give the connection a queue for data we want to send message_queues[connection] = queue.Queue() #连接进来后,把发的消息放到这个队列;以连接的句柄作为key,来以字典形式存放队列信息,保证每个连接的句柄发送的数据都是隔离的 else : #如果是服务端连接的句柄 data = s.recv( 1024 ) #接收客户端发来的数据 if data: #如果客户端发过来的数据不为空 # A readable client socket has data print (sys.stderr, 'received "%s" from %s' % (data, s.getpeername()) ) #如果有错误,打印错误信息 message_queues[s].put(data) #放到以客户端socket的句柄为key的字典里,数据作为value存放 # Add output channel for response if s not in outputs: 如果当前客户端的连接句柄没有在outputs这个待发送的列表中 outputs.append(s) #先不给客户端发送其发来的数据,因为如果客户端没有空闲,就会造成阻塞。先把要发送的数据append到outputs自己维护的这个列表,下次select的时候,这个列表就不为空了,那个时候再给客户端发送数据 else : #如果客户端发过来的数据是空 # Interpret empty result as closed connection print ( 'closing' , client_address, 'after reading no data' ) #打印关闭信息 # Stop listening for input on the connection if s in outputs: #如果当前客户端的句柄,在要回复数据的列表中 outputs.remove(s) #既然客户端都断开了,我就不用再给它返回数据了,所以这时候如果这个客户端的连接对象还在outputs列表中,就把它删掉 inputs.remove(s) #inputs中也删除掉 s.close() #把这个连接关闭掉 # Remove message queue del message_queues[s] #删掉客户端这个socket发过来的数据 # Handle outputs for s in writable: #这是要给客户端回复数据的列表,这列表是自己维护的 try : next_msg = message_queues[s].get_nowait() #取出当前客户端的socket句柄对应的消息 except queue.Empty: #如果这个客户端的socket句柄对应的消息是空的话 # No messages waiting so stop checking for writability. print ( 'output queue for' , s.getpeername(), 'is empty' ) #打印空队列消息 outputs.remove(s) #移除这个客户端的socket句柄 else : #如果没有报错的话 print ( 'sending "%s" to %s' % (next_msg, s.getpeername())) #打印发送数据 s.send(next_msg) #服务器真正给客户端发送之前客户端发过来的数据 # Handle "exceptional conditions" for s in exceptional: #在客户端断开的情况下,会赋值给exceptional print ( 'handling exceptional condition for' , s.getpeername() ) #打印错误信息 # Stop listening for input on the connection inputs.remove(s) #删除客户端socket对应的句柄 if s in outputs: #如果在要发的消息列表中存在客户端的socket句柄 outputs.remove(s) #删除客户端的socket句柄 s.close() #服务器关闭这个客户端先断开的连接 # Remove message queue del message_queues[s] #删除掉客户端socket句柄对应的消息 |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步