python官方文档翻译_socketserver模块

socketserver – 网络服务器框架

socketserver模块简化了编写网络服务器的任务

socketserver模块中有四个基本的服务器类:

  • class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)
    这个类使用TCP协议(Transmission Control Protocol传输控制协议),这个协议提供了在服务器和客户端之间的连续的数据流,如果bind_and_activateTrue,构造器会自动的试图调用server_bind()和server_activate()方法。其它的参数会被传递到它的父类BaseServer
  • class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)
    这个类使用UDP协议(User Datagram Protocol用户数据报协议),这种协议发送的是不连续的信息包,这些信息包到达目的地的顺序可能与发送的顺序不同,在传输过程中,信息包也有可能会丢失。它的参数与TCPServer一样.
  • class socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True)
    class socketserver.UnixDatagramServer(server_address, RequestHandlerClass, bind_and_activate=True)
    这两个类不太常用,与上面两个类类似,但使用的是Unix域套接字(原文Unix domain socket不知道翻译的对不),它们在非Unix平台不可用。它们的参数与TCPServer一样。

以上四个类都是同步处理网络请求的,也就是说,处理完毕一个请求之后,才能继续处理下一个请求。在某些情况下,比如处理某个请求需要大量的计算,或者服务器需要返回大量的数据而客户端又处理得比较慢,导致处理一个请求需要很长时间,那么,简单的使用上面的四个类就不太合适了。解决方案是创建单独的进程或线程来处理耗时任务,ForkingMixInThreadingMixIn 这两个混合类(mix-in class)可以用来支持异步行为。

创建一个网络服务器需要几个步骤:

  1. 创建一个请求处理器类(request handler class),这个类继承自BaseRequestHandler类,并且需要重写它的handle() 方法,这个方法用来处理传入的请求
  2. 实例化一个服务器类(上面提到的四个类之一),传入服务器地址请求处理器类作为参数,建议把服务器对象with语句搭配使用
  3. 调用服务器对象handle_request() 或者 serve_forever() 方法,来处理一个或者多个请求
  4. 调用server_close() 方法来关闭socket(当然,如果你使用了with语句,就不需要手动关闭服务器了)

当混合ThreadingMinIn类来添加多线程连接行为的时候,你需要明确的声明当服务器遇到意外关闭的时候,你的线程应该如何应对。ThreadingMixIn类定义了一个deamon_threads属性,这个属性决定了服务器是否等待线程终结。如果你想要你的服务器自动处理等待线程的行为,就需要明确的设置这个属性,这个属性的默认值是False,意味着线程不是守护线程,True表示线程是守护线程。(当所有非守护线程都退出的时候,python也会退出)

对于之前提到的四个基本的服务器类,无论它们使用哪种网络协议,它们对外暴露的方法和属性都是一样的。

创建服务器的注意事项

下面的继承图中有五个类,其中四个类是四种类型的同步服务器类
在这里插入图片描述
注意UnixDataframServer类继承自UDPServer,而不是继承自UnixStreamServer,它们之间的区别仅仅是地址族(adress family)的差别

class socketserver.ForkingMixIn
class socketserver.ThreadingMixIn
多进程和多线程版本的每种类型的服务器类,可以通过混合上面两个类来实现,比如一个多线程的UDP服务器类可以通过下面的方式来创建:

class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass

混合类型(ThreadingMixIn)必须放在继承列表的首位,这样它才能覆盖UDPServer类的方法。通过设置混合类型提供的属性,可以改变底层服务器机制的行为。

下面所提到的ForkingMixIn类和其它的Forking类都只能在支持fork()的POSIX平台下使用。

  • socketserver.ForkingMixIn.server_close()
    这个方法会等待所有的子进程全部完成后关闭服务器,除非socketserver.ForkingMixIn.block_on_close属性被设置为False
  • socketserver.ThreadingMixIn.server_close()
    这个方法等待所有的非守护线程完成后,关闭服务器,除非socketserver.ThreadingMixIn.block_on_close属性被设置为False
    通过设置ThreadingMixIn.daemon_threadsTrue可以将线程设置为守护线程(即服务器不会等待此线程完成后再退出)

Python 3.7版本的新变化:
socketserver.ForkingMixIn.server_close()
socketserver.ThreadingMixIn.server_close()
这两个方法现在会等待所有子进程和非守护线程完成后才会退出
并且添加了一个
socketserver.ForkingMixIn.block_on_close
属性,来选择性的加入3.7版本之前的行为

class socketserver.ForkingTCPServer
class socketserver.ForkingUDPServer
class socketserver.ThreadingTCPServer
class socketserver.ThreadingUDPServer
这些预定义的类就是使用上述的混合类定义的

实现一个服务,你需要子类化BaseRequestHandler并且重写它的handle()方法。通过组合某种类型的服务器类,你就可以运行多种类型的服务。请求处理器类必须与服务器类是不同的类。当然这些细节都可以无视,而直接使用现成的类StreamRequestHandlerDatagramRequestHandler

当然,你仍然需要动动脑筋。比如,多进程服务器的进程之间会各自保留状态信息的副本,对状态信息的修改不会影响到其它进程,其它进程也不会感知到状态信息的修改,这种情况下(需要共享数据),可以考虑使用多线程服务器类,但是,既然涉及了共享数据,你又不得不考虑使用线程锁来解决数据同步的问题。

另一方面,如果你正在构建一个HTTP服务器,服务器的所有数据都是存储在外部的(比如存储在文件系统中),这样的话,当服务器正忙于处理一个请求的时候,这个服务器实际上会变成一个…“聋子”(意思是它听不到其它的请求),尤其是当这个正在被处理得请求需要消耗相当长时间的时候,或者客户端接收数据很慢的时候,在诸如此类的场景下,使用多线程或者多进程的服务器会比较好。

在有些情况下,同步的处理请求的一部分,剩余的部分放到新的进程中去完成,可能会更合适。这可以通过在一个同步服务器请求处理器类handle()方法中明确的创建新进程来实现。

如果一个环境既不支持多线程也不支持多进程(可能支持这些功能的代价太大,或者并不适合此服务器),那么还有另一种方法来处理高并发的请求:维护一个明确的记录未处理完成的请求的表格,使用selectors来决定下一步该处理表格中的哪个请求,或者是否处理一个新请求。这对于那些提供流服务的服务器特别重要,因为它的客户端可能会连接很长时间(如果不支持多线程或多进程的话),asyncore模块提供了另一种处理此类问题的方法。

服务器对象

class socketserver.BaseServer(server_address, RequestHandlerClass)

这是这个模块中所有其它服务器类的父类。它定义了通用接口,大多数底层的但并未实现的方法,这些方法将在子类中实现。两个参数分别存储在server_addressRequestHandlerClass属性中。

  • fileno()
    返回服务器正在监听的套接字的int类型的文件描述符。通常情况下,这个函数会被传递给选择器(selectors),来允许监听同一个进程中的多个服务器。

  • 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秒轮询一次。
    忽略timeout属性。
    它也会调用service_actions(),这个方法被子类或混合类用来添加一些对于特定服务的专属操作。比如,ForkingMixIn类用service_actions()来清理僵尸子进程
    Python 3.3 的变化
    serve_forever方法添加service_actions方法

  • service_actions()
    这个方法在serve_forever()方法中被调用。
    这个方法可以被子类或混合类重写,来为特定的服务提供专属操作,比如清理操作
    Python 3.3 版本新添加的

  • shutdown()
    serve_forever()循环退出,并等待它完成退出
    shutdown()必须在和serve_forever()不同的线程中调用,否则会造成死锁

  • server_close()
    清理服务器,可以重写。

  • address_family
    此属性表示服务器的套接字所属的协议族,常见的有:
    socket.AF_INET
    socket.AF_UNIX

  • RequestHandlerClass
    用户提供的请求处理器类,对于每个请求,都会创建一个这个类的实例。

  • server_address
    服务器所监听的地址。
    不同的协议族有不同的地址格式,查看socket模块的文档来获取更详细的信息
    对于IP协议,地址是一个拥有两个元素的元组:一个字符串类型的IP地址和一个整数类型的端口号,例如(‘127.0.0.1’,80)

  • socket
    服务器所监听的传入请求的套接字

前述的几种服务器类支持以下一些类变量:

  • allow_reuse_address
    服务器是否允许重用一个地址。这个变量默认为False,并且可以在子类中设置,来改变策略
  • request_queue_size
    请求队列的大小。如果服务器处理某个请求需要花费大量的时间,那么当服务器忙碌时传入的请求会被放入一个队列,这个队列最多容纳request_queue_size个请求。一旦队列装满,后续的请求会得到一个“拒绝连接”(Connection denied)的错误。变量的默认值是5,但可以在子类中重写。
  • socket_type
    服务器所使用的套接字的类型,通常的取值是:
    socket.SOCK_STREAM
    socket.SOCK_DGRAM
  • timeout
    超时时间,以秒为单位,设置为None表示不需要超时时间。
    如果handle_request()方法在timeout秒之内没有收到任何传入的请求,则handle_timeout()方法将被调用。

还有一些服务器方法,可以被BaseServer的子类(比如TCPServer)所重写。这些方法对于服务器对象的外部用户来说,并没有什么卵用。

  • finish_request(request, client_address)
    这是实际处理请求的地方,负责实例化RequestHandlerClass并且调用这个实例的handle()方法
  • get_request()
    必须接受一个来自套接字的请求,并且返回一个包含了新套接字对象和客户端地址的二元组。
  • handle_error(request, client_address)
    RequestHandlerClass实例的handle()方法抛出异常(Exception)时,这个方法会被调用。默认的行为是向标准错误流打印追踪栈信息,并且继续处理后续的请求。
    Python 3.6 的变化:
    只在抛出由Exception派生的异常时,才会调用此方法。
  • handle_timeout()
    timeout属性设置了某个非None的值,并且超过timeout时间后仍然没有收到请求,则这个方法会被调用。在多进程的分支服务器上,默认的行为是收集所有已经退出的子进程的状态,而在多线程服务器上,这个方法什么都不做。
  • process_request(request,client_address)
    调用finish_request()方法来实例化RequestHandlerClass。这个方法也可以被子类重写,创建新的进程或线程来处理请求。ForkingMixInThreadingMixIn类已经做好了这些事情。
  • server_activate()
    由服务器的构造器调用,来激活服务器。在TCP服务器上,这个方法的默认行为就是调用套接字对象的listen()方法。可以被子类重写。
  • server_bind()
    由服务器的构造器调用,将套接字与指定的地址绑定起来,可以被子类重写。
  • verify_request(request, client_address)
    必须返回一个布尔值;返回True,则请求会被处理,返回False,则请求会被拒绝。这个方法可以被重写,用来实现服务器的访问控制。默认总是返回True
    Python 3.6 的变化:
    添加了对上下文管理器协议的支持(即with语句块)。
    退出上下文管理器等同于调用server_close()方法。

请求处理器对象

class socketserver.BaseRequestHandler

这是所有请求处理器类的超类。它定义了底层的接口。
一个具体的请求处理器子类必须实现新的handle()方法,并重写其它一些方法。
对于每个请求,都会创建一个新的子类的实例

  • setup()
    在handle()方法之前调用,来执行一些必要的初始化动作。
    默认实现什么都不做。
  • handle()
    服务一个请求所需要的所有工作都由这个函数来完成。
    默认实现什么都不做。
    它(应该指的是请求处理器对象)有几个可用的实例属性,能够用来访问服务器信息:
    self.request 请求
    self.client_address 客户端地址
    self.server 服务器对象实例
    对于数据报(datagram)和流(stream)服务来说,它们所对应的self.request的类型是不同的。
    对于流服务,self.request 是一个套接字对象
    对于数据报服务,self.request 是一个包含字符串和套接字对象的二元组。
  • finish()
    handle()之后调用,执行一些必要的清理工作。
    默认实现什么都不做。
    如果setup()抛出异常,则这个函数不会被调用。

class socketserver.StreamRequestHandler

class socketserver.DatagramRequestHandler

这些BaseRequestHandler的子类重写了setup() 和 finish() 方法,并且提供self.rfileself.wfile 属性。
self.rfileself.wfile 属性可以分别读取数据或者将数据返回给客户端。
这两个类的 rfile 属性支持 io.BufferedIOBase 可读接口。
这两个类的 wfile 属性支持 io.BufferedIOBase 可写接口。
Python 3.6 的变化:
StreamRequestHandler.wfile 也支持 io.BufferedIOBase 可写接口。

示例

socketserver.TCPServer 示例

这是服务器端:

import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):
    """
    我们的服务器的请求处理器.
	对于每一个连接,都会实例化一次请求处理器,
	而且它必须重写handle()方法来实现与客户端的交流.
    """

    def handle(self):
        # self.request 是连接到客户端的TCP类型的套接字
        self.data = self.request.recv(1024).strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # 这里仅仅回传了相同的内容,只不过转换成了大写
        self.request.sendall(self.data.upper())

if __name__ == "__main__":
    HOST, PORT = "localhost", 9999
	# 创建服务器,绑定到 localhost 的 9999 端口
    with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
    	# 启动服务器,这回让服务器一直运行,指导你用Ctrl + C 来终止程序
        server.serve_forever()

一个利用流的请求处理器类的替代品(这是一个类文件对象,通过与标准文件对象一样的接口来与客户端进行简单的交流)

class MyTCPHandler(socketserver.StreamRequestHandler):

    def handle(self):
        # self.rfile 是请求处理器所创建的类文件对象;
        # 我们可以使用 readline() 来替代原来的 recv() 调用
        self.data = self.rfile.readline().strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # 类似的, self.wfile 是一个用来将数据写回客户端的类文件对象
        self.wfile.write(self.data.upper())

它们之间的不同点在于:第二个处理器的readline()方法会多次调用recv()方法,直到遇到一个换行符;然而,第一个处理器的recv()方法会返回由客户端的sendall()方法发送的全部内容。

这是客户端:

import socket
import sys

HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])

# 创建套接字 (SOCK_STREAM 代表 TCP 套接字)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    # 连接服务器并发送数据
    sock.connect((HOST, PORT))
    sock.sendall(bytes(data + "\n", "utf-8"))

    # 从服务器接收数据,然后关闭
    received = str(sock.recv(1024), "utf-8")

print("Sent:     {}".format(data))
print("Received: {}".format(received))

这个示例的输出应该看起来像这样:
服务器:

$ python TCPServer.py
127.0.0.1 wrote:
b'hello world with TCP'
127.0.0.1 wrote:
b'python is nice'

客户端:

$ python TCPClient.py hello world with TCP
Sent:     hello world with TCP
Received: HELLO WORLD WITH TCP
$ python TCPClient.py python is nice
Sent:     python is nice
Received: PYTHON IS NICE

socketserver.UDPServer 示例

这是服务器端:

import socketserver

class MyUDPHandler(socketserver.BaseRequestHandler):
    """
    这个类工作起来跟TCP处理器是类似的, 
    只不过 self.request 是一个包含数据和
    客户端套接字的二元组
    因为这里没有连接, 所以在通过sendto()发送数据的时候,
    需要明确指定目标的地址
    """

    def handle(self):
        data = self.request[0].strip()
        socket = self.request[1]
        print("{} wrote:".format(self.client_address[0]))
        print(data)
        socket.sendto(data.upper(), self.client_address)

if __name__ == "__main__":
    HOST, PORT = "localhost", 9999
    with socketserver.UDPServer((HOST, PORT), MyUDPHandler) as server:
        server.serve_forever()

这是客户端:

import socket
import sys

HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])

# SOCK_DGRAM 代表使用的是 UDP 类型的套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 正如你看到的, 这里没有 connect() 调用; UDP 没有连接.
# 作为替代, 数据直接通过 sendto() 发送给接收者
sock.sendto(bytes(data + "\n", "utf-8"), (HOST, PORT))
received = str(sock.recv(1024), "utf-8")

print("Sent:     {}".format(data))
print("Received: {}".format(received))

这个示例的输出与之前 TCP 的版本一样

异步混合类示例

要构建一个异步处理器, 可以使用 ThreadingMixIn 和 ForkingMixIn 类.
一个 ThreadingMixIn 类的示例:

import socket
import threading
import socketserver

class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):

    def handle(self):
        data = str(self.request.recv(1024), 'ascii')
        cur_thread = threading.current_thread()
        response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')
        self.request.sendall(response)

class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    pass

def client(ip, port, message):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.connect((ip, port))
        sock.sendall(bytes(message, 'ascii'))
        response = str(sock.recv(1024), 'ascii')
        print("Received: {}".format(response))

if __name__ == "__main__":
    # 端口 0 意思是选择任意一个未使用的端口
    HOST, PORT = "localhost", 0

    server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
    with server:
        ip, port = server.server_address

        # 为服务器开启一个线程, 这个线程将会为每个请求再开启一个线程
        server_thread = threading.Thread(target=server.serve_forever)
        # 当主线程终止的时候, 退出服务器
        server_thread.daemon = True
        server_thread.start()
        print("Server loop running in thread:", server_thread.name)

        client(ip, port, "Hello World 1")
        client(ip, port, "Hello World 2")
        client(ip, port, "Hello World 3")

        server.shutdown()

示例的输出应该看起来像这样:

$ python ThreadedTCPServer.py
Server loop running in thread: Thread-1
Received: Thread-2: Hello World 1
Received: Thread-3: Hello World 2
Received: Thread-4: Hello World 3

ForkingMixIn类的使用方法类似,唯一的区别是服务器会为每一个请求派生出来一个进程。这仅在支持fork() 的 POSIX 平台下可用。

关于此翻译

翻译这篇文档的初衷是,翻译者是个英语渣,然而又在学习Python的这个模块,看英文文档实在是太慢了,也不方便回过头来复习,所以索性在学习的过程中顺便把中文写下来,方便以后回来看
这篇文档是基于Python 3.9.5 官方文档翻译的
原文地址: socketserver
刚才也说了,翻译者是个英语渣,所以翻译过程中难免会有错误,对于模块功能的理解也可能不全面,还请各路大神指正,先行谢过,膜拜!!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值