Python-SocketServer模块
1、简介
socket编程过于底层,编程虽然有套路,但是想要写出健壮的代码还是比较困难的,所以很多语言都对
socket底层API进行封装,Python的封装就是socketserver模块。它是网络服务编程框架,便于企业级快速开发。
2、类的继承关系
+------------+ | BaseServer | +------------+ | v +-----------+ +------------------+ | TCPServer |------->| UnixStreamServer | +-----------+ +------------------+ | v +-----------+ +--------------------+ | UDPServer |------->| UnixDatagramServer | +-----------+ +--------------------+
SocketServer简化了网络服务器的编写。 它有4个同步类: TCPServer UDPServer UnixStreamServer UnixDatagramServer。 2个Mixin类:ForkingMixIn 和 ThreadingMixIn 类,用来支持异步。由此得到 class ForkingUDPServer(ForkingMixIn, UDPServer): pass class ForkingTCPServer(ForkingMixIn, TCPServer): pass class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass fork是创建多进程,thread是创建多线程。 fork需要操作系统支持,Windows不支持。
3、编程接口
3.1、方法介绍
socketserver.BaseServer(server_address, RequestHandlerClass) 需要提供服务器绑定的地址信息,和用于处理请求的RequestHandlerClass类。 RequestHandlerClass类必须是BaseRequestHandler类的子类,在BaseServer中代码如下: # BaseServer代码 class BaseServer: 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 def finish_request(self, request, client_address): # 处理请求的方法 """Finish one request by instantiating RequestHandlerClass.""" self.RequestHandlerClass(request, client_address, self) # RequestHandlerClass构造
3.2、BaseRequestHandler类
3.2.1、介绍
它是和用户连接的用户请求处理类的基类,定义为 BaseRequestHandler(request, client_address, server) 服务端Server实例接收用户请求后,最后会实例化这个类。 它被初始化时,送入3个构造参数:request, client_address, server自身 以后就可以在BaseRequestHandler类的实例上使用以下属性: self.request是和客户端的连接的socket对象 self.server是TCPServer实例本身 self.client_address是客户端地址 这个类在初始化的时候,它会依次调用3个方法。子类可以覆盖这些方法。 # BaseRequestHandler要子类覆盖的方法 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
3.2.2、测试代码
import threading import socketserver class MyHandler(socketserver.BaseRequestHandler): def handle(self): # super().handle() # 可以不调用,父类handle什么都没有做 print('-' * 30) print(self.server) # 服务 print(self.request) # 服务端负责客户端连接请求的socket对象 print(self.client_address) # 客户端地址 print(self.__dict__) print(self.server.__dict__) # 能看到负责accept的socket print(threading.enumerate()) print(threading.current_thread()) print('-' * 30) addr = ('0.0.0.0', 9999) server = socketserver.ThreadingTCPServer(addr, MyHandler) # 注意参数是MyHandler类 # server.handle_request() # 一次性 server.serve_forever() # 永久循环执行
测试结果说明,handle方法相当于socket的recv方法。
每个不同的连接上的请求过来后,生成这个连接的socket对象即self.request,客户端地址是self.client_address。
3.3、问题
测试过程中,上面代码,连接后立即断开了,为什么?
怎样才能客户端和服务器端长时间连接?
import threading import socketserver import logging FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s" logging.basicConfig(format=FORMAT, level=logging.INFO) class MyHandler(socketserver.BaseRequestHandler): def handle(self): # super().handle() # 可以不调用,父类handle什么都没有做 print('-' * 30) print(self.server) # 服务 print(self.request) # 服务端负责客户端连接请求的socket对象 print(self.client_address) # 客户端地址 print(self.__dict__) print(self.server.__dict__) # 能看到负责accept的 print(threading.enumerate()) print(threading.current_thread()) print('-' * 30) for i in range(3): data = self.request.recv(1024) logging.info(data) logging.info('====end====') addr = ('0.0.0.0', 9999) server = socketserver.ThreadingTCPServer(addr, MyHandler) server.serve_forever() # 永久
将ThreadingTCPServer换成TCPServer,同时连接2个客户端观察效果。
ThreadingTCPServer是异步的,可以同时处理多个连接。
TCPServer是同步的,一个连接处理完了,即一个连接的handle方法执行完了,才能处理另一个连接,且只有主线程。
3.4、总结
创建服务器需要几个步骤: 1. 从BaseRequestHandler类派生出子类,并覆盖其handle()方法来创建请求处理程序类,此方法将处理传入请求 2. 实例化一个服务器类,传参服务器的地址和请求处理类 3. 调用服务器实例的handle_request()或serve_forever()方法 4. 调用server_close()关闭套接字
4、实现EchoServer
4.1、需求
顾名思义,Echo,来什么消息回显什么消息
客户端发来什么信息,返回什么信息
4.2、代码
import threading import socketserver class Handler(socketserver.BaseRequestHandler): def setup(self): super().setup() self.event = threading.Event() def finish(self): super().finish() self.event.set() def handle(self): super().handle() print('-' * 30) while not self.event.is_set(): data = self.request.recv(1024).decode() print(data) msg = '{} {}'.format(self.client_address, data).encode() self.request.send(msg) server = socketserver.ThreadingTCPServer(('127.0.0.1', 9999), Handler) print(server) threading.Thread(target=server.serve_forever, name='EchoServer',daemon=True).start() while True: cmd = input('>>') if cmd == 'quit': server.server_close() break print(threading.enumerate())
5、总结
为每一个连接提供RequestHandlerClass类实例,依次调用setup、handle、finish方法,且使用了 try...finally结构保证finish方法一定能被调用。这些方法依次执行完成,如果想维持这个连接和客户端通信,就需要在handle函数中使用循环。 socketserver模块提供的不同的类,但是编程接口是一样的,即使是多进程、多线程的类也是一样,大大减少了编程的难度。 将socket编程简化,只需要程序员关注数据处理本身,实现Handler类就行了。这种风格在Python十分常见。
6、示例
6.1、用SocketServer来改写ChatServer
6.1.1、代码
使用ThreadingTCPServer改写ChatServer
import threading from socketserver import ThreadingTCPServer, StreamRequestHandler import logging FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s" logging.basicConfig(format=FORMAT, level=logging.INFO) class ChatHandler(StreamRequestHandler): clients = {} def setup(self): super().setup() self.event = threading.Event() self.clients[self.client_address] = self.wfile def handle(self): super().handle() # 虽然父类什么都没做,但是调用是个好习惯 while not self.event.is_set(): data = self.rfile.read().strip() if data == b'quit' or data == b'': break msg = "From {}:{}. data={}".format(*self.client_address, data) for f in self.clients.values(): f.write(msg.encode()) f.flush() def finish(self): self.clients.pop(self.client_address) super().finish() self.event.set() class ChatServer: def __init__(self, ip='127.0.0.1', port=9999): self.server = ThreadingTCPServer((ip, port), ChatHandler) self.server.daemon_threads = True def start(self): threading.Thread( target=self.server.serve_forever, name='chatserver', daemon=True).start() def stop(self): self.server.server_close() if __name__ == '__main__': cs = ChatServer() cs.start() while True: cmd = input('>>').strip() if cmd == 'quit': cs.stop() break print(threading.enumerate())
6.1.2、问题
上例 self.clients.pop(self.client_address) 能执行到吗?
如果连接的线程中handle方法中抛出异常,例如客户端主动断开导致的异常,线程崩溃,self.clients的pop方法还能执行吗?
当然能执行,基类源码保证了即使异常,也能执行finish方法。但不代表不应该不捕获客户端各种异常。
注意:此程序线程不安全
6.2、使用IO多路复用改写群聊软件
不需要启动多线程来执行socket的accept、recv方法了
import threading import selectors import socket import logging FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s" logging.basicConfig(format=FORMAT, level=logging.INFO) class ChatServer: def __init__(self, ip='127.0.0.1', port=9999): self.addr = ip, port self.sock = socket.socket() self.sock.setblocking(False) # 非阻塞 self.event = threading.Event() # 构建本系统最优Selector self.selector = selectors.DefaultSelector() def start(self): self.sock.bind(self.addr) self.sock.listen() key = self.selector.register(self.sock, selectors.EVENT_READ, self.accept) threading.Thread(target=self.select, name='select').start() def select(self): with self.selector: while not self.event.is_set(): events = self.selector.select(0.5) # 超时返回[] # 监听注册的对象的事件,发生被关注事件则返回events for key, mask in events: key.data(key.fileobj, mask) def accept(self, server: socket.socket, mask): conn, raddr = server.accept() conn.setblocking(False) logging.info("New client {} accepted. fd={}".format(raddr, conn.fileno())) key = self.selector.register(conn, selectors.EVENT_READ, self.recv) def recv(self, conn: socket.socket, mask): data = conn.recv(1024).strip() if data == b'' or data == b'quit': self.selector.unregister(conn) conn.close() # 关闭前一定要注销 return msg = "Your msg={}".format(data.decode()).encode() logging.info(msg) for key in self.selector.get_map().values(): print(key.data.__name__) # 特别注意,绑定的方法==和is的区别 print(key.data is self.accept, key.data == self.accept) print(key.data is self.recv, key.data == self.recv) if key.data == self.recv: key.fileobj.send(msg) def stop(self): self.event.set() if __name__ == '__main__': cs = ChatServer() cs.start() while True: cmd = input('>>').strip() if cmd == 'quit': cs.stop() break print(*cs.selector.get_map().values())
本例只完成基本功能,其他功能如有需要,请自行完成。
注意使用IO多路复用,使用了几个线程?
特别注意key.data == self.recv
6.3、自己实现HTTPServer
import threading import selectors import socket import logging import webob # pip install webob FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s" logging.basicConfig(format=FORMAT, level=logging.INFO) html_content = """ <html> <head><title></title></head> <body> 欢迎访问首页 </body> </html> """ class WebServer: def __init__(self, ip='0.0.0.0', port=80): self.addr = ip, port self.sock = socket.socket() self.sock.setblocking(False) # 非阻塞 self.event = threading.Event() # 构建本系统最优Selector self.selector = selectors.DefaultSelector() def start(self): self.sock.bind(self.addr) self.sock.listen() key = self.selector.register(self.sock, selectors.EVENT_READ, self.accept) threading.Thread(target=self.select, name='select').start() def select(self): with self.selector: while not self.event.is_set(): events = self.selector.select(1) # 超时返回[] # 监听注册的对象的事件,发生被关注事件则返回events print(events) for key, mask in events: key.data(key.fileobj, mask) def accept(self, server: socket.socket, mask): conn, raddr = server.accept() conn.setblocking(False) logging.info("New client {} accepted. fd={}".format(raddr, conn.fileno())) key = self.selector.register(conn, selectors.EVENT_READ, self.recv) def recv(self, conn: socket.socket, mask): with conn: # 用完就断 try: data = conn.recv(1024).strip() # 收到request报文,下面要做url映射等,此处都省略 request = webob.Request.from_bytes(data) print(request.url) print('=' * 30) response = webob.Response(html_content, status=201) response.headers.add('Server', 'MageServer') firstline = 'HTTP/1.1 {}'.format(response.status) print(response.headerlist) headers = "\r\n".join( [firstline] + ["{}: {}".format(k, v) for k, v in response.headerlist] + ['', ''] ) # 响应头:第一行、头部字段、2个回车换行 body = response.body print(type(headers), type(body)) content = headers.encode() + body conn.send(content) finally: self.selector.unregister(conn) def stop(self): self.event.set() if __name__ == '__main__': cs = WebServer() cs.start() while True: cmd = input('>>').strip() if cmd == 'quit': cs.stop() break
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构