socketserver模块
虽说用Python编写简单的网络程序很方便,但复杂一点的网络程序还是用现成的框架比较好。这样就可以专心事务逻辑,而不是套接字的各种细节。SocketServer模块简化了编写网络服务程序的任务。同时SocketServer模块也是Python标准库中很多服务器框架的基础。
socketserver模块可以简化网络服务器的编写,Python把网络服务抽象成两个主要的类,一个是Server类,用于处理连接相关的网络操作,另外一个则是RequestHandler类,用于处理数据相关的操作。并且提供两个MixIn 类,用于扩展 Server,实现多进程或多线程。
一、单线程示例
server端和客户端持续聊天的示例,但是同时只能和一个客户端进行通信,并没有并发的效果。
1、服务端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | from socket import * ip_port = ( '127.0.0.1' , 8880 ) tcp_socket_server = socket() tcp_socket_server.bind(ip_port) tcp_socket_server.listen( 5 ) while 1 : conn,addr = tcp_socket_server.accept() print ( '客户端' ,addr) while 1 : client_data = conn.recv( 1024 ) if len (client_data) = = 0 : print ( "客户端断开连接,等待新的用户连接...." ) break print ( "接受数据 >>>" , str (client_data, "utf8" )) response = input ( "响应数据 >>>" ) conn.sendall(bytes(response, "utf8" )) conn.close() |
2、客户端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import socket ip_port = ( '127.0.0.1' , 8888 ) sock = socket.socket() sock.connect(ip_port) print ( "客户端启动:" ) while True : inp = input ( '发送数据 >>>' ) if inp = = 'exit' : break sock.sendall(bytes(inp, "utf8" )) server_response = sock.recv( 1024 ) print ( "服务端响应数据 >>>" , str (server_response, "utf8" )) sock.close() |
二、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 | import socketserver class MyServer(socketserver.BaseRequestHandler): # 必须继承这个类 def handle( self ): """ 并发的业务逻辑 conn(客户端套接字对象):self.request :return: """ while 1 : client_data = self .request.recv( 1024 ) if client_data.decode( "utf8" ) = = "exit" : print ( "客户端断开连接,等待新的用户连接...." ) break print ( "接受数据 >>>" , str (client_data, "utf8" )) response = input ( "响应数据 >>>" ) self .request.sendall(bytes(response, "utf8" )) self .request.close() # 封装TCP协议相关的套接字对象 server = socketserver.ThreadingTCPServer(( '127.0.0.1' , 8880 ), MyServer) # 第一个参数是ip+port,第二个参数是类名MyServer server.serve_forever() |
1、总结socketserver使用分以下三步:
(1)自定义功能类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class MyServer(socketserver.BaseRequestHandler): # 必须继承这个类 def handle( self ): """ 并发的业务逻辑 conn(客户端套接字对象):self.request :return: """ while 1 : client_data = self .request.recv( 1024 ) if len (client_data) = = 0 : print ( "客户端断开连接,等待新的用户连接...." ) break print ( "接受数据 >>>" , str (client_data, "utf8" )) response = input ( "响应数据 >>>" ) self .request.sendall(bytes(response, "utf8" )) self .request.close() |
(2)封装TCP协议相关的套接字对象
1 | server = socketserver.ThreadingTCPServer(( '127.0.0.1' , 8880 ), MyServer) # 第一个参数是ip+port,第二个参数是类名MyServer |
(3)调用server.serve_forever()
1 | server.serve_forever() |
2、运行客户端服务端显示效果
三、socketserver源码解析
1、socketserver.ThreadingTCPServer源码解析
(1)查看ThreadingTCPServer类
1 | class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass |
在socketserver.py中查看到这句源码,混合继承了ThreadingMixIn和TCPServer类。根据广度优先先查看左边的类。
(2)查看ThreadingMixIn类
1 2 3 4 5 6 7 8 9 10 | class ThreadingMixIn: """Mix-in class to handle each request in a new thread.""" # Decides how threads will act upon termination of the # main process daemon_threads = False def process_request_thread( self , request, client_address):... def process_request( self , request, client_address):... |
发现这个类并没有__init__方法,只有两个实例方法。因此再回去查看TCPServer类。
(3)查看TCPServer类中的__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 26 27 | class TCPServer(BaseServer): address_family = socket.AF_INET socket_type = socket.SOCK_STREAM request_queue_size = 5 allow_reuse_address = False def __init__( self , server_address, RequestHandlerClass, bind_and_activate = True ): """ 构造函数 :param server_address: 接收的要绑定的元组('127.0.0.1', 8880) :param RequestHandlerClass: 接收功能类MyServer :param bind_and_activate: """ BaseServer.__init__( self , server_address, RequestHandlerClass) # 执行BaseServer的__init__方法 self .socket = socket.socket( self .address_family, self .socket_type) if bind_and_activate: try : self .server_bind() self .server_activate() except : self .server_close() raise """后面代码省略""" |
注意__init__方法接收的参数,server_address: 接收的要绑定的元组('127.0.0.1', 8880);RequestHandlerClass: 接收功能类MyServer。
__init__方法第一步就是执行BaseServer的__init__方法。
(4)查看BaseServer的__init__方法
1 2 3 4 5 6 7 8 9 10 11 | class BaseServer: timeout = None def __init__( self , server_address, RequestHandlerClass): """Constructor. May be extended, do not override.""" self .server_address = server_address # 实例变量赋值 self .RequestHandlerClass = RequestHandlerClass # 实例变量赋值 self .__is_shut_down = threading.Event() self .__shutdown_request = False """代码省略""" |
可以看到主要是做了一个实例变量赋值
(5)继续解析TCPServer类中的__init__方法
在执行完BaseServer的__init__方法后,接下里开始创建socket对象:
1 2 | self .socket = socket.socket( self .address_family, self .socket_type) |
socket对象创建完成后,执行socket.bind和socket.listen方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | class TCPServer(BaseServer): def __init__( self , server_address, RequestHandlerClass, bind_and_activate = True ): if bind_and_activate: try : self .server_bind() self .server_activate() except : self .server_close() raise def server_bind( self ): """Called by constructor to bind the socket. May be overridden. """ if self .allow_reuse_address: self .socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 ) self .socket.bind( self .server_address) # bind元组地址 self .server_address = self .socket.getsockname() def server_activate( self ): """Called by constructor to activate the server. May be overridden. """ self .socket.listen( self .request_queue_size) # listen,这里默认值也是5 |
(6)总结server = socketserver.ThreadingTCPServer(('127.0.0.1', 8880), MyServer) 做的事情
1.self.socket 2.self.socket.bind() 3.self.socket.listen(5)
这三步完成后,初始化也就完成了。
2、server.serve_forever()源码解析
由于server是ThreadingTCPServer的实例对象,因此依次追溯ThreadingTCPServer、ThreadingMixIn、TCPServer、BaseServer类,最终在BaseServer类中找到serve_forever方法。
(1)查看serve_forever方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class BaseServer: def serve_forever( self , poll_interval = 0.5 ): self .__is_shut_down.clear() try : with _ServerSelector() as selector: # IO多路复用监听 selector.register( self , selectors.EVENT_READ) while not self .__shutdown_request: # 也是监听 ready = selector.select(poll_interval) if ready: self ._handle_request_noblock() self .service_actions() finally : self .__shutdown_request = False self .__is_shut_down. set () |
前面两步都是监听相关的操作,直接查看self._handle_request_noblock()源码。
(2)查看_handle_request_noblock方法
同样是依次追溯各个类,最后在BaseServer类中找到方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class BaseServer: def _handle_request_noblock( self ): try : request, client_address = self .get_request() except OSError: return if self .verify_request(request, client_address): try : self .process_request(request, client_address) except Exception: self .handle_error(request, client_address) self .shutdown_request(request) except : self .shutdown_request(request) raise else : self .shutdown_request(request) |
(3)查看self.get_request()方法
1 2 3 4 5 6 7 | class TCPServer(BaseServer): def get_request( self ): """Get the request and client address from the socket. May be overridden. """ return self .socket.accept() |
在这里找到了socket.accept方法,被动接受TCP客户的连接,(阻塞式)等待连接到来。
因此_handle_request_noblock中的request, client_address = self.get_request():request就是新的套接字对象(conn),可以用来接收和发送数据;client_address就是连接客户端的地址。
继续查看_handle_request_noblock方法的self.process_request(request, client_address)调用。
(4)查看process_request方法
这里按照查找顺序,在ThreadingMixIn类找到process_request方法。(注意不要找BaseServer里的process_request,它已经被覆盖了)
1 2 3 4 5 6 7 8 | class ThreadingMixIn: def process_request( self , request, client_address): """Start a new thread to process the request.""" t = threading.Thread(target = self .process_request_thread, args = (request, client_address)) t.daemon = self .daemon_threads t.start() |
每来一个用户就开启一个线程,执行self.process_request_thread(request, client_address)方法。
process_request_thread方法也在ThreadingMixIn类中。
(5)查看process_request_thread方法
1 2 3 4 5 6 7 8 | class ThreadingMixIn: def process_request_thread( self , request, client_address): try : self .finish_request(request, client_address) except Exception: self .handle_error(request, client_address) finally : self .shutdown_request(request) |
按照查找顺序在BaseServer中找到finish_request方法。
(6)查看finish_request方法
1 2 3 4 | class BaseServer: def finish_request( self , request, client_address): """Finish one request by instantiating RequestHandlerClass.""" self .RequestHandlerClass(request, client_address, self ) |
注意这里的RequestHandlerClass在TCPServer类中的__init__方法执行时,用RequestHandlerClass来接收功能类MyServer。
这里是在调用MyServer类的实例化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class MyServer(socketserver.BaseRequestHandler): # 必须继承这个类 def handle( self ): """ 并发的业务逻辑 conn(客户端套接字对象):self.request :return: """ while 1 : client_data = self .request.recv( 1024 ) if client_data.decode( "utf8" ) = = "exit" : print ( "客户端断开连接,等待新的用户连接...." ) break print ( "接受数据 >>>" , str (client_data, "utf8" )) response = input ( "响应数据 >>>" ) self .request.sendall(bytes(response, "utf8" )) self .request.close() |
但是这个类并没有__init__方法,因此需要查看它的父类__init__方法。
(7)查看BaseRequestHandler类
1 2 3 4 5 6 7 8 9 10 11 | class BaseRequestHandler: def __init__( self , request, client_address, server): self .request = request # 新的套接字对象(conn)实例变量赋值 self .client_address = client_address # 连接客户端的地址实例变量赋值 self .server = server self .setup() try : self .handle() # 调用自己的handle方法 finally : self .finish() |
在完成新的套接字对象(conn)实例变量赋值和连接客户端的地址实例变量赋值后,调用自己的handle方法。
这个self.handle()调用的不是BaseRequestHandler类的handle方法而是MyServer的handle方法,执行自定义的handle方法。
3、流程图
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术