• 快速使用python搭建一个简易服务器 --- socketServer


    官方提供了socketserver包去方便我们快速的搭建一个服务器框架。

    server类

    socketserver包提供5个Server类,这些单独使用这些Server类都只能完成同步的操作,他是一个单线程的,不能同时处理各个客户端的请求,只能按照顺序依次处理。

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

    两个Mixin类

    +--------------+        +----------------+
    | ForkingMixIn |        | ThreadingMixIn |
    +--------------+        +----------------+

    各自实现了多进程和多线程的功能(ForkingMixIn在Windows不支持)

    于是将这些同步类和Mixin类组合就实现了异步服务类的效果。

    class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
    class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
    
    class ForkingUDPServer(ForkingMixIn, UDPServer): pass 
    class ForkingTCPServer(ForkingMixIn, TCPServer): pass

    基本使用

    由于server需要同时处理来自多个客户端的请求,需要提供异步的支持,所以通常使用上面的异步类创建服务器。在Windows系统中没有提供os.fork()接口,Windows无法使用多进程的ForkingUDPServer和ForkingTCPServer,只能使用ThreadingTCPServer或者ThreadingUDPServer;而Linux和Unix多线程和多进程版本都可以使用。

    服务器主要负责接受客户端的连接请求,当一个新的客户端请求到来后,将分配一个新的线程去处理这个请求(异步服务器ThreadingTCPServer),而与客户端信息的交互则交给了专门的请求处理类(RequestHandlerClass)处理。

    import socketserver
    
    # 创建一个基于TCP的server对象,并使用BaseRequestHandler处理客户端发送的消息
    server = socketserver.ThreadingTCPServer(("127.0.0.1", 8000), BaseRequestHandler)  
    
    server.serve_forever()  # 启动服务器,

    只需要上面两行代码就可以创建开启一个服务,运行上面代码后常看本机8000端口,发现有程序正在监听。

    C:Usersuser>netstat -anp tcp | findstr 8000
    TCP    127.0.0.1:8000         0.0.0.0:0              LISTENING
    ThreadingTCPServer可以对我们的请求进行接受,但是并不会进行处理请求,处理请求的类是上面指定BaseRequestHandler类,该类可以定义handle方法来处理接受的请求。

    BaseRequestHandler的源码
    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

    server = socketserver.ThreadingTCPServer(("127.0.0.1", 8000), BaseRequestHandler)中,BaseRequestHandler将作为参数绑定到服务器的实例上,服务器启动后,每当有一个新的客户端接接入服务器,将会实例化一个请求处理对象,并传入三个参数,request(连接客户端的socket)、client_address(远程客户端的地址)、server(服务器对象),执行init方法,将这三个参数保存到对应属性上。这个请求处理对象便可以与客户端交互了。

    简单示例

    import socketserver
    import threading 
    
    class MyRequestHandler(socketserver.BaseRequestHandler):
        """ BaseRequestHandler的实例化方法中,获得了三个属性
        self.request = request   # 该线程中与客户端交互的 socket 对象。
        self.client_address      # 该线程处理的客户端地址
        self.server = server     # 服务器对象
        """
    
        def handle(self):
            while True:
                msg = self.request.recv()   # 接受客户端的数据
                if msg == b"quit" or msg == "":  # 退出
                    break
    
                print(msg.decode())
                self.request.send(msg)  # 将消息发送回客户端
    
        def finish(self):
            self.request.close()       # 关闭套接字
    
    if __name__ == "__main__":
        # 创建一个基于TCP的server对象,并使用BaseRequestHandler处理客户端发送的消息
        server = socketserver.ThreadingTCPServer(("127.0.0.1", 8000), MyRequestHandler)
    
        server.serve_forever()   # 启动服务器

    我们创建了一个ThreadingTCPServer服务器,然后在传入的处理类MyRequestHandler,并在handle方法中提供与客户端消息交互的业务逻辑,此处只是将客户端的消息返回客户端。最后我们在finish方法中关闭资源,finish方法使用了finally机制,保证了这些代码一定会执行。

    上一篇使用socket实现了一个群聊服务器,这个里使用socketServer将更加方便的实现

    class MyRequestHandle(BaseRequestHandler):
        clients = {}  # 在类属性中记录所有与客户端连接socket。
        lock = threading.Lock()  # 互斥锁,各个线程共用
    
        def setup(self):  # 新的用户连接时,预处理,将这个新的连接加入到clients中,考虑线程安全,需要加锁
            with self.lock:
                self.clients[self.client_address] = self.request
    
        def handle(self):  # 处理客户端的请求主逻辑
            while True:
                data = self.request.recv(1024).strip()   # 接受数据
    
                if data == b"quit" or data == b"":  # 客户端退出
                    with self.lock:
                        self.server.clients.pop(self.client_address)
                        self.request.close()
                        break
    
                print("{}-{}: {}".format(*self.client_address, data.decode()))
    
                with self.lock:
                    for _, c in self.server.clients.items():  # 群发
                        c.send(data)
    
        def finish(self):
            with server.lock:
                for _, c in server.clients.items():
                    c.close()
            server.server_close()def main():
        server = ThreadingTCPServer(("127.0.0.1", 8000), MyRequestHandle)
        # 将创建的所有线程设置为daemon线程,这样控台主程序退出时,这个服务器的所有线程将会被结束
        server.daemon_threads = True 
    
    if __name__ == "__main__":
        main()

    上面requestHandlerclass中的handle方法和finish方式对应了上一篇中TCP服务器的recv方法和stop方法,他们处理请求的逻辑是相同的。只是上面使用了socketserver的代码变少了,处理的逻辑也变少了,TCPserver帮我们完成了大量的工作,这利于软件的快速开发。

    内置的两个RequestHandlerClass

    StreamHandlerRequest

    StreamHandlerRequest顾名思义是一种流式的求情处理类,对应TCP协议的面向字节流的传输形式。我们从源代码分析。(去除了一些次要代码)

    class StreamRequestHandler(BaseRequestHandler):
        rbufsize = -1  # 读缓存
        wbufsize = 0   # 写缓存
        timeout = None # 超时时间
        # IP/TCP拥塞控制的Nagle算法算法。
        disable_nagle_algorithm = False
    
        def setup(self): # 实现了setup,
            self.connection = self.request
            if self.timeout is not None:
                self.connection.settimeout(self.timeout)
            if self.disable_nagle_algorithm:
                self.connection.setsockopt(socket.IPPROTO_TCP,
                                           socket.TCP_NODELAY, True)
            
            # 使用 makefile方法获得了一个只读文件对象 rfile
            self.rfile = self.connection.makefile('rb', self.rbufsize)
            
            # 获得一个只写的文件对象 wfile
            if self.wbufsize == 0:
                self.wfile = _SocketWriter(self.connection)
            else:
                self.wfile = self.connection.makefile('wb', self.wbufsize)
    
        def finish(self):  # 负责将这个 wfile 和 rfile方法关闭。
            if not self.wfile.closed: 
                try:
                    self.wfile.flush()
                except socket.error:
                    pass
            self.wfile.close()
            self.rfile.close()

    使用StreamRequestHandler方法可以将这个socket包装成一个类文件对象,方便我们使用一套文件对象的方法处理这个socket,它没有实现handle方法,我仍然需要我们实现。我们可以这样使用它

    class MyHandle(StreamRequestHandler):
        # 如果需要使用setup和finish方法,需要调用父类方法,否则该方法将会被覆盖。
        def setup(self):
            super().setup()
            # 添加自己的需求
    
        def handle(self):
            # 这里我们可以使用wfile和rfile来处理socket消息了,例如之前使用self.request.recv()方法等同于self.rfile.read()
            # 而 self.wfile.write 等同于 self.request.send(),在handle方法中完成业务逻辑即可
    
        def finish(self):
            super().finish()
    
    server = ThreadingTCPServer("127.0.0.1", MyHandle)
    server.serve_forever()

    StreamRequestHandler主要定义了两个新的 wfile对象和rfile对象,来分别对这个socket进行读写操作,当我们业务需要时,比如需要使用文件接口方法时,选择继承于StreamRequestHandler构建我们自己处理请求类来完成业务逻辑将会更加的方便。

    DatagramRequestHandler

    DatagramRequestHandler字面意思是数据报请求处理,也就是基于UDPServer的服务器才能使用该请求处理类

    class DatagramRequestHandler(BaseRequestHandler):
    
        def setup(self):
            from io import BytesIO
            # udp的self.request包含两部分(data,socket)它来自于
            # data, client_addr = self.socket.recvfrom(self.max_packet_size)
            #     return (data, self.socket), client_addr
            # (data, self.socket)就是这个self.request,在这里将其解构,data为recvfrom接收的数据
            self.packet, self.socket = self.request
            
            # 该数据包封装为 BytesIO,同样为一个类文件对象。
            self.rfile = BytesIO(self.packet)
            self.wfile = BytesIO()
    
        def finish(self):
            self.socket.sendto(self.wfile.getvalue(), self.client_address)

    从源码可以看出,DatagramRequestHandler将数据包封装为一个rfile,并实例化一个ByteIO对象用于写入数据,写入的数据可以通过self.socket这个套接字发送。这样可以使用rfile和wfile这两个类文件对象的read或者write接口来进行一些IO方面的操作。

  • 相关阅读:
    Spring注解运行时抛出null
    关于apache服务器加载so的报错
    apache apr的编译和引用
    FreeSWITCH在会议室中持续播放音频文件
    64位FreeSWITCH编译安装(版本1.4.20)
    Spring整合Tiles
    eclipse启动报错eclipse failed to create the java virutal machine
    菜鸟新闻2--设置沉浸式状态栏的三种方法
    OkHttp3源码详解(三) 拦截器
    Android N(API level 24.)废弃了Html.fromHtml(String)
  • 原文地址:https://www.cnblogs.com/k5210202/p/13071577.html
Copyright © 2020-2023  润新知