千家信息网

python 之 SocketServer

发表于:2024-11-30 作者:千家信息网编辑
千家信息网最后更新 2024年11月30日,20.17. SocketServer--网络框架注意:SocketServer 在 python 3 中更名为 socketserver。 在将代码转换为 python 3 的版本时,2to3 工具
千家信息网最后更新 2024年11月30日python 之 SocketServer

20.17. SocketServer--网络框架

注意:SocketServer 在 python 3 中更名为 socketserver。 在将代码转换为 python 3 的版本时,2to3 工具会自动进行导入适配。


源码:Lib/SocketServer.py


SocketServer 模块简化了编写网络服务器应用的步骤。它有四个具体的基础服务器类:

class SocketServer.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)

该类使用 TCP 网络协议,在客户端和服务器之间提供持续的流式数据如果 bind_and_activate 为真,则构造方法会自动调用 server_bind() 和 server_activate() 方法。其余参数传给基类 BaseServer。

class SocketServer.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)

该类使用数据报文,而且报文包是离散的;在数据传输过程中,包到达的次序可能会错乱,或者出现丢失的情况。类参数和 TCPServer 一样。

class SocketServer.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True)class SocketServer.UnixDatagramServer(server_address, RequestHandlerClass, bind_and_activate=True)

这两个类使用得不如前两个类频繁,其用法类似,只是采用的是 Unix 域下的 sockets;它们不能在非 Unix 平台上使用。类参数和 TCPServer 一样。


以上四个类均采用同步方式处理网络请求,即在处理下一个请求之前必须先处理完本次请求。如果请求耗时很长,比如计算量大,或数据太多引起客户端处理慢等等,(同步方式)就不太合适了。解决的办法是创建独立的进程或者线程用来应对每个请求;mix-in 类 ForkingMixIn 和 ThreadIingMixin 可以支持这种异步行为。


创建一个服务器需要经历几个步骤。首先,你要写一个请求解析类,继承自基类 BaseRequestHandler,同时改写基类的 handle() 方法;这个方法会处理进入服务器的每一个请求。第二步,你必须创建一个服务器类的对象,将服务器地址和请求解析类传递给该对象。之后调用服务器对象的 handle_request() 或者 server_forever() 方法处理一个或者多个请求。最后,调用 server_close() 方法关闭 socket。


若想继承基类 ThreadingMixIn 完成多线程连接行为,你需要显式地声明线程在遇到突然关闭时如何处理。ThreadingMixIn 类有一个名为 daemon_threads 的属性,表示线程如果终止,服务器是否应该等待。如果你希望线程自动处理,则必须显式设置该标志;默认情况下,其值为 False,表示只有当所有 ThreadingMixIn 创建的线程退出,Python 才会退出。


无论采用何种类型的网络协议,服务器类都有相同的外部方法和属性。

20.17.1. 服务器创建要点

继承的关系表格中有 5 个类,其中四种类型代表四个同步方式的服务器类。

+------------+| BaseServer |+------------+      |      v+-----------+        +------------------+| TCPServer |------->| UnixStreamServer |+-----------+        +------------------+      |      v+-----------+        +--------------------+| UDPServer |------->| UnixDatagramServer |+-----------+        +--------------------+

注意 UnixDatagramServer 继承自 UDPServer,而不是 UnixStreamServer--IP 和 Unix 流式服务器之间的唯一区别是地址族,两个 Unix 服务器使用相同的地址族。

class SocketServer.ForkingMixInclass SocketServer.ThreadingMixIn

每种类型的服务器都可以使用 mix-in 类创建多进程或者多线程。例如下面的例子,创建一个 ThreadingUDPServer 类:

class ThreadingUDPServer(ThreadingMixIn, UDPServer):    pass

注意,mix-in 类在前面,因为它会重写 UDPServer 中的一个方法。设置不同属性也可以修改底层服务器的行为。


以下 ForkingMixIn 和 Forking 类只能在 POSIX 平台上使用,支持 fork()。

class SocketServer.ForkingTCPServerclass SocketServer.ForkingUDPServerclass SocketServer.ThreadingTCPServerclass SocketServer.ThreadingUDPServer

这四个类是预定义的 mix-in 类。


为给客户端提供服务,你必须从 BaseRequestHandler 类中派生子类,并重写 handle() 方法。之后就可以通过合并某种类型的服务器与 BaseRequestHandler 子类来提供该类型的服务。报文服务和流式服务的请求解析类肯定是不同的。你可以继承 StreamRequestHandler 或者 DatagramRequestHandler 类来隐藏这种差异。


当然,你也可以自主发挥。比如,如果服务在内存中的状态可以被不同的请求修改,而子进程的修改如果根本到不了父进程的初始状态并传给子进程,那么创建一个服务器的子进程毫无意义。在这种情况下,你可以创建一个服务器的子线程,但为保证共享数据的完整性,可能需要加锁。


另一个方面,如果你创建的一个 HTTP 服务器,其所有数据都存在外部媒介(例如文件系统)上,那么同步服务器在解析一个因客户端接收数据慢导致耗时很长的请求时,就基本上只能"耗"在该服务上,两耳不闻窗外事了。这个时候,创建一个子线程或者子进程就很合适。


某些情况下,基于请求数据,将请求的一部分内容作同步处理,而将剩余部分放在子进程中处理是可以的。可以创建一个同步服务器,而在 handle() 方法中 fork 一个子进程完成上述动作。


在一个既不支持多线程也不支持 fork 的环境下(或者在特定环境下,它们的代价都太大或者不合适),解析多个同时接收的请求的另一种办法是维持一张显式的表,表中记录部分完成的请求,通过 select() 方法来决定哪一个是即将工作的请求(或者需要解析新请求)。这对于每个客户端可能会长时间连接的流式服务尤其重要(前提是多线程或者多进程不能使用)。可以去看看 asyncore 模块,里面提到另一种方法来处理这种情况。

20.17.2. 服务器对象

class SocketServer.BaseServer(server_address, RequestHandlerClass)

BaseServer 是模块里所有服务器对象的父类。它定义了以下接口,但是其中的绝大部分的方法都不需要实现,因为它们在子类里都实现过了。需要两个参数,分别存在 server_address 和 RequestHandlerClass 属性里。

fileno()

获取服务器正在监听的 socket 文件描述符。该方法最常用的做法是将返回值传给 select.select() 方法,允许在同一个进程中监听多个服务器。

handle_request()

处理单一请求。这个函数按顺序调用以下方法:get_request(), verify_request() 和 process_request()。如果用户提供的请求解析类 handle() 方法抛出了异常,则服务器会调用 handle_error() 方法。如果在 timeout 秒内没有收到请求,则会调用 handle_timeout() 方法,同时 handle_request() 返回。

serve_forever(poll_interval=0.5

解析请求,直至接收到一个显示的 shutdown() 请求为止。每隔 poll_interval 秒会检查一下是否收到 shutdown 请求。忽略 timeout 属性。如果你需要进行周期性的任务,请在另一个线程里执行。

shutdown()

告诉 serve_forever() 停止循环,并等待 serve_forever() 停止之后才返回。

server_close()

清理服务器。可以被子类改写。

address_family

服务器的 socket 所属的协议族。常用值为 socket.AF_INET 和 socket.AF_UNIX

RequestHandlerClass

用户提供的请求解析类;每个请求都会创建一个解析类对象。

server_address

服务器正在监听的地址。地址值的格式取决于协议族;参考 socket 模块的文档以获取更多详细信息。因特网协议中,服务器地址是一个二元组,它包含指定的地址字符串和一个整型端口号:例如 ( '127.0.0.1',80)

socket

服务器监听请求时使用的 socket 对象


服务器类支持以下类属性:

allow_resue_address

无论服务器是否允许地址重用,该值默认为 False,可以在子类中修改。

request_queue_size

请求队列的大小。如果处理单一请求时间很长,则当服务器忙的时候,任何到达的请求会进入到一个队列中,直到个数达到 request_queue_size。一旦队列满了,那么后来的请求会收到 "Connection denied" 的错误回馈。默认值通常是 5,但是可以在子类进行修改。

socket_type

服务器使用的 socket 类型;通常取 socket.SOCK_STREAM 和 socket.SOCK_DGRAM

timeout

超时时长,单位是秒,如果不需要超时设置,则为 None。如果 handle_request() 在超时时段内没有收到请求,则会调用 handle_timeout() 方法。


有几个服务器方法可以在子类中(如 TCPServer) 中改写;这些方法对于服务器对象的使用者而言是没有什么用处的。

finish_request()

创建 RequestHandlerClass 对象并真正处理请求,调用解析类 handle() 的方法。

get_request()

必须接受一个 socket 请求,返回一个二元组,包括新的 socket 对象用于和客户端通信以及客户端地址

handle_error(request, client_address)

如果 RequestHandlerClass 对象的 handle() 函数抛出了异常,则会调用 handle_error()。handle_error 默认行为是将异常调用栈打印到标准输出终端上,并继续解析下一个请求。

handle_timeout()

当 timeout 属性不是 None,而是一个有效值,而经过 timeout 时间之后还是没有收到请求时,会调用这个函数。handle_timeout 的默认行为是 fork 一个服务器的子进程用于收集任何退出的子进程状态,而在服务器的多个线程中,该方法什么也不做。

process_request(request, client_addresss)

调用 finish_request() 创建一个 RequestHandlerClass 对象。如果需要,这个函数会创建一个新的进程或者线程用于解析请求;ForkingMixIn 和 ThreadingMixIn 类会完成这个动作。

server_activate()

由类的构造方法调用,用于激活服务器。TCP 服务器里的 server_activate() 默认只会调用 listen() 方法,用于监听服务器的 socket 对象。该方法可以被子类改写。

server_bind()

由类的构造方法调用,用于将 socket 对象绑定到指定地址上。该方法可以被子类改写。

verify_request(request, client_address)

必须返回一个布尔值;如果值为 True,则处理请求,否则拒绝处理。verify_request 可以被子类改写,凭借该方法,服务器可以实现访问控制。该方法默认返回 True。

20.17.3.请求解析类对象

class SocketServer.BaseRequestHandler

它是所有请求解析类对象的父类。有以下接口。请求解析类的子类必须实现 handle() 方法,可以改写其他方法。每个请求都会创建一个子类对象。

setup()

在 handle() 函数之前调用,用于初始化行为。默认什么也不做

handle()

这个函数必须完成所有期望的工作,为请求提供服务。默认什么也不做。handle() 可以使用几个实例属性;请求实例 self.request;客户端地址 self.client_address;服务器实例 self.server,以便访问服务器的信息。


报文服务或者流式服务的 self.request 类型是不一样的。对于流式服务,self.request 是一个 socket 对象;而报文服务,self.request 是一对字符串加 socket。

finish()

在 handle() 方法调用之后执行所有清理操作。默认什么也不做。如果 setup() 抛出异常,那么这个函数不会被执行。


class SocketServer.StreamRequestHandler

class SocketServer.DatagramRequestHandler


这些 BaseRequestHandler 的子类改写 setup() 和 finish() 方法,同时提供了 self.rfile 和 self.wfile 属性。self.rfile 是可读的,用于获取请求数据;self.wfile 属性是可写的,用于提供数据给客户端。

0