目录python
socket编程过于底层,编程虽然有套路,可是要写出健壮的代码仍是比较困难的,因此不少语言都会socket底层API进行封装,Python的封装就是SocketServer模块。它是网络服务编程框架,便于企业级快速开发。编程
SocketServer,网络通讯服务器,是Python标准库中的一个高级模块,其做用是建立网络服务器。该模块简化了编写网络服务器的任务。bash
SocketServer模块中定义了五种服务器类。服务器
它们的继承关系:网络
+------------+ | BaseServer | +------------+ | v +-----------+ +------------------+ | TCPServer |------->| UnixStreamServer | +-----------+ +------------------+ | v +-----------+ +--------------------+ | UDPServer |------->| UnixDatagramServer | +-----------+ +--------------------+
除了基类为抽象基类意外,其余四个类都是同步阻塞的。多线程
SocketServer模块中还提供了一些Mixin类,用于和同步类组成多线程或者多进程的异步方式。框架
fork是建立多进程,thread是建立多线程。fork须要操做系统支持,Windows不支持。异步
要想使用服务器类,须要传入一个RequestHandlerClass类,为何要传入这个RequestHandlerClass类,它是干什么的?下面一块儿来看看源码:socket
# 446 行 def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True): """Constructor. May be extended, do not override.""" BaseServer.__init__(self, server_address, RequestHandlerClass) # 把RequestHandlerClass交给了BaseServer self.socket = socket.socket(self.address_family, self.socket_type)
# 204 行 def __init__(self, server_address, RequestHandlerClass): """Constructor. May be extended, do not override.""" self.server_address = server_address self.RequestHandlerClass = RequestHandlerClass # 将RequestHandlerClass看成类属性付给了实例自己 self.__is_shut_down = threading.Event() self.__shutdown_request = False
# 219 行 def serve_forever(self, poll_interval=0.5): self.__is_shut_down.clear() try: ... ... if ready: self._handle_request_noblock() # 这里执行了_handle_request_noblock()方法 self.service_actions() finally: self.__shutdown_request = False self.__is_shut_down.set()
# 304行 def _handle_request_noblock(self): ... ... try: self.process_request(request, client_address) # 这里又调用了process_request方法 except Exception: self.handle_error(request, client_address) self.shutdown_request(request) except: self.shutdown_request(request) raise else: self.shutdown_request(request)
# 342 行 def process_request(self, request, client_address): self.finish_request(request, client_address) # 这里又调用了finish_request方法 self.shutdown_request(request)
# 359 行 def finish_request(self, request, client_address): """Finish one request by instantiating RequestHandlerClass.""" self.RequestHandlerClass(request, client_address, self) # 实例化对象
经过观察源码,咱们知道RequestHandlerClass接受了两个参数,根据上下文代码咱们可知:tcp
那么这个RequestHandlerClass究竟是啥?
经过源码,咱们能够知道,最后传入RequestHandlerClass的是socket和client的ip地址,是否是和咱们前面写的TCP多线程版本的recv函数很想?没错,RequestHandlerClass在这里被称为编程接口,因为处理业务逻辑。
可是RequestHandlerClass咱们没有参照,该如何写呢? socketserver提供了抽象基类BaseRequestHandler,共咱们继承来实现。
BaseRequestHandler 是和用户链接的用户请求处理类的基类,因此 RequestHandlerClass 必须是 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
观察源码咱们知道,setup、handler、finish在类初始化时就会被执行:
这些参数究竟是什么?
import socketserver class MyRequestHandler(socketserver.BaseRequestHandler): def handle(self): print(self.server) # <socketserver.TCPServer object at 0x00000153270DA320> print(self.client_address) # ('127.0.0.1', 62124) print(self.request) # <socket.socket fd=296, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 999), raddr=('127.0.0.1', 62124)> s = socketserver.TCPServer(('127.0.0.1', 999), MyRequestHandler) s.serve_forever()
根据上面的输出信息咱们知道:
咱们总结一下,使用socketserver建立一个服务器
须要如下几个步骤:
顾名思义:Echo,即来什么消息就回显什么消息,即客户端发来什么消息,就返回什么消息。
import socketserver import logging import threading FORMAT = '%(asctime)s %(message)s' logging.basicConfig(level=logging.INFO, format=FORMAT) class MyRequestHandler(socketserver.BaseRequestHandler): def setup(self): # 优先执行父类同名方法是一个很好的习惯,即使是父类不做任何处理 super(MyRequestHandler, self).setup() self.event = threading.Event() def handle(self): super(MyRequestHandler, self).handle() while not self.event.is_set(): data = self.request.recv(1024) if data == b'quit' or data == b'': self.event.set() break msg = '{}:{} {}'.format(*self.client_address, data.decode()) logging.info(msg) self.request.send(msg.encode()) def finish(self): super(MyRequestHandler, self).finish() self.event.set() if __name__ == '__main__': with socketserver.ThreadingTCPServer(('127.0.0.1', 9999), MyRequestHandler) as server: # 让全部启动的线程都为daemon进程 server.daemon_threads = True # serve_foreve会阻塞,因此交给子线程运行 threading.Thread(target=server.serve_forever, daemon=True).start() while True: cmd = input('>>>').strip() if not cmd: continue if cmd == 'quit': server.server_close() break print(threading.enumerate())
ThreadingTCPServer是TCPServer的子类,它混合了ThreadingMixIn类使用多线程来接受客户端的链接。
代码以下:
import socketserver import logging import threading FORMAT = '%(asctime)s %(message)s' logging.basicConfig(level=logging.INFO, format=FORMAT) class ChatRequestHandler(socketserver.BaseRequestHandler): clients = set() # 每一个链接进来时的操做 def setup(self): super(ChatRequestHandler, self).setup() self.event = threading.Event() self.lock = threading.Lock() self.clients.add(self.request) # 每一个链接的业务处理 def handle(self): super(ChatRequestHandler, self).handle() while not self.event.is_set(): # use Client code except try: data = self.request.recv(1024) except ConnectionResetError: self.clients.remove(self.request) self.event.set() break # use TCP tools except if data.rstrip() == b'quit' or data == b'': with self.lock: self.clients.remove(self.request) logging.info("{}:{} is down ~~~~~~".format(*self.client_address)) self.event.set() break msg = "{}:{} {}".format(*self.client_address, data.decode()).encode() logging.info('{}:{} {}'.format(*self.client_address, msg)) with self.lock: for client in self.clients: client.send(msg) # 每一个链接关闭的时候,会执行 def finish(self): super(ChatRequestHandler, self).finish() self.event.set() self.request.close() class ChatTCPServer: def __init__(self, ip, port): self.ip = ip self.port = port self.sock = socketserver.ThreadingTCPServer((self.ip, self.port), ChatRequestHandler) def start(self): self.sock.daemon_threads = True threading.Thread(target=self.sock.serve_forever, name='server', daemon=True).start() def stop(self): self.sock.server_close() if __name__ == '__main__': cts = ChatTCPServer('127.0.0.1', 9999) cts.start() while True: cmd = input('>>>>:').strip() if cmd == 'quit': cts.stop() if not cmd: continue print(threading.enumerate())
使用TCP工具强制退出时会发送空串,而使用咱们本身写的tcp client,则不会发送,因此这里所了两种异常的处理。socketserver只是用在服务端,客户端使用上一篇TCP client便可。