目录
SocketServer
socket编程过于底层,编程虽然有套路,但是想要写出健壮的代码还是比较困难的,所以很多语言都对socket底层 API进行了封装,Python的封装就是------socketserver模块,它是网络服务编程模块,便于企业级快速开发
类的继承关系
SocketServer简化了网络服务器的编写
它有4个同步类:TCPServer、UDPServer、UnixStramServer、UnixDatagramServer
2个Mixin类:ForkingMixin和ThreadingMixin,用来支持异步
Class ForkingUDPServer(ForkingMixin,UDPServer):pass #UDPServer 多进程同异步编程
Class ForkingTCPServer(ForkingMixin,TCPServer):pass #TCPServer多线程,同异步编程
Class ThreadingUDPServer(ThreadingMixin,UDPServer):pass #UDPServer 多线程、同异步编程
Class ThreadingTCPSever(ThreadingMixin,TCPServer):pass #TCPServer 多线程编程
fork是创建多进程,thread是创建多进程
编程接口
socketserver.BaseServer(server_address, RequestHandlerClass) #需要提供服务器绑定的地址信息,和用于处理请求的RequestHandlerClass类
RequestHandlerClass类必须是BaseRequestHandler类的子类,在BaseServer中代码如下
class BaseServver: 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)
BaseRequestHandler类
它是和用户连接的用户请求处理类的基类,定义为BaseRequestHandler(request,client_adress,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
测试代码
import threading import socketserver import logging FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s" logging.basicConfig(format=FORMAT,level=logging.INFO) class MyHandler_my(socketserver.BaseRequestHandler): def setup(self): super().setup() self.event = threading.Event() def handle(self): #super().hanler() #可以不调用,父类handler什么都没走 print("~"*30) print(self.server) #服务 print(self.request) #服务端负责客户端连接请求的socker对象 print(self.client_address) #客户端地址 print(self.__dict__) print(self.server.__dict__) #能看到负责accept的socket print(threading.enumerate()) print(threading.current_thread()) print("~"*30) # logging.info() while not self.event.is_set(): print("come") data = self.request.recv(1024) msg = "I`m comming you msg {} ".format(data.decode()) self.request.send(msg.encode()) addr=("127.0.0.1",12000) server = socketserver.ThreadingTCPServer(addr,MyHandler_my) print(server) logging.info(11) server.serve_forever() #永久 server.server_close() 结果: <socketserver.ThreadingTCPServer object at 0x022CEF50> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ <socketserver.ThreadingTCPServer object at 0x022CEF50> <socket.socket fd=488, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 12000), raddr=('127.0.0.1', 51527)> ('127.0.0.1', 51527) {'request': <socket.socket fd=488, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 12000), raddr=('127.0.0.1', 51527)>, 'client_address': ('127.0.0.1', 51527), 'server': <socketserver.ThreadingTCPServer object at 0x022CEF50>, 'event': <threading.Event object at 0x023653D0>} {'server_address': ('127.0.0.1', 12000), 'RequestHandlerClass': <class '__main__.MyHandler_my'>, '_BaseServer__is_shut_down': <threading.Event object at 0x022D5230>, '_BaseServer__shutdown_request': False, 'socket': <socket.socket fd=484, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 12000)>, '_threads': [<Thread(Thread-1, started 704)>]} [<_MainThread(MainThread, started 24636)>, <Thread(Thread-1, started 704)>] <Thread(Thread-1, started 704)> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ come come
总结:
将ThreadingTCPServer换成TCPServer,同时连接2个客户端观察效果
ThreadingTCPServer是异步的,可以同时处理多个连接TCPServer是同步的,一个连接处理完了,即一个连接的handle方法执行完了,才能处理另一个连接,且只有主线程
创建服务器需要几个步骤:
- 从BaseRequestHandler类的派生子类,并覆盖器handle()方法创建处理程序类,次方法将处理传入请求
- 实例化一个服务器类,传参服务器的地址和请求处理类
- 调用服务器实例的handle_request()或server_forever()方法
- 调用server_close() 关闭套接字
实现EchoSerer
import threading from socketserver import ThreadingTCPServer , BaseRequestHandler import sys class EchoHandler(BaseRequestHandler): def setup(self) -> None: super().setup() self.event = threading.Event() #初始话工作 def finish(self) -> None: super().finish() self.event.set() def handle(self) -> None: super().handle() while not self.event.is_set(): data = self.request.recv(1024).decode() msg = "{} {}".format(self.client_address,data).encode() self.request.send(msg) print("end") addr = ("127.0.0.1",9999) server = ThreadingTCPServer(addr,EchoHandler) server_thread= threading.Thread(target=server.serve_forever,name="EchoHandle") server_thread.start() try: while True: cmd = input(">>>") if cmd.strip() == "quit": break print(threading.enumerate()) except Exception as e: print(e) except KeyboardInterrupt: pass finally: print("exit") sys.exit(0)
练习---改写ChatServer
使用ThreadingTCP改写ChatSserver
import threading from socketserver import ThreadingTCPServer,BaseRequestHandler import sys import logging FORMAT = '%(asctime)s %(threadName)s %(thread)d %(message)s' logging.basicConfig(format=FORMAT,level=logging.INFO) class ChatHandler(BaseRequestHandler): clients = { } def setup(self) -> None: super().setup() self.event = threading.Event() self.clients[self.client_address] = self.request def finish(self) -> None: super().finish() #清理工作 self.clients.pop(self.client_address) #能执行到吗? self.event.set() def handle(self) -> None: super().handle() while not self.event.is_set(): data = self.request.recv(1024).decode() if data == "quit": break msg = "{} {} ".format(self.client_address,data).encode() logging.info(msg) for c in self.clients.values(): self.request.send(msg) print("End") addr = ("0.0.0.0",9999) server_tcp = ThreadingTCPServer(addr,ChatHandler) server_thread = threading.Thread(target=server_tcp.serve_forever,name="ChatSever",daemon=True) server_thread.start() try: while True: cmd = input(">>>") if cmd.strip() == 'quit': break except Exception as e: print(e) except KeyboardInterrupt: pass finally: print("Exit") sys.exit() 结果: >>>2021-07-17 10:47:09,845 Thread-1 8912 b"('127.0.0.1', 50991) 11111 " End 2021-07-17 10:47:30,923 Thread-2 15940 b"('127.0.0.1', 50992) fsfs "
上述问题
上例 self.clients.pop(self.client_address)能执行到吗?如果连接的线程中handler方法抛出异常,例如客户端主动断开导致的异常,线程崩溃,self.clents的pop方法还能执行吗?可以执行,基类源保证了即使异常,也能执行finish的方法,但不代表不应该不捕获客户端各种异常
修改客户端断开后的异常
- 通过打印可以看到,客户端主动断开,会导致recv方法立即返回一个空bytes,并没有同时抛出异常,当循环回到recv这一句的时候就会产生异常,可以通过判断data数据是否为空来判断客户端是否断开
import threading from socketserver import ThreadingTCPServer,BaseRequestHandler import sys import logging FORMAT = '%(asctime)s %(threadName)s %(thread)d %(message)s' logging.basicConfig(format=FORMAT,level=logging.INFO) class ChatHandler(BaseRequestHandler): clients = { } def setup(self) -> None: super().setup() self.event = threading.Event() self.clients[self.client_address] = self.request def finish(self) -> None: super().finish() #清理工作 self.clients.pop(self.client_address) self.event.set() def handle(self) -> None: super().handle() while not self.event.is_set(): data = self.request.recv(1024).decode() print(data,"~"*30) if not data or data == "quit": break msg = "{} {} ".format(self.client_address,data).encode() logging.info(msg) for c in self.clients.values(): self.request.send(msg) print("End") addr = ("0.0.0.0",9999) server_tcp = ThreadingTCPServer(addr,ChatHandler) server_thread = threading.Thread(target=server_tcp.serve_forever,name="ChatSever",daemon=True) server_thread.start() try: while True: cmd = input(">>>") if cmd.strip() == 'quit': break except Exception as e: print(e) except KeyboardInterrupt: pass finally: print("Exit") sys.exit()
总结
为每一个连接提供RequestHandlerClass类的实例,一次调用setup、handle、finish方法,且使用try……finally结构保证finish方法一定能被调用。这些方法依次执行完成,如果想维持这个连接和客户端通信,就需要在handler函数中使用循环
sockerserver模块提供的不同的类,但是编程接口是一样的,即使多线程,多进程的类也是一样,大大减少了编程的难度