python标准库之SocketServer

时间:2021-11-21 23:58:01

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

  socketserver内包含了四个基于网络服务的类:

1.TCPServer:使用TCP协议,提供在客户端和服务端进行持续的流式数据通讯。

2.UDPServer:使用UDP数据包协议,这是一种不连续的数据包,在包的传输过程中可能出现数据包的到达顺序不一致或者丢失的情况。

3.UnixStreamServer:继承自TCPServer,使用了Unix domain socket在非Unix平台下无法工作。

4.UnixDatagramServer继承自UDPServer,使用了Unix domain socket在非Unix平台下无法工作。

      所有的Server类都拥有相同的属性和方法,不管它们使用的是何种协议!

  关于Unix domains socket可以参考下面两本书。

W. Richard Stevens 《UNIX Network Programming》

Ralph Daviss 《Win32 Network Programming》


这四个类使用"同步"来处理请求。只有处理完所有请求后,才可以开始处理新的请求!不适合使用在处理单个请求需要花费大量时间的场合。因为需要花费大量的计算时间,或者这因为它会返回大量的数据导致客户端处理速度变得很慢。解决方法是创建单独的进程或者线程处理每一个请求。在类内部的ForkingMixIn和ThreadingMixIn 组合可以支持"异步"的操作。


建立一个服务端需要以下几步:

1.首先,需要建立一个从BaseRequestHandler类继承的子类,并且重写itshandle()方法,这个方法会处理每个进来请求。

2.再者,实例化一个server类的对象,传递给其服务器地址和在第一步继承自BaseRequestHandler类的名称,以便server对象按照重写的方法来处理请求。

3.最后,调用server对象的handle_request()或者serve_forever()方法来处理单个或多个请求

为了使用线程链而继承自ThreadingMixIn的时候,必须明确的声明线程在遇到突发情况下如何关闭。ThreadingMixIn类定义了一个属性"daemon_threads",它表明了server需要等待线程的结束,如果你需要让线程独立于进程,你就应该改明确的设置这个属性,它的默认值是False,意味着Python程序进程不会退出,除非所有使用ThreadingMixIn创建的线程已经都结束了。


服务器创建笔记:


以下是五个类的继承关系图,其中四个代表同步服务器中的类型:

+------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+
注意UnixDatagramServer继承自UDPServer,而不是UnixStreamServer。一个IP地址和一个Unix流服务器的唯一区别就是他们的地址描述族不一样。在Unix服务器类中的话其实它们是近乎重复的。

分布(进程处理)和线程版本的每个类型的服务器可以使用ForkingMixIn和ThreadingMixIn混合类创建。例如一个线程UDP服务器创建类如下:

class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass

类的混合是第一位的,因为它会重写定义在UDPServer类内的方法,而设置属性的同时会改变server的基本运行机制。

为了实现一个服务,首先要从BaseRequestHandler派生一个类并且重写itshandle()方法。然后将其和一个server类绑定就可以运行其服务。这个类必须与datagram或者stream services不同,可以通过隐藏StreamRequestHandle 或者DatagramRequestHandle子类来实现。

当然,很多情况你必须自己思考,比如,一个服务进程中的内存状态会随着每个不同去请求而改变,你就没必要使用分布式服务。因为子进程的变动不会传递到到主进程的初始状态并且传递给其他的主进程。在这种情况下,你可以使用线程式服务,但是你可能需要使用线程锁来保证共享数据的完整性。

另一方面,如果你建立了一个HTTP服务器,所有的数据都是存储在外部,(比如,在文件系统上)如果使用同步server类的话,基本会让服务器表现为聋子一样,什么请求也听不到。如果有一个客户端缓慢接收其对服务器的请求的时候,其他客户端的请求要等待相当长的一段时间。很明显这里使用线程server或者分布server是很合适的。

在某些情况下,处理一个合适的同步请求的一部分,但是其请求处理完成是在子分布服务器上完成的。这可以通过使用一个同步服务器请求处理类handle()方法做一个明确的分布实现

另外一种情况:在一个不支持线程和分布式处理的环境下处理多个并行请求的情况下(或是其成本太高 或者其场合不合适),保持一个显式的部分请求完成表,并且使用select()去判断哪个请求应该在下一步被执行(是否处理一个新传入的请求)。这是一种相当重要的方法可以保持流示服务器上的客户端可以连接很长一段时间(如果线程或者子进程不可使用)。可以参考asyncore用其他的方法管理他们


Server 对象:


class socketserver.BaseServer
#这是一个集合所有Server对象的超类,它只定义了接口方法,具体实现在子类内完成!

BaseServer.fileno() """当服务器已经处于listening状态下此方法返回一个描述socket的整数描述符号。
这个方法通常传递给select.select(),以便允许在同一个进程中多个server对socket的监听。"""

BaseServer.handle_request() """在处理单个请求的情况下
此方法会按顺序调用一下方法:get_request(), verify_request(), process_request()。
如果用户提供的处理请求类中的handle()方法印发了一个异常,
那么Server对象的handle_error()方法将会被调用.
如果在self.timeout设置的时间内没有请求到达将会调用handle_timeout()方法并且handle_request()会返回."""

BaseServer.serve_forever(poll_interval=0.5) """处理请求除非遇到明确的 shutdown() 请求,。
忽略self.timeout设定的时间并且间隔poll_interval秒检测是否需要关闭连接.
通常也会调用service_actions().用来实现子类方法或者是混合类,从而提供特殊的服务行为
比如, ForkingMixIn类使用service_actions()去清除僵尸子进程 。
在3.3版本中的改变:增加service_actions()调用serve_forever()"""

BaseServer.service_actions() """这个方法在serve_forever()循环中被调用.
这个方法可以被子类重写和混合类重写用于执行特别的服务.
比如清理动作。此方法只在3.3中拥有 """

BaseServer.shutdown() #告诉serve_forever()循环停止并且等待其完成

BaseServer.address_family #Server套接字使用的地址协议族,通常来说他们是socket.AF_INET 和 socket.AF_UNIX

BaseServer.RequestHandlerClass #由使用者提供的请求处理类,这个请求处理类的对象将会处理每个请求。

BaseServer.server_address """这个地址是表示server在监听哪个地址,地址格式和地址的协议族相关联;
想要获得更多其信息可以查看 socket模块文档,对于IP协议来说,
这个元组包含一个字符串地址 和一个整数的端口号,比如('127.0.0.1',80)"""

BaseServer.socket # 监听来自客户端请求的socket对象

#---------------所有 server 类支持以下类变量-----------------------:

BaseServer.allow_reuse_address #是否允许server重新使用地址,默认为Flase,可以在子类改变其默认策略!

BaseServer.request_queue_size """请求队列的大小,如果单个请求处理了很长时间,其他的所有请求会放置在队列中,
如果队列已经满了的话,新的客户端请求会得到一个“Connection denied”错误,
默认值为5,此大小可在子类中修改!"""

BaseServer.socket_type #server使用的socket类型通常是:socket.SOCK_STREAM和socket.SOCK_DGRAM。

BaseServer.timeout #超时间隔,单位为秒,如果在超时间隔内handle_request()没有收到新的请求,handle_timeout()方法将会被调用!

#--------------以下server方法是可以被子类重写的,比如TCPServer;对于外部的使用者来说 这些server对象的方法都是没有用的!--------------------

BaseServer.finish_request() #实际处理请求通过实例化RequestHandlerClass并且调用handle()方法

BaseServer.get_request() #必须在socket得到请求后返回一个二元组包含:客户端连接的新socket对象,和客户端的地址。

BaseServer.handle_error(request, client_address) """此方法将在RequestHandlerClass的handle()方法引发了一个异常后调用,默认动作是输出错误异常,
并且继续处理下一步的请求!"""

BaseServer.handle_timeout()"""在一段时间内没有新的请求的话将会执行此方法,在分布式进程server中它会收集所有已经退出的子进程状态。在线程server中,它什么也不做!"""

BaseServer.process_request(request, client_address) """如果需要的话会执行finsh_request()方法,来实例化RequestHandlerClass,
这个方法可以建立一个新的处理线程去处理请求。可以使用ForkingMixIn 和 ThreadingMixIn 类来处理此请求。"""

BaseServer.server_activate()#在server的构造函数内调用此方法,默认的行为是监听server的socket。可以被重写!

BaseServer.server_bind() #在server的构造函数内调用此方法,将socket和网络地址绑定。可以被重写!

BaseServer.verify_request(request, client_address) """返回值为Boolean值,如果为True,这个请求将会被处理,如果为False这个请求将会被拒绝。
重写这个方法可以实现“访问控制”默认的返回值为True"""

RequestHandler 对象!

#-------------此处理请求类必须定义新的handle()方法,并且可以重写以下方法。一个新的对象实例会为每个请求进行处理!---------------

RequestHandler.finish() """此方法将在handle()方法被调用后用来执行必要的清理动作,默认实现是什么也不做,如果setup()方法引发了一个异常,此方法将不会被调用。"""

RequestHandler.handle() """此方法必须为所有请求完成其要求的服务。默认的实现是什么也不做。允许其中有多个server对象。
万一需要访问每个server的信息,那么其请求可以通过self.request获得;客户端的地址self.client_address;server的对象self.server。
self.request的类型在datagram(数据包)和streamservices(流式服务)下是不同的。
在StreamServices下self.request类型是一个socket对象,在datagram下self.request是以字符串和socket的组合。
然而这个方法可以通过重写其子类StreamRequestHandler 或者 DatagramRequestHandler 类的setup() 和 finish()方法来达到隐藏的目的。
并且通过self.rfile和self.wfile属性的读写来达到获取客户端的请求数据或者返回给客户端数据。"""

RequestHandler.setup() #此方法在handle()方法前调用,用来做一些初始化的工作。默认的实现是什么也不做!

示例:TCPServer

服务端:

import socketserver


class MyTCPHandler(socketserver.BaseRequestHandler):
    """
    The RequestHandler 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
    server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)


    # 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())
两种方法不同之处在于:第二种方法将多次调用recv()方法直到遇上一个换行符号。而第一种是只调用一次recv()方法,其返回的数据是在客户端执行sendall()方法中的所有数据
客户端:

import socket
import sys


HOST, PORT = "localhost", 9999
data = "hello Python "


# Create a socket (SOCK_STREAM means a TCP socket)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)


try:
    # 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")
finally:
    sock.close()


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

首先运行服务端,然后运行客户端(在命令行模式下)

将会看到类似的结果

服务端:

127.0.0.1 wrote:
b'hello Python'

客户端:

Sent:     hello Python 
Received: HELLO PYTHON

示例: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
server = socketserver.UDPServer((HOST, PORT), MyUDPHandler)
server.serve_forever()

客户端:

import socket
import sys

HOST, PORT = "localhost", 9999
data = "hello Python"

# 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):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ip, port))
try:
sock.sendall(bytes(message, 'ascii'))
response = str(sock.recv(1024), 'ascii')
print("Received: {}".format(response))
finally:
sock.close()

if __name__ == "__main__":
# Port 0 means to select an arbitrary unused port
HOST, PORT = "localhost", 0

server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
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()

结果应该类似这样:

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
原文地址: http://docs.python.org/3/library/socketserver.html