socketserver- 网络服务器的框架

目录

源代码: Lib / socketserver.py


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

有四种基本的具体服务器类:

class socketserver.TCPServerserver_addressRequestHandlerClassbind_and_activate = True 

它使用Internet TCP协议,该协议在客户端和服务器之间提供连续的数据流。如果bind_and_activate为true,则构造函数会自动尝试调用server_bind()和 server_activate()。其他参数将传递给BaseServer基类。

class socketserver.UDPServerserver_addressRequestHandlerClassbind_and_activate = True 

这使用数据报,这些数据报是在传输过程中可能无序到达或丢失的离散信息包。参数与for相同TCPServer

class socketserver.UnixStreamServerserver_addressRequestHandlerClassbind_and_activate = True 

class socketserver.UnixDatagramServerserver_addressRequestHandlerClassbind_and_activate = True 

这些不经常使用的类类似于TCP和UDP类,但使用Unix域套接字; 它们在非Unix平台上不可用。参数与for相同TCPServer

这四个类同步处理请求; 必须在下一个请求开始之前完成每个请求。如果每个请求需要很长时间才能完成,这是不合适的,因为它需要大量计算,或者因为它返回了客户端处理速度慢的大量数据。解决方案是创建一个单独的进程或线程来处理每个请求; 在 ForkingMixInThreadingMixIn混合型类可用于支持异步行为。

创建服务器需要几个步骤。首先,您必须通过继承BaseRequestHandler类并重写其handle()方法来创建请求处理程序类; 此方法将处理传入的请求。其次,您必须实例化其中一个服务器类,并将其传递给服务器的地址和请求处理程序类。建议在with语句中使用服务器。然后调用服务器对象的 handle_request()or serve_forever()方法来处理一个或多个请求。最后,调用server_close() 关闭套接字(除非你使用了一个with语句)。

从继承ThreadingMixIn线程连接行为继承时,应明确声明线程在突然关闭时的行为方式。在ThreadingMixIn类定义的属性 daemon_threads,这表明服务器是否应该等待线程终止。如果您希望线程自动运行,则应该显式设置标志; 默认值是False,这意味着在所有创建的线程退出之前,Python不会ThreadingMixIn退出。

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

服务器创建注释

继承图中有五个类,其中四个代表四种类型的同步服务器:

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

请注意,UnixDatagramServer源自而UDPServer不是来自 UnixStreamServer- IP和Unix流服务器之间的唯一区别是地址族,这在两个Unix服务器类中都是重复的。

socketserver.ForkingMixIn

socketserver.ThreadingMixIn

可以使用这些混合类创建每种类型服务器的分叉和线程版本。例如,ThreadingUDPServer 创建如下:

class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass

混合类首先出现,因为它覆盖了一个定义的方法 UDPServer。设置各种属性还会更改基础服务器机制的行为。

ForkingMixIn并且下面提到的Forking类仅适用于支持的POSIX平台fork()

socketserver.ForkingMixIn.server_close()等待所有子进程完成,除非socketserver.ForkingMixIn.block_on_close属性为false。

socketserver.ThreadingMixIn.server_close()等待所有非守护程序线程完成,除非socketserver.ThreadingMixIn.block_on_close属性为false。通过设置ThreadingMixIn.daemon_threadsTrue不等待线程完成来使用守护线程 。

改变在3.7版本:socketserver.ForkingMixIn.server_close()socketserver.ThreadingMixIn.server_close()现在一直等待,直到所有子进程和非守护线程完成。添加新的socketserver.ForkingMixIn.block_on_close类属性以选择加入3.7之前的行为。

socketserver.ForkingTCPServer

socketserver.ForkingUDPServer

socketserver.ThreadingTCPServer

socketserver.ThreadingUDPServer

这些类是使用混合类预定义的。

要实现服务,您必须从中派生类BaseRequestHandler 并重新定义其handle()方法。然后,您可以通过将其中一个服务器类与请求处理程序类相结合来运行各种版本的服务。对于数据报或流服务,请求处理程序类必须不同。这可以通过使用处理程序子类StreamRequestHandler或隐藏来隐藏 DatagramRequestHandler

当然,你还是要用脑袋!例如,如果服务包含可由不同请求修改的内存中的状态,则使用分叉服务器是没有意义的,因为子进程中的修改永远不会达到父进程中保留的初始状态并传递给每个子进程。在这种情况下,您可以使用线程服务器,但您可能必须使用锁来保护共享数据的完整性。

另一方面,如果您正在构建一个所有数据都存储在外部的HTTP服务器(例如,在文件系统中),同步类实际上会在处理一个请求时使服务“失聪” - 这可能适用于很长一段时间,如果客户端收到它请求的所有数据的速度很慢。这里的线程或分叉服务器是合适的。

在某些情况下,同步处理部分请求可能是合适的,但要根据请求数据完成分叉子进程的处理。这可以通过使用同步服务器并在请求处理程序类handle()方法中执行显式fork来实现。

在既不支持线程也不支持线程的环境中处理多个同时请求的另一种方法是fork()(或者这些请求太昂贵或不适合服务)是维护一个部分完成的请求的显式表,并selectors用于决定下一个要处理的请求(或是否处理新的传入请求)。这对于流服务尤为重要,因为每个客户端都可能长时间连接(如果不能使用线程或子进程)。请参阅asyncore另一种管理方法。

服务器对象

class socketserver.BaseServerserver_addressRequestHandlerClass 

这是模块中所有Server对象的超类。它定义了下面给出的接口,但没有实现大多数在子类中完成的方法。两个参数被存储在相应的 server_addressRequestHandlerClass属性。

fileno()

返回服务器正在侦听的套接字的整数文件描述符。此函数最常传递给selectors,以允许在同一进程中监视多个服务器。

handle_request()

处理单个请求。这个函数调用下面的方法依次是:get_request()verify_request(),和process_request()。如果handle()处理程序类的用户提供的 方法引发异常,handle_error()则将调用服务器的方法。如果在timeout 几秒钟内没有收到请求,handle_timeout()将被调用handle_request() 并将返回。

serve_foreverpoll_interval = 0.5 

处理请求直到明确shutdown()请求。轮询每个poll_interval秒关闭。忽略该timeout属性。它还调用service_actions(),子类或mixin可以使用它来提供特定于给定服务的操作。例如, ForkingMixIn该类用于service_actions()清理僵尸子进程。

版本3.3中已更改:添加service_actions了对serve_forever方法的调用。

service_actions()

这在serve_forever()循环中调用。子类或mixin类可以重写此方法,以执行特定于给定服务的操作,例如清理操作。

版本3.3中的新功能。

shutdown()

告诉serve_forever()循环停止并等待它。

server_close()

清理服务器。可能会被覆盖。

address_family

服务器套接字所属的协议族。常见的例子是socket.AF_INETsocket.AF_UNIX

RequestHandlerClass

用户提供的请求处理程序类; 为每个请求创建此类的实例。

server_address

服务器正在侦听的地址。地址格式因协议族而异; socket有关详细信息,请参阅模块的文档。对于Internet协议,这是一个包含给出地址的字符串的元组,以及一个整数端口号:。('127.0.0.1', 80)

socket

服务器将侦听传入请求的套接字对象。

服务器类支持以下类变量:

allow_reuse_address

服务器是否允许重用地址。默认为 False,可以在子类中设置以更改策略。

request_queue_size

请求队列的大小。如果处理单个请求需要很长时间,那么在服务器繁忙时到达的任何请求都会被放入队列中,直到request_queue_size请求为止。队列满后,来自客户端的进一步请求将出现“连接被拒绝”错误。默认值通常为5,但子类可以覆盖它。

socket_type

服务器使用的套接字类型; socket.SOCK_STREAM并且 socket.SOCK_DGRAM是两个共同的价值观。

timeout

超时持续时间,以秒为单位,或者None如果不需要超时。如果handle_request()在超时期限内未收到传入请求,handle_timeout()则调用该方法。

有各种服务器方法可以被基本服务器类的子类覆盖,如TCPServer:这些方法对服务器对象的外部用户没有用。

finish_requestrequestclient_address 

实际上通过实例化RequestHandlerClass和调用其handle()方法来处理请求。

get_request()

必须接受来自套接字的请求,并返回包含 用于与客户端通信的套接字对象的2元组以及客户端的地址。

handle_errorrequestclient_address 

如果实例的handle() 方法RequestHandlerClass引发异常,则调用此函数。默认操作是将回溯打印到标准错误并继续处理进一步的请求。

版本3.6中已更改:现在仅调用从Exception 类派生的异常。

handle_timeout()

timeout属性设置为非其他值None且超时时间已过,且未收到任何请求时,将调用此函数。分叉服务器的默认操作是收集已退出的任何子进程的状态,而在线程服务器中,此方法不执行任何操作。

process_requestrequestclient_address 

调用finish_request()创建一个实例 RequestHandlerClass。如果需要,此函数可以创建一个新进程或线程来处理请求; 在ForkingMixIn和 ThreadingMixIn班做到这一点。

server_activate()

由服务器的构造函数调用以激活服务器。TCP服务器的默认行为只是listen() 在服务器的套接字上调用。可能会被覆盖。

server_bind()

由服务器的构造函数调用,以将套接字绑定到所需的地址。可能会被覆盖。

verify_requestrequestclient_address 

必须返回一个布尔值; 如果值为True,则将处理请求,如果是False,则拒绝请求。可以重写此函数以实现服务器的访问控制。默认实现始终返回True

版本3.6中已更改:添加了对上下文管理器协议的支持。退出上下文管理器等同于调用server_close()

请求处理程序对象

socketserver.BaseRequestHandler

这是所有请求处理程序对象的超类。它定义了接口,如下所示。具体的请求处理程序子类必须定义一个新handle()方法,并且可以覆盖任何其他方法。为每个请求创建子类的新实例。

setup()

handle()方法之前调用以执行所需的任何初始化操作。默认实现什么都不做。

handle()

此功能必须完成服务请求所需的所有工作。默认实现什么都不做。有几个实例属性可用; 请求可用self.request; 客户地址为self.client_address; 和服务器实例一样 self.server,以防它需要访问每服务器信息。

self.request数据报或流服务的类型不同。对于流服务,self.request是一个套接字对象; 对于数据报服务,self.request是一对字符串和套接字。

finish()

handle()执行所需的任何清理操作的方法之后调用。默认实现什么都不做。如果setup() 引发异常,则不会调用此函数。

socketserver.StreamRequestHandler

socketserver.DatagramRequestHandler

这些BaseRequestHandler子类覆盖 setup()finish() 方法,并提供self.rfileself.wfile属性。该self.rfileself.wfile属性可以被读取或写入,分别获得请求的数据或者数据返回给客户端。

rfile两个类的属性支持 io.BufferedIOBase可读接口,并 DatagramRequestHandler.wfile支持io.BufferedIOBase可写接口。

版本3.6中更改:StreamRequestHandler.wfile还支持 io.BufferedIOBase可写接口。

示例

socketserver.TCPServer示例

这是服务器端:

import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):
    """
 The request handler class for our server.

 It is instantiated once per connection to the server, and must
 override the handle() method to implement communication to the
 client.
 """

    def handle(self):
        # self.request is the TCP socket connected to the client
        self.data = self.request.recv(1024).strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # just send back the same data, but upper-cased
        self.request.sendall(self.data.upper())

if __name__ == "__main__":
    HOST, PORT = "localhost", 9999

    # Create the server, binding to localhost on port 9999
    with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
        # Activate the server; this will keep running until you
        # interrupt the program with Ctrl-C
        server.serve_forever()

一个替代请求处理程序类,它使用流(类似文件的对象,通过提供标准文件接口简化通信):

class MyTCPHandler(socketserver.StreamRequestHandler):

    def handle(self):
        # self.rfile is a file-like object created by the handler;
        # we can now use e.g. readline() instead of raw recv() calls
        self.data = self.rfile.readline().strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # Likewise, self.wfile is a file-like object used to write back
        # to the client
        self.wfile.write(self.data.upper())

区别在于readline()第二个处理程序中的调用将recv()多次调用 ,直到遇到换行符,而recv()第一个处理程序中的单个调用将返回在一次sendall()调用中从客户端发送的内容。

这是客户端:

import socket
import sys

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

# Create a socket (SOCK_STREAM means a TCP socket)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    # Connect to server and send data
    sock.connect((HOST, PORT))
    sock.sendall(bytes(data + "\n", "utf-8"))

    # Receive data from the server and shut down
    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):
    """
 This class works similar to the TCP handler class, except that
 self.request consists of a pair of data and client socket, and since
 there is no connection the client address must be given explicitly
 when sending data back via 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 is the socket type to use for UDP sockets
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# As you can see, there is no connect() call; UDP has no connections.
# Instead, data is directly sent to the recipient via 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__":
    # Port 0 means to select an arbitrary unused port
    HOST, PORT = "localhost", 0

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

        # Start a thread with the server -- that thread will then start one
        # more thread for each request
        server_thread = threading.Thread(target=server.serve_forever)
        # Exit the server thread when the main thread terminates
        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类被以相同的方式使用的,所不同的是,服务器将生成一个新的过程为每个请求。仅适用于支持的POSIX平台fork()

转载于:https://my.oschina.net/u/3612528/blog/3050937

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值