官方提供了socketserver包去方便我们快速的搭建一个服务器框架。
server类
socketserver包提供5个Server类,这些单独使用这些Server类都只能完成同步的操作,他是一个单线程的,不能同时处理各个客户端的请求,只能按照顺序依次处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 | +------------+ | BaseServer | +------------+ | v +-----------+ +------------------+ | TCPServer |------->| UnixStreamServer | +-----------+ +------------------+ | v +-----------+ +--------------------+ | UDPServer |------->| UnixDatagramServer | +-----------+ +--------------------+ |
两个Mixin类
1 2 3 | +--------------+ +----------------+ | ForkingMixIn | | ThreadingMixIn | +--------------+ +----------------+ |
各自实现了多进程和多线程的功能(ForkingMixIn在Windows不支持)
于是将这些同步类和Mixin类组合就实现了异步服务类的效果。
class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
class ThreadingTCPServer(ThreadingMixIn, TCPServer): passclass ForkingUDPServer(ForkingMixIn, UDPServer): pass
class ForkingTCPServer(ForkingMixIn, TCPServer): pass
基本使用
由于server需要同时处理来自多个客户端的请求,需要提供异步的支持,所以通常使用上面的异步类创建服务器。在Windows系统中没有提供os.fork()接口,Windows无法使用多进程的ForkingUDPServer和ForkingTCPServer,只能使用ThreadingTCPServer或者ThreadingUDPServer;而Linux和Unix多线程和多进程版本都可以使用。
服务器主要负责接受客户端的连接请求,当一个新的客户端请求到来后,将分配一个新的线程去处理这个请求(异步服务器ThreadingTCPServer),而与客户端信息的交互则交给了专门的请求处理类(RequestHandlerClass)处理。
1 2 3 4 | import socketserver # 创建一个基于TCP的server对象,并使用BaseRequestHandler处理客户端发送的消息 server = socketserver.ThreadingTCPServer(( "127.0.0.1" , 8000 ), BaseRequestHandler) server.serve_forever() # 启动服务器, |
只需要上面两行代码就可以创建开启一个服务,运行上面代码后常看本机8000端口,发现有程序正在监听。
C:\Users\user>netstat -anp tcp | findstr 8000
TCP 127.0.0.1:8000 0.0.0.0:0 LISTENING
ThreadingTCPServer可以对我们的请求进行接受,但是并不会进行处理请求,处理请求的类是上面指定BaseRequestHandler类,该类可以定义handle方法来处理接受的请求。
BaseRequestHandler的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class BaseRequestHandler: def __init__( self , request, client_address, server): self .request = request self .client_address = client_address self .server = server self .setup() try : self .handle() finally : self .finish() def setup( self ): pass def handle( self ): pass def finish( self ): pass |
在server = socketserver.ThreadingTCPServer(("127.0.0.1", 8000), BaseRequestHandler)中,BaseRequestHandler将作为参数绑定到服务器的实例上,服务器启动后,每当有一个新的客户端接接入服务器,将会实例化一个请求处理对象,并传入三个参数,request(连接客户端的socket)、client_address(远程客户端的地址)、server(服务器对象),执行init方法,将这三个参数保存到对应属性上。这个请求处理对象便可以与客户端交互了。
简单示例
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 | import socketserver import threading class MyRequestHandler(socketserver.BaseRequestHandler): """ BaseRequestHandler的实例化方法中,获得了三个属性 self.request = request # 该线程中与客户端交互的 socket 对象。 self.client_address # 该线程处理的客户端地址 self.server = server # 服务器对象 """ def handle( self ): while True : msg = self .request.recv() # 接受客户端的数据 if msg = = b "quit" or msg = = "": # 退出 break print (msg.decode()) self .request.send(msg) # 将消息发送回客户端 def finish( self ): self .request.close() # 关闭套接字 if __name__ = = "__main__" : # 创建一个基于TCP的server对象,并使用BaseRequestHandler处理客户端发送的消息 server = socketserver.ThreadingTCPServer(( "127.0.0.1" , 8000 ), MyRequestHandler) server.serve_forever() # 启动服务器 |
我们创建了一个ThreadingTCPServer服务器,然后在传入的处理类MyRequestHandler,并在handle方法中提供与客户端消息交互的业务逻辑,此处只是将客户端的消息返回客户端。最后我们在finish方法中关闭资源,finish方法使用了finally机制,保证了这些代码一定会执行。
上一篇使用socket实现了一个群聊服务器,这个里使用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 | class MyRequestHandle(BaseRequestHandler): clients = {} # 在类属性中记录所有与客户端连接socket。 lock = threading.Lock() # 互斥锁,各个线程共用 def setup( self ): # 新的用户连接时,预处理,将这个新的连接加入到clients中,考虑线程安全,需要加锁 with self .lock: self .clients[ self .client_address] = self .request def handle( self ): # 处理客户端的请求主逻辑 while True : data = self .request.recv( 1024 ).strip() # 接受数据 if data = = b "quit" or data = = b"": # 客户端退出 with self .lock: self .server.clients.pop( self .client_address) self .request.close() break print ( "{}-{}: {}" . format ( * self .client_address, data.decode())) with self .lock: for _, c in self .server.clients.items(): # 群发 c.send(data) def finish( self ): with server.lock: for _, c in server.clients.items(): c.close() server.server_close() def main(): server = ThreadingTCPServer(( "127.0.0.1" , 8000 ), MyRequestHandle) # 将创建的所有线程设置为daemon线程,这样控台主程序退出时,这个服务器的所有线程将会被结束 server.daemon_threads = True if __name__ = = "__main__" : main() |
上面requestHandlerclass中的handle方法和finish方式对应了上一篇中TCP服务器的recv方法和stop方法,他们处理请求的逻辑是相同的。只是上面使用了socketserver的代码变少了,处理的逻辑也变少了,TCPserver帮我们完成了大量的工作,这利于软件的快速开发。
内置的两个RequestHandlerClass
StreamHandlerRequest
StreamHandlerRequest顾名思义是一种流式的求情处理类,对应TCP协议的面向字节流的传输形式。我们从源代码分析。(去除了一些次要代码)
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 | class StreamRequestHandler(BaseRequestHandler): rbufsize = - 1 # 读缓存 wbufsize = 0 # 写缓存 timeout = None # 超时时间 # IP/TCP拥塞控制的Nagle算法算法。 disable_nagle_algorithm = False def setup( self ): # 实现了setup, self .connection = self .request if self .timeout is not None : self .connection.settimeout( self .timeout) if self .disable_nagle_algorithm: self .connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True ) # 使用 makefile方法获得了一个只读文件对象 rfile self .rfile = self .connection.makefile( 'rb' , self .rbufsize) # 获得一个只写的文件对象 wfile if self .wbufsize = = 0 : self .wfile = _SocketWriter( self .connection) else : self .wfile = self .connection.makefile( 'wb' , self .wbufsize) def finish( self ): # 负责将这个 wfile 和 rfile方法关闭。 if not self .wfile.closed: try : self .wfile.flush() except socket.error: pass self .wfile.close() self .rfile.close() |
使用StreamRequestHandler方法可以将这个socket包装成一个类文件对象,方便我们使用一套文件对象的方法处理这个socket,它没有实现handle方法,我仍然需要我们实现。我们可以这样使用它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class MyHandle(StreamRequestHandler): # 如果需要使用setup和finish方法,需要调用父类方法,否则该方法将会被覆盖。 def setup( self ): super ().setup() # 添加自己的需求 def handle( self ): # 这里我们可以使用wfile和rfile来处理socket消息了,例如之前使用self.request.recv()方法等同于self.rfile.read() # 而 self.wfile.write 等同于 self.request.send(),在handle方法中完成业务逻辑即可 def finish( self ): super ().finish() server = ThreadingTCPServer( "127.0.0.1" , MyHandle) server.serve_forever() |
StreamRequestHandler主要定义了两个新的 wfile对象和rfile对象,来分别对这个socket进行读写操作,当我们业务需要时,比如需要使用文件接口方法时,选择继承于StreamRequestHandler构建我们自己处理请求类来完成业务逻辑将会更加的方便。
DatagramRequestHandler
DatagramRequestHandler字面意思是数据报请求处理,也就是基于UDPServer的服务器才能使用该请求处理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class DatagramRequestHandler(BaseRequestHandler): def setup( self ): from io import BytesIO # udp的self.request包含两部分(data,socket)它来自于 # data, client_addr = self.socket.recvfrom(self.max_packet_size) # return (data, self.socket), client_addr # (data, self.socket)就是这个self.request,在这里将其解构,data为recvfrom接收的数据 self .packet, self .socket = self .request # 该数据包封装为 BytesIO,同样为一个类文件对象。 self .rfile = BytesIO( self .packet) self .wfile = BytesIO() def finish( self ): self .socket.sendto( self .wfile.getvalue(), self .client_address) |
从源码可以看出,DatagramRequestHandler将数据包封装为一个rfile,并实例化一个ByteIO对象用于写入数据,写入的数据可以通过self.socket这个套接字发送。这样可以使用rfile和wfile这两个类文件对象的read或者write接口来进行一些IO方面的操作。
本文出自https://www.jb51.net/article/188551.htm