文章目录
SocketServer
简介
socket编程过于底层,想要编写健壮的代码比较困难,很多语句都对socket底层API进行了封装
- Python的封装就是socketserver模块,它是网路服务编程框架,便于企业级快速开发
类的继承关系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-32SezvwN-1576046194657)(D:\1Face_To_Face\14week _fourteen\xmind\socketserver类继承关系.png)]
SocketServer简化了网路服务器的编写
4个同步类
- TCPServer(BaseServer);
- UDPServer(TCPServer);
- UnixStreamServer;
- UnixDatagramServer;
2个Mixin类,用来支持异步
- ThreadingMixIn,创建多线程
- ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
- ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
- ForkingMixIn,创建多进程
- class ForkingTCPServer(ForkingMixIn, TCPServer): pass
- class ForkingUDPServer(ForkingMixIn, UDPServer): pass
- 注意:fork需要操作系统支持,Windows不支持
编程接口
socketserver.BaseServer
__init__(self, server_address, RequestHandlerClass)
- server_address,服务器绑定的地址信息;
- RequestHandlerClass,处理请求的RequestHandlerClass类;
- RequestHandlerClass类必须是BaseRequestHandler的子类;
class BaseServer:
def __init__(self, server_address, RequestHandlerClass):
"""Constructor. May be extended, do not override.""" # 构造器,可以扩展,不能重写
self.server_address = server_address # 为TCPServer实例添加server_address属性
self.RequestHandlerClass = RequestHandlerClass # 为TCPServer实例添加RequestHandlerClass类对象,注意不是实例
self.__is_shut_down = threading.Event()
self.__shutdown_request = False
BaseServer.serve_forever(self, poll_interval=0.5)
- 循环调用_handle_request_noblock(self),查看跟进代码1;
- _handle_request_noblock(self);
- Server端与Client端进行连接,并创建newsocket对象,等价socket.socket().accept()
- 调用self.process_request(request, client_address),查看跟进代码2;
- self.process_request(request, client_address),查看跟进代码3;
- finish_request(self, request, client_address)
- RequestHandlerClasss实例化,即BaseRequestHandler类实例化
# 1
def serve_forever(self, poll_interval=0.5):
self.__is_shut_down.clear() # threading.Event()设置为False
try:
with _ServerSelector() as selector: # 看不懂,跳过
selector.register(self, selectors.EVENT_READ)
while not self.__shutdown_request:
ready = selector.select(poll_interval)
if ready:
self._handle_request_noblock() # 跟进代码1,循环调用self._handle_request_noblock()
self.service_actions()
finally:
self.__shutdown_request = False
self.__is_shut_down.set()
...
# 2
def _handle_request_noblock(self):
try:
# 注意,BaseServer没有get_request()方法,子类TCPServer实现了get_request()方法
# self.get_request(),巧妙之处:此时的self为TCPServer的实例,TCPServer的实例调用自己类的方法,完美
request, client_address = self.get_request() # 每个Client端,都会创建一个newsocket对象与其建立通信连接
except OSError:
return
if self.verify_request(request, client_address):
try:
self.process_request(request, client_address) # 跟进代码2
except Exception:
self.handle_error(request, client_address)
self.shutdown_request(request)
except:
self.shutdown_request(request)
raise
else:
self.shutdown_request(request)
...
# 3
def process_request(self, request, client_address):
"""Call finish_request.
Overridden by ForkingMixIn and ThreadingMixIn.
""" # 该方法在ForkingMixIn and ThreadingMixIn中覆盖
self.finish_request(request, client_address) # 跟进代码3
self.shutdown_request(request)
...
# 4
def finish_request(self, request, client_address):
# RequestHandlerClasss实例化,即BaseRequestHandler类实例化
self.RequestHandlerClass(request, client_address, self)
BaseRequestHandler
BaseServer实例调用serve_forrver() / handle_request()之后,最后会实例化这个类
注意:未调用serve_forrver() / handle_request()之前,Server端只是bing和listen,没有accept;
- __init__(self, request, client_address, server);
- request,和Client端通信的服务端new-socket对象,包含laddr和raddr,raddr即client_address;
- client_address,Clinet端的IP地址和Port端口,二元组(‘IP地址’,Port);
- server,TCPServer实例本身,其实例属性self.socket为socket监听对象;
BaseRequestHandler初始化过程
- 先调用self.setup(),一般在self.handle()中循环发送消息,退出之后一定会调用self.finish归还资源;
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() # 保证资源socket资源归还
BaseRequestHandler实例方法
- self.setup()方法,为 self / self.server增加属性等;
- self.handle()方法,收发核心,相当与socket.socket.recv()、sockte.sockte.send()。Client端和newsocket对象的消息收发,都在此方法中实现;
- self.finish()方法,requeset.close()归还socket资源;
# TCP编程,Server端每连接一个Client端,都会生成一个新的socket对象
def setup(self): # 每一个连接初始化
pass
def handle(self): # 每一个请求处理
pass #
def finish(self): # 每一个连接清理
pass
- 应用举例
class MyHandler(socketserver.BaseRequestHandler):
def handle(self):
pass
def setup(self):
pass
def finish(self):
pass
serversocket.TCPServer(BaseServer)
-
__init__(self, server_address, RequestHandlerClass, bind_and_activate=True)
- server_address,服务器绑定的地址信息;
- RequestHandlerClass,处理请求的BaseRequestHandler的子类;
- bind_and_activate=True,缺省True,服务端绑定Ip和Port,并开始监听
-
注意事项
- 同步阻塞类
- 构造器函数中,调用了socket.bind()、socket.listen()方法
get_request(self)
方法调用socket.accept()方法
-
部分源码阅读
# TCPServer部分源码
class TCPServer(BaseServer):
def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
BaseServer.__init__(self, server_address, RequestHandlerClass) # 调用父类构造器方法
self.socket = socket.socket(self.address_family,
self.socket_type) # 对应TCP编程中的socket监听对象
if bind_and_activate:
try:
self.server_bind() # 对应self.socket.bind(),绑定Ip和Port
self.server_activate() # 对应self.socket.listen(),开始监听
except:
self.server_close() # self.socket.close(),异常关闭监听socket监听对象
raise # 将捕获的异常重新抛出
...
def get_request(self): # 注意,实例化时不会调用get_request方法
return self.socket.accept()
ThreadingTCPServer(ThreadingMixIn, TCPServer)
ThreadingTCPServer是异步非阻塞的,可以同时处理多个连接
- ThreadingMixIn,通过继承关系创建不同的线程运行BaseRequestHandler类实例对象;
- TCPServer是同步的阻塞的,一个连接的handle方法执行完毕,参能处理另一个连接,且只有主线程;
class ThreadingMixIn:
def process_request_thread(self, request, client_address):
try:
self.finish_request(request, client_address) # 生成BaseRequestHandler类实例
except Exception:
self.handle_error(request, client_address)
finally:
self.shutdown_request(request) # 归还资源
def process_request(self, request, client_address):
t = threading.Thread(target = self.process_request_thread,
args = (request, client_address)) # 创建线程
t.daemon = self.daemon_threads # non-daemon线程
t.start()
测试代码
- 初始化代码
import socketserver
import threading
import logging
logging.basicConfig(format='%(asctime)s %(message)s',level=logging.INFO)
class MyHandler(socketserver.BaseRequestHandler):
def __init__(self,request,client_address,server):
# 函数的传参过程,易混点1
super().__init__(request=request, client_address=client_address, server=server)
self.event = threading.Event()
# 打印不出来,因为super().__init__调用了handle,且当前handle死循环
logging.info(self.request)
super().__init__(request=request,client_address=client_address, server=server)
,- 等号右边的为MyHandler构造函数的形参,是MyHandler构造函数的本地变量
- 等号左边为BaseRequestHandler构造函数的形参,是BaseRequestHandler构造函数的本地变量
- 两者是不同作用域且只是标识符相同的不同本地变量
- 将传入Myhandler构造函数的实参,再次指向BaseRequestHandler构造函数的形参即不同作用域的不同本地变量指向了同一个对象,实现函数的传参
- 测试代码
class MyHandler(socketserver.BaseRequestHandler):
def __init__(self,request,client_address,server):
super().__init__(request=request, client_address=client_address, server=server)
self.event = threading.Event() # 无法成功赋值
logging.info(self.request)
def handle(self):
super().handle() # 可以不调用,父类handle什么都没有做
logging.info('handle')
while not self.event.is_set():
data = self.request.recv(1024)
logging.info(data)
self.request.send(data)
def finish(self):
super().finish()
self.request.close()
server = socketserver.ThreadingTCPServer(('127.0.0.1',9999),MyHandler)
threading.Thread(target=server.serve_forever,name='EchoServer',daemon=True).start()
while True:
cmd = input('>>>>').strip().lower()
if cmd == 'quit':
server.server_close()
break
print(threading.enumerate())
self.event = threading.Event()
无法成功赋值
- 父类BaseRequestHandler初始化的时候,调用了self.handle();
- 而此处的self.handle()先于
self.event = threading.Event()
,故抛异常; - 在异常处停止运行,所以无法赋值成功,放在super().__init__()之前即可;
总结
创建服务器的步骤
- 从BaseRequestHandler类派生出子类,并覆盖handle()方法来创建请求处理程序类,此方法将处理传入的请求;
- 实例化一个服务类,传参服务器的地址和请求处理类
- 调用服务器实例的handle_request()或者serve_forever()方法
- 调用server_close()关闭套接字
TCP群聊
初步尝试
import socketserver
import threading
import logging
logging.basicConfig(format='%(asctime)s %(message)s',level=logging.INFO)
class MyHandler(socketserver.BaseRequestHandler):
def handle(self):
super().handle() # 可以不调用,父类handle什么都没有做
while not self.event.is_set():
try:
data = self.request.recv(1024)
except Exception as e:
logging.info(e)
self.finish()
else:
if data == b'' or data == b'quit':
self.server.clients.discard(self.request)
self.finish()
break
logging.info((self.client_address,data)) # 观察client端发送的信息
for c in self.server.clients:
c.send(data)
def setup(self):
super().setup()
self.event = threading.Event()
if not hasattr(self.server,'clients'): # 动态为self.server添加属性clients
setattr(self.server,'clients',set()) # 无则创建
self.server.clients.add(self.request) # 有则追加
def finish(self):
super().finish()
self.event.set()
self.request.close()
server = socketserver.ThreadingTCPServer(('127.0.0.1',9999),MyHandler)
threading.Thread(target=server.serve_forever,name='EchoServer',daemon=True).start()
server.handle_request()
while True:
cmd = input('>>>>').strip().lower()
if cmd == 'quit':
server.server_close()
break
print(threading.enumerate())
TCP群聊实现
import socketserver
import threading
import logging
logging.basicConfig(format='%(asctime)s %(message)s',level=logging.INFO)
class MyHandler(socketserver.BaseRequestHandler):
def handle(self):
super().handle() # 可以不调用,父类handle什么都没有做
# print('-'*30) # 均为测试
# print(self.request)
# print(self.server)
# print(self.client_address)
# print(self.__dict__)
# print(self.server.__dict__)
#
# print(threading.enumerate())
# print(threading.current_thread())
# print('-'*30)
while not self.event.is_set():
data = self.request.recv(1024)
logging.info(data)
for c in self.server.clients:
c.send(data)
self.finish()
def setup(self):
super().setup()
self.event = threading.Event()
# if not hasattr(self.server,'clients'): # 动态为self.server添加属性clients
# setattr(self.server,'clients',set()) # 无clients,添加
# self.server.clients.add(self.request) # 有则追加
# else:
# self.server.clients.add(self.request)
if not hasattr(self.server,'clients'): # 动态为self.server添加属性clients
setattr(self.server,'clients',set()) # 无则创建
self.server.clients.add(self.request) # 有则追加
logging.info(self.server.clients) # 观察添加情况
def finish(self):
super().finish()
self.event.set()
self.request.close()
addr = ('127.0.0.1',9999)
server = socketserver.ThreadingTCPServer(('127.0.0.1',9999),MyHandler)
threading.Thread(target=server.serve_forever,name='EchoServer',daemon=True).start()
while True:
cmd = input('>>>>').strip().lower()
if cmd == 'quit':
server.server_close()
break
print(threading.enumerate())
addr = ('127.0.0.1',9999)
server = socketserver.ThreadingTCPServer(('127.0.0.1',9999),MyHandler)
threading.Thread(target=server.serve_forever,name='EchoServer',daemon=True).start()
while True:
cmd = input('>>>>').strip().lower()
if cmd == 'quit':
server.server_close()
break
print(threading.enumerate())