Python中的SocketServer模块
原创
©著作权归作者所有:来自51CTO博客作者带着梦想飞翔的原创作品,请联系作者获取转载授权,否则将追究法律责任
Python中的SocketServer模块
socket编程过于底层,编程虽然有套路,但是想要写出健壮的代码还是比较困难的,所以很多语言都对socket底层 API进行封装,Python的封装就是——socketserver模块。它是网络服务编程框架,便于企业级快速开发。
- 类的继承关系
+------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+
- SocketServer简化了网络服务器的编写
- TCPServer
- UDPServer
- UnixStreamServer
- UnixDatagramServer
- ForkingMixIn
- ThreadingMixIn
- class ForkingUDPServer(ForkingMixIn,UDPServer):pass
- class ForkingTCPServer(ForkingMixIn,TCPServer):pass
- class ThreadingUDPServer(ThreadingMixIn,UDPServer):pass
- class ThreadingTCPServer(ThreadingMixIn,TCPServer):pass
- fork是创建多进程,thread是创建多线程。
- fork需要操作系统支持,Windows不支持。
- ThreadingUDPServer与ThreadingTCPServer类中的特有属性:
- daemon_threads=False #默认值是False表示创建的线程都不是daemon线程,改为True表示创建的所有线程都是daemon线程
- block_on_close=False #默认值为Fasle,如果为True,可以设置为守护线程3.7版本可以用
编程接口
- socketserver.BaseServer(server_address,RequestHandlerClass) #实例化一个服务器
- server_address #服务器绑定的地址信息,是一个元组(ip,prost)
- RequestHandlerClass #必须是BaseRequestHandler的一个子类。
- 在BaseServer中原码代码如下:
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
# 处理请求的方法,会实例化一个RequestHandlerClass对象
def finish_request(self, request, client_address):
"""Finish one request by instantiating RequestHandlerClass."""
self.RequestHandlerClass(request, client_address, self)
- BaseServer中定义的接口常用属性和方法
方法
| 含义
|
server_address | 服务器正在监听的地址和端口,在不同协议格式不一样。Internet协议上是一个元组(“127.0.0.1”,80)
|
socket | 服务器正在监听的套接字对象。socket
|
request_queue_size
| 请求队列的大小。如果处理单个请求需要很长时间,那么在服务器繁忙时到达的任何请求都会被放入队列中,直到request_queue_size请求为止。一旦队列满了,来自客户机的进一步请求将得到一个“连接被拒绝”错误。默认值通常是5,但是可以被子类覆盖。
|
address_family
| 服务器套接字所属的协议族。常见的例子是套接字。AF_INET socket.AF_UNIX。
|
socket_type
| 服务器使用的套接字类型;套接字。SOCK_STREAM套接字。SOCK_DGRAM是两个常见的值。
|
timeout
| 超时持续时间,以秒为单位度量,如果不需要超时,则为None。如果handle_request()在超时期间没有收到传入的请求,则调用handle_timeout()方法。
|
handle_request() | 处理单个请求,同步执行 这个函数按顺序调用以下方法:get_request()、verify_request()和process_request()。如果处理程序类的用户提供的handle()方法引发异常,将调用服务器的handle_error()方法。如果在超时秒内没有收到任何请求,那么将调用handle_timeout()并返回handle_request()。
|
server_forever(poll_interval=0.5) | 异步执行,处理请求。每隔poll_interval秒轮询一次。 忽略timeout属性,还会调用service_actions(),在ForkingMixIn的子类中定义,可以用来清理僵尸进程。
|
shutdown() | 告诉serve_forever循环停止。并等待他结束。
|
server_close() | 关闭服务器
|
finish_request(request,client_address)
| 通过实例化RequestHandlerClass并调用它的handle()方法来处理请求
|
server_bind()
| 由服务器的构造函数调用,以将套接字绑定到所需的地址。可能会被覆盖。
|
verify_request(request,client_address)
| 必须返回一个布尔值;如果值为True,请求将被处理,如果值为False,请求将被拒绝。可以重写此函数来实现服务器的访问控制。默认实现总是返回True。
|
BaseRequestHandler类
- 是和用户连接的用户请求处理类的基类,
-
BaseRequestHandler(request,client_address,server)
#构造函数
- request #是和客户端的连接的socket对象
- client_address #是客户端地址
- server #是TCPServer实例本身
- 服务端Server实例接收用户请求后,最后会实例化这个类。它被初始化时,送入3个构造参数:request, client_address, server自身 以后就可以在BaseRequestHandler类的实例上使用以下属性:
- self.request是和客户端的连接的socket对象
- self.server是TCPServer实例本身
- self.client_address是客户端地址
- 这个类在初始化的时候,它会依次调用3个方法。子类可以覆盖这些方法。
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 socketserver
import socket
class MyBaseRequestHandle(socketserver.BaseRequestHandler):
def setup(self):
super().setup() #可以不调用父类的setup()方法,父类的setup方法什么都没做
print("----setup方法被执行-----")
def handle(self):
super().handle() #可以不调用父类的handler(),方法,父类的handler方法什么都没做
print("-------handler方法被执行----")
print(self.server)
print(self.request) #服务
print(self.client_address) #客户端地址
print(self.__dict__)
print("- "*30)
print(self.server.__dict__)
print("- "*30)
sk:socket.socket = self.request
data = sk.recv(1024)
print(data)
sk.send("{}-{}".format(sk.getpeername(),data).encode())
print("----------handler end ----------")
def finish(self):
super().finish() #可以不调用父类的finish(),方法,父类的finish方法什么都没做
print("--------finish方法被执行---")
laddr = "127.0.0.1",3999
tcpserver = socketserver.TCPServer(laddr,MyBaseRequestHandle) #注意:参数是MyBaseRequestHandle
tcpserver.handle_request() #只接受一个客户端连接
# tcpserver.serve_forever() #永久循环执行,可以接受多个客户端连接
- 每个不同的连接上的请求过来后,生成这个连接的socket对象即self.request,客户端地址是self.client_address。
- 将ThreadingTCPServer换成TCPServer,当每个客户端连接进来就会创建一个新的线程。
- ThreadingTCPServer是异步的,可以同时处理多个连接。
- TCPServer是同步的,一个连接处理完了,即一个连接的handle方法执行完了,才能处理另一个连接,且只有主线程。
总结
- 从BaseRequestHandler类派生出子类,并覆盖其handle()方法来创建请求处理程序类,此方法将处理传入请求
- 实例化一个服务器类,传参服务器的地址和请求处理类
- 调用服务器实例的handle_request()或serve_forever()方法
- 调用server_close()关闭套接字
实现EchoServer
- Echo:来声明消息,回显什么消息,即客户端发来什么消息,返回什么消息
import logging
import sys
import socketserver
import socket
import threading
logging.basicConfig(format="%(asctime)s %(thread)d %(threadName)s %(message)s",stream=sys.stdout,level=logging.INFO)
class Handler(socketserver.BaseRequestHandler):
def setup(self):
super().setup()
self.event = threading.Event()
logging.info("新加入了一个连接{}".format(self.client_address))
def handle(self):
super().handle()
sk:socket.socket = self.request
while not self.event.is_set():
try:
data = sk.recv(1024).decode()
except Exception as e:
logging.info(e)
break
logging.info(data)
msg = "{}-{}".format(self.client_address,data).encode()
sk.send(msg)
def finish(self):
super().finish()
self.event.set()
self.request.close()
if __name__ == "__main__":
server = socketserver.ThreadingTCPServer(("127.0.0.1",3999),Handler)
threading.Thread(target=server.serve_forever,name="server").start()
while True:
cmd = input(">>>")
if cmd.strip() == "quit":
server.server_close()
break
logging.info(threading.enumerate())
实战,使用socketserver实现群聊的server
- 使用ThreadingTCPServer改写ChatServer
- 使用BaseRequestHandler定义Handler
import logging
import sys
import socketserver
import socket
import threading
logging.basicConfig(format="%(asctime)s %(thread)d %(threadName)s %(message)s",stream=sys.stdout,level=logging.INFO)
log = logging.getLogger()
class Handler(socketserver.BaseRequestHandler):
lock = threading.Lock()
clients = {}
def setup(self):
super().setup()
self.event = threading.Event()
with self.lock:
self.clients[self.client_address] = self.request
log.info("新加入了一个连接{}".format(self.client_address))
def handle(self):
super().handle()
sock:socket.socket = self.request
while not self.event.is_set():
try:
data = sock.recv(1024)
except Exception as e:
log.error(e)
data = b""
log.info(data)
if data == b"by" or data == b"":
break
msg = "service:{}-->{}".format(self.client_address, data).encode()
expc = [] # 记录sock出错时对应的clients
with self.lock:
for c, sk in self.clients.items():
try:
sk.send(msg) # 可能在发送消息是就出错
except:
expc.append(c)
for c in expc:
self.clients.pop(c)
def finish(self):
super().finish()
self.event.set()
with self.lock:
if self.client_address in self.clients:
self.clients.pop(self.client_address)
self.request.close()
log.info("{}退出了".format(self.client_address))
if __name__ == "__main__":
server = socketserver.ThreadingTCPServer(("127.0.0.1",3999),Handler)
server.daemon_threads = True #设置所有创建的线程都为Daemo线程
threading.Thread(target=server.serve_forever,name="server",daemon=True).start()
while True:
cmd = input(">>>")
if cmd.strip() == "quit":
server.shutdown() #告诉serve_forever循环停止。
server.server_close()
break
logging.info(threading.enumerate())
- 使用StreamRequestHandler定义handler
import logging
import sys
import socketserver
import socket
import threading
logging.basicConfig(format="%(asctime)s %(thread)d %(threadName)s %(message)s",stream=sys.stdout,level=logging.INFO)
log = logging.getLogger()
class Handler(socketserver.StreamRequestHandler):
lock = threading.Lock()
clients = {}
def setup(self):
super().setup()
self.event = threading.Event()
with self.lock:
self.clients[self.client_address] = self.request
log.info("新加入了一个连接{}".format(self.client_address))
def handle(self):
super().handle()
import io
rfile:io.TextIOWrapper= self.rfile
while not self.event.is_set():
try:
data = rfile.read1(1024) #类似于sock.recv(1024)
# data = rfile.readline() #行读取
except Exception as e:
log.error(e)
data = b""
log.info(data)
if data == b"by" or data == b"":
break
msg = "service:{}-->{}".format(self.client_address, data).encode()
expc = [] # 记录sock出错时对应的clients
with self.lock:
for c, sk in self.clients.items():
try:
sk.send(msg) # 可能在发送消息是就出错
except:
expc.append(c)
for c in expc:
self.clients.pop(c)
def finish(self):
super().finish()
self.event.set()
with self.lock:
if self.client_address in self.clients:
self.clients.pop(self.client_address)
self.request.close()
log.info("{}退出了".format(self.client_address))
if __name__ == "__main__":
server = socketserver.ThreadingTCPServer(("127.0.0.1",3999),Handler)
server.daemon_threads = True #设置所有创建的线程都为Daemo线程
threading.Thread(target=server.serve_forever,name="server",daemon=True).start()
while True:
cmd = input(">>>")
if cmd.strip() == "quit":
server.shutdown() #告诉serve_forever循环停止。
server.server_close()
break
logging.info(threading.enumerate())
- 为每一个连接提供RequestHandlerClass类实例,依次调用setup、handle、finish方法,且使用了try…finally结构 保证finish方法一定能被调用。这些方法依次执行完成,如果想维持这个连接和客户端通信,就需要在handle函数 中使用循环。
- socketserver模块提供的不同的类,但是编程接口是一样的,即使是多进程、多线程的类也是一样,大大减少了编 程的难度。
- 将socket编程简化,只需要程序员关注数据处理本身,实现Handler类就行了。这种风格在Python十分常见。