Python中的SocketServer模块

Python中的SocketServer模块

socket编程过于底层,编程虽然有套路,但是想要写出健壮的代码还是比较困难的,所以很多语言都对socket底层 API进行封装,Python的封装就是——socketserver模块。它是网络服务编程框架,便于企业级快速开发。

  1. 类的继承关系

    +------------+
    | BaseServer |
    +------------+
        |
        v
    +-----------+        +------------------+
    | TCPServer |------->| UnixStreamServer |
    +-----------+        +------------------+
        |
        v
    +-----------+        +--------------------+
    | UDPServer |------->| UnixDatagramServer |
    +-----------+        +--------------------+
    
  2. SocketServer简化了网络服务器的编写

    • 它有4个同步类:
      1. TCPServer
      2. UDPServer
      3. UnixStreamServer
      4. UnixDatagramServer
    • 两个Mixin类,用来支持异步。
      • 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类中的特有属性:
    1. daemon_threads=False #默认值是False表示创建的线程都不是daemon线程,改为True表示创建的所有线程都是daemon线程
    2. block_on_close=False #默认值为Fasle,如果为True,可以设置为守护线程3.7版本可以用

编程接口

  1. 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)
    
  2. 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类

  1. 是和用户连接的用户请求处理类的基类,
  2. BaseRequestHandler(request,client_address,server) #构造函数
    • request #是和客户端的连接的socket对象
    • client_address #是客户端地址
    • server #是TCPServer实例本身
  3. 服务端Server实例接收用户请求后,最后会实例化这个类。它被初始化时,送入3个构造参数:request, client_address, server自身 以后就可以在BaseRequestHandler类的实例上使用以下属性:
    • self.request是和客户端的连接的socket对象
    • self.server是TCPServer实例本身
    • self.client_address是客户端地址
  4. 这个类在初始化的时候,它会依次调用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() #永久循环执行,可以接受多个客户端连接

socketserver_001

  • 每个不同的连接上的请求过来后,生成这个连接的socket对象即self.request,客户端地址是self.client_address。
  • 将ThreadingTCPServer换成TCPServer,当每个客户端连接进来就会创建一个新的线程。
  • ThreadingTCPServer是异步的,可以同时处理多个连接。
  • TCPServer是同步的,一个连接处理完了,即一个连接的handle方法执行完了,才能处理另一个连接,且只有主线程。

总结

  • 创建服务器需要几个步骤:
    1. 从BaseRequestHandler类派生出子类,并覆盖其handle()方法来创建请求处理程序类,此方法将处理传入请求
    2. 实例化一个服务器类,传参服务器的地址和请求处理类
    3. 调用服务器实例的handle_request()或serve_forever()方法
    4. 调用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())

  • 总结
    1. 为每一个连接提供RequestHandlerClass类实例,依次调用setup、handle、finish方法,且使用了try…finally结构 保证finish方法一定能被调用。这些方法依次执行完成,如果想维持这个连接和客户端通信,就需要在handle函数 中使用循环。
    2. socketserver模块提供的不同的类,但是编程接口是一样的,即使是多进程、多线程的类也是一样,大大减少了编 程的难度。
    3. 将socket编程简化,只需要程序员关注数据处理本身,实现Handler类就行了。这种风格在Python十分常见。

猜你喜欢

转载自blog.csdn.net/u013008795/article/details/92642840