目录
SocketServer
类的继承关系
编程接口
测试代码
总结:
实现EchoSerer
练习---改写ChatServer
总结
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_addressself.RequestHandlerClass = RequestHandlerClassself.__is_shut_down = threading.Event()self.__shutdown_request = Falsedef 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 = requestself.client_address = client_addressself.server = serverself.setup()try:self.handle()finally:self.finish()def setup(self): #初始化每一个连接passdef handle(self): #每一次请求处理passdef finish(self): #每一次连接清理pass测试代码
import threading
import socketserver
import loggingFORMAT = "%(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的socketprint(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()结果:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
('127.0.0.1', 51527)
{'request':, 'client_address': ('127.0.0.1', 51527), 'server': , 'event': }
{'server_address': ('127.0.0.1', 12000), 'RequestHandlerClass':, '_BaseServer__is_shut_down': , '_BaseServer__shutdown_request': False, 'socket': , '_threads': [ ]}
[<_MainThread(MainThread, started 24636)>,]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
come
come总结&#xff1a;
将ThreadingTCPServer换成TCPServer&#xff0c;同时连接2个客户端观察效果
ThreadingTCPServer是异步的&#xff0c;可以同时处理多个连接TCPServer是同步的&#xff0c;一个连接处理完了&#xff0c;即一个连接的handle方法执行完了&#xff0c;才能处理另一个连接&#xff0c;且只有主线程
创建服务器需要几个步骤&#xff1a;
- 从BaseRequestHandler类的派生子类&#xff0c;并覆盖器handle()方法创建处理程序类&#xff0c;次方法将处理传入请求
- 实例化一个服务器类&#xff0c;传参服务器的地址和请求处理类
- 调用服务器实例的handle_request()或server_forever()方法
- 调用server_close() 关闭套接字
import threading
from socketserver import ThreadingTCPServer , BaseRequestHandler
import sysclass EchoHandler(BaseRequestHandler):def setup(self) -> None:super().setup()self.event &#61; threading.Event() #初始话工作def finish(self) -> None:super().finish()self.event.set()def handle(self) -> None:super().handle()while not self.event.is_set():data &#61; self.request.recv(1024).decode()msg &#61; "{} {}".format(self.client_address,data).encode()self.request.send(msg)print("end")addr &#61; ("127.0.0.1",9999)
server &#61; ThreadingTCPServer(addr,EchoHandler)
server_thread&#61; threading.Thread(target&#61;server.serve_forever,name&#61;"EchoHandle")
server_thread.start()try:while True:cmd &#61; input(">>>")if cmd.strip() &#61;&#61; "quit":breakprint(threading.enumerate())
except Exception as e:print(e)
except KeyboardInterrupt:pass
finally:print("exit")sys.exit(0)
使用ThreadingTCP改写ChatSserver
import threading
from socketserver import ThreadingTCPServer,BaseRequestHandler
import sys
import loggingFORMAT &#61; &#39;%(asctime)s %(threadName)s %(thread)d %(message)s&#39;
logging.basicConfig(format&#61;FORMAT,level&#61;logging.INFO)class ChatHandler(BaseRequestHandler):clients &#61; { }def setup(self) -> None:super().setup()self.event &#61; threading.Event()self.clients[self.client_address] &#61; self.requestdef finish(self) -> None:super().finish() #清理工作self.clients.pop(self.client_address) #能执行到吗&#xff1f;self.event.set()def handle(self) -> None:super().handle()while not self.event.is_set():data &#61; self.request.recv(1024).decode()if data &#61;&#61; "quit":breakmsg &#61; "{} {} ".format(self.client_address,data).encode()logging.info(msg)for c in self.clients.values():self.request.send(msg)print("End")addr &#61; ("0.0.0.0",9999)
server_tcp &#61; ThreadingTCPServer(addr,ChatHandler)server_thread &#61; threading.Thread(target&#61;server_tcp.serve_forever,name&#61;"ChatSever",daemon&#61;True)
server_thread.start()try:while True:cmd &#61; input(">>>")if cmd.strip() &#61;&#61; &#39;quit&#39;:break
except Exception as e:print(e)
except KeyboardInterrupt:pass
finally:print("Exit")sys.exit()结果&#xff1a;
>>>2021-07-17 10:47:09,845 Thread-1 8912 b"(&#39;127.0.0.1&#39;, 50991) 11111 "
End
2021-07-17 10:47:30,923 Thread-2 15940 b"(&#39;127.0.0.1&#39;, 50992) fsfs "上述问题
上例 self.clients.pop(self.client_address)能执行到吗&#xff1f;如果连接的线程中handler方法抛出异常&#xff0c;例如客户端主动断开导致的异常&#xff0c;线程崩溃&#xff0c;self.clents的pop方法还能执行吗&#xff1f;可以执行&#xff0c;基类源保证了即使异常&#xff0c;也能执行finish的方法&#xff0c;但不代表不应该不捕获客户端各种异常
修改客户端断开后的异常
- 通过打印可以看到&#xff0c;客户端主动断开&#xff0c;会导致recv方法立即返回一个空bytes&#xff0c;并没有同时抛出异常&#xff0c;当循环回到recv这一句的时候就会产生异常&#xff0c;可以通过判断data数据是否为空来判断客户端是否断开
import threading
from socketserver import ThreadingTCPServer,BaseRequestHandler
import sys
import loggingFORMAT &#61; &#39;%(asctime)s %(threadName)s %(thread)d %(message)s&#39;
logging.basicConfig(format&#61;FORMAT,level&#61;logging.INFO)class ChatHandler(BaseRequestHandler):clients &#61; { }def setup(self) -> None:super().setup()self.event &#61; threading.Event()self.clients[self.client_address] &#61; self.requestdef 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 &#61; self.request.recv(1024).decode()print(data,"~"*30)if not data or data &#61;&#61; "quit":breakmsg &#61; "{} {} ".format(self.client_address,data).encode()logging.info(msg)for c in self.clients.values():self.request.send(msg)print("End")addr &#61; ("0.0.0.0",9999)
server_tcp &#61; ThreadingTCPServer(addr,ChatHandler)server_thread &#61; threading.Thread(target&#61;server_tcp.serve_forever,name&#61;"ChatSever",daemon&#61;True)
server_thread.start()try:while True:cmd &#61; input(">>>")if cmd.strip() &#61;&#61; &#39;quit&#39;:break
except Exception as e:print(e)
except KeyboardInterrupt:pass
finally:print("Exit")sys.exit()
为每一个连接提供RequestHandlerClass类的实例&#xff0c;一次调用setup、handle、finish方法&#xff0c;且使用try……finally结构保证finish方法一定能被调用。这些方法依次执行完成&#xff0c;如果想维持这个连接和客户端通信&#xff0c;就需要在handler函数中使用循环
sockerserver模块提供的不同的类&#xff0c;但是编程接口是一样的&#xff0c;即使多线程&#xff0c;多进程的类也是一样&#xff0c;大大减少了编程的难度