之前实现的Socket不能实现并发,只支持一对一的连接,socketserver内部使用IO多路复用以及“多进程”和”多线程“,从而实现并发处理多个客户端请求
一. Socketserver
模块中包含服务类(server class)和请求处理类(request handle class)
服务类
包含有5个server类,只能实现同步操作,单线程只能按照顺序一次处理客户端请求
(继承关系)
+------------+
| BaseServer |
+------------+
↓
+-----------+ +------------------+
| TCPServer |------- | UnixStreamServer |
+-----------+ +------------------+
↓
+-----------+ +--------------------+
| UDPServer |------- | UnixDatagramServer |
+-----------+ +--------------------+
2个Mixin类
+--------------+ +----------------+
| ForkingMixIn | | ThreadingMixIn |
+--------------+ +----------------+
各自实现了多进程和多线程功能。
ForkingMixIn只在支持fork()的POSIX平台上可用
将同步的server类和Mixin类组合就可以实现异步服务功能,所以socketserver下主要使用以下几个类
- class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
- class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
- class ForkingUDPServer(ForkingMixIn, UDPServer): pass
- class ForkingTCPServer(ForkingMixIn, TCPServer): pass
Windows系统没有提供os.fork()接口,所以无法使用多进程的ForkingDUPServer和ForkingTCPServer类,而Linux和Unix多进程和多线程都可以使用
一般过程
服务器主要负责接受客户端的连接请求,当一个新的客户端请求到来后,将分配一个新的线程去处理这个请求(异步服务器ThreadingTCPServer),而与客户端信息的交互则交给了专门的请求处理类(RequestHandlerClass)处理。
import socketserver
# 创建一个基于TCP的server对象,并使用BaseRequestHandler处理客户端发送的消息
server = socketserver.ThreadingTCPServer(("127.0.0.1", 8000), BaseRequestHandler)
server.serve_forever() # 启动服务器
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
在server = socketserver.ThreadingTCPServer((“127.0.0.1”, 8000), BaseRequestHandler)中,BaseRequestHandler将作为参数绑定到服务器的实例上,服务器启动后,每当有一个新的客户端接接入服务器,将会实例化一个请求处理对象,并传入三个参数,request(连接客户端的socket)、client_address(远程客户端的地址)、server(服务器对象),执行init方法,将这三个参数保存到对应属性上。这个请求处理对象便可以与客户端交互了。
实例
服务端
import socketserver
import threading
class MyRequestHandler(socketserver.BaseRequestHandler):
""" BaseRequestHandler的实例化方法中,获得了三个属性
self.request = request # 该线程中与客户端交互的 socket 对象。
self.client_address # 该线程处理的客户端地址
self.server = server # 服务器对象
"""
def handle(self):
while True:
msg = self.request.recv(1024) # 接受客户端的数据
if msg == b"quit" or msg == "": # 退出
break
print(msg.decode('utf-8'))
self.request.send(msg.upper()) # 将消息发送回客户端
def finish(self):
self.request.close() # 关闭套接字
if __name__ == "__main__":
# 创建一个基于TCP的server对象,并使用BaseRequestHandler处理客户端发送的消息
server = socketserver.ThreadingTCPServer(("127.0.0.1", 8000), MyRequestHandler)
server.serve_forever() # 启动服务器
创建了一个ThreadingTCPServer服务器,然后在传入的处理类MyRequestHandler,并在handle方法中提供与客户端消息交互的业务逻辑,此处只是将客户端的消息的大写返回客户端。最后在finish方法中关闭资源,finish方法使用了finally机制,保证了这些代码一定会执行。
客户端
import socket
# 监听本机网卡,端口
ip_port = ('127.0.0.1', 8000)
# 收发消息长度
BUFSIZE = 1024
# 获取客户套接字
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 尝试链接服务器
s.connect_ex(ip_port)
# 可以一直收发消息
while True:
msg = input('>>: ').strip()
# 发送消息给服务端
if not msg:
continue
s.send(msg.encode('utf-8'))
feedback = s.recv(BUFSIZE)
# 接收服务端消息
print(feedback.decode('utf-8'))
# 关闭客户端套接字
s.close()
总结
如何创建一个socketserver:
1. 创建一个请求处理的类,并且这个类要继承BaseRequestHandler class ,并且还要重写父类里handle()方法(handle方法默认结束后断开链接继续监听)
2. 必须实例化 TCPServer,并且传递server IP和上面创建的请求处理类,给这个TCPServer;
3. Server对象调用server.handle_requese()(只处理一个请求)或server.server_forever()(处理多个一个请求,永远执行)。如果不调用server_forever,在外面循环调用handle_request
server_forever:服务循环,监听端口,处理请求
4. 关闭连接server_close()
二. TCP\UDP版本源码分析
以下述代码为例,分析socketserver源码:
ftpserver=socketserver.ThreadingTCPServer(('127.0.0.1',8080),FtpServer)
ftpserver.serve_forever()
查找属性的顺序:ThreadingTCPServer->ThreadingMixIn->TCPServer->BaseServer
- 实例化得到ftpserver,先找类ThreadingTCPServer的__init__,在TCPServer中找到,进而执行server_bind,server_active
- 找ftpserver下的serve_forever,在BaseServer中找到,进而执行self._handle_request_noblock(),该方法同样是在BaseServer中
- 执行self._handle_request_noblock()进而执行request, client_address = self.get_request()(就是TCPServer中的self.socket.accept()),然后执行self.process_request(request, client_address)
- 在ThreadingMixIn中找到process_request,开启多线程应对并发,进而执行process_request_thread,执行self.finish_request(request, client_address)
- 上述四部分完成了链接循环,本部分开始进入处理通讯部分,在BaseServer中找到finish_request,触发我们自己定义的类的实例化,去找__init__方法,而我们自己定义的类没有该方法,则去它的父类也就是BaseRequestHandler中找
真正的请求处理函数
BaseServer._handle_request_noblock()
- get_request: 接收请求 accept
- verify_request: 验证,做一些验证工作,比如 ip 过滤
- process_request: 处理请求,子类重写该方法后,需要 调用 SocketServer.BaseServer.process_request,
- BaseServer.process_request 中有 BaseRequestHandler 的回调动作,实例化用户定义的 handler, __init__ 中完成对 handle() 的调用
- shutdown_reques: 关闭连接
def _handle_request_noblock(self):
"""Handle one request, without blocking.
I assume that select.select has returned that the socket is
readable before this function was called, so there should be
no risk of blocking in get_request().
"""
try:
# 接收请求
# get_request 由子类实现,一般为接收请求,返回 socket
request, client_address = self.get_request()
except socket.error:
return
if self.verify_request(request, client_address):
try:
self.process_request(request, client_address)
except:
self.handle_error(request, client_address)
self.shutdown_request(request)
else:
self.shutdown_request(request)
UDPServer get_request 返回的是一个 (data, socket) 的 tuple,而 TCPServer 返回的是 socket。 handle 中要区分处理,msg, sock = self.request(msg 已经获取,无需额外 recv)
对于数据的传送, 应该使用 socket 的 sendto() 和 recvfrom() 方法。 尽管传统的 send() 和 recv() 也可以达到同样的效果, 但是前面的两个方法对于 UDP 连接而言更普遍。
TCPServer
def get_request(self):
"""Get the request and client address from the socket.
May be overridden.
"""
return self.socket.accept()
UDPServer
def get_request(self):
data, client_addr = self.socket.recvfrom(self.max_packet_size)
return (data, self.socket), client_addr