• Python-SocketServer模块


    1、SocketServer:

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

    2、类的继承关系:

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

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

        它有4个同步类:

            TCPServer,UDPServer,UnixStreamServer,UnixDatagramServer。
        2个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是创建多线程

    3、编程接口:

      socketserver.BaseServer(server_address, RequestHandlerClass)
      需要提供服务器绑定的地址信息,和用于处理请求的RequestHandlerClass类。
      RequestHandlerClass类必须是BaseRequestHandler类的子类,在BaseServer中代码如下:

     1 # BaseServer代码 ----》
     2 class BaseServer:
     3   def __init__(self, server_address, RequestHandlerClass):
     4     """Constructor. May be extended, do not override."""
     5     self.server_address = server_address
     6     self.RequestHandlerClass = RequestHandlerClass
     7     self.__is_shut_down = threading.Event()
     8     self.__shutdown_request = False
     9   def finish_request(self, request, client_address): # 处理请求的方法
    10     """Finish one request by instantiating RequestHandlerClass."""
    11     self.RequestHandlerClass(request, client_address, self) # RequestHandlerClass构造

      BaseRequestHandler类
        它是和用户连接的用户请求处理类的基类,定义为BaseRequestHandler(request, client_address, server)

      服务端Server实例接收用户请求后,最后会实例化这个类。
      它被初始化时,送入3个构造参数:request, client_address, server自身
      以后就可以在BaseRequestHandler类的实例上使用以下属性:
      self.request是和客户端的连接的socket对象
      self.server是TCPServer实例本身
      self.client_address是客户端地址‘

      这个类在初始化的时候,它会依次调用3个方法。子类可以覆盖这些方法。

     1 # BaseRequestHandler要子类覆盖的方法
     2 class BaseRequestHandler:
     3   def __init__(self, request, client_address, server):
     4     self.request = request
     5     self.client_address = client_address
     6     self.server = server
     7     self.setup()
     8   try:
     9     self.handle()
    10   finally:
    11     self.finish()
    12   def setup(self): # 每一个连接初始化
    13     pass
    14   def handle(self): # 每一次请求处理
    15     pass
    16   def finish(self): # 每一个连接清理
    17     pass

      测试:

     1 import threading
     2 import socketserver
     3 
     4 class MyHandler(socketserver.BaseRequestHandler):
     5     def handle(self):
     6         # super().handle() # 可以不调用,父类handle什么都没有做,一般习惯性的调用一下父类的
     7         print('-'*30)
     8         print(self.__dict__)
     9         print(self.server) # 服务
    10         print(self.request) # 服务端负责客户端连接请求的socket对象
    11         print(self.client_address) # 客户端地址
    12         print(self.__dict__)
    13         print(self.server.__dict__) # 能看到负责accept的socket
    14 
    15         print(threading.enumerate())
    16         print(threading.current_thread())
    17         print('-'*30)
    18 
    19 addr = ('127.0.0.1', 9999)
    20 server = socketserver.ThreadingTCPServer(addr, MyHandler) # 生成一个多线程的server
    21 print(server.__class__.__name__)
    22 
    23 
    24 server.serve_forever() # 永久开启
    测试

      结果:

     1 D:python3.7python.exe E:/code_pycharm/复习/t4.py
     2 ThreadingTCPServer
     3 ------------------------------
     4 {'request': <socket.socket fd=244, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 63665)>, 'client_address': ('127.0.0.1', 63665), 'server': <socketserver.ThreadingTCPServer object at 0x000000000224C438>}
     5 <socketserver.ThreadingTCPServer object at 0x000000000224C438>
     6 <socket.socket fd=244, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 63665)>
     7 ('127.0.0.1', 63665)
     8 {'request': <socket.socket fd=244, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 63665)>, 'client_address': ('127.0.0.1', 63665), 'server': <socketserver.ThreadingTCPServer object at 0x000000000224C438>}
     9 {'server_address': ('127.0.0.1', 9999), 'RequestHandlerClass': <class '__main__.MyHandler'>, '_BaseServer__is_shut_down': <threading.Event object at 0x000000000224C860>, '_BaseServer__shutdown_request': False, 'socket': <socket.socket fd=228, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999)>, '_threads': [<Thread(Thread-1, started 8732)>]}
    10 [<_MainThread(MainThread, started 8048)>, <Thread(Thread-1, started 8732)>]
    11 <Thread(Thread-1, started 8732)>
    12 ------------------------------
    13 
    14 Process finished with exit code 1
    View Code

      测试结果说明:

        handler方法相当于socket的recv方法

        生成的Baseserver 对象(ThreadingTCPServer),就包含了accept,可以通过server.__dict_-可以看到accept的socket

        每个不同的连接上的请求过来后,

          生成这个连接的socket对象,即self.request,

          客户端地址是,self.client_address

        而且没有请求进来,阻塞在server_forever()

        但是,上面的代码,连接后就客户端 立即断开了

      测试:客户端 和 服务器端持久连接

     1 import threading
     2 import socketserver
     3 
     4 class MyHandler(socketserver.BaseRequestHandler):
     5     def handle(self):
     6         # super().handle() # 可以不调用,父类handle什么都没有做,一般习惯性的调用一下父类的
     7         print('-'*30)
     8         print(self.server) # 服务
     9 
    10         # print('=================')
    11         print(self.request) # 服务端负责客户端连接请求的socket对象
    12         print(self.client_address) # 客户端地址
    13         print(self.__dict__)
    14         print(self.server.__dict__) # 能看到负责accept的socket
    15 
    16         print(threading.enumerate())
    17         print(threading.current_thread())
    18         print('-'*30)
    19 
    20         for i in range(3):
    21             data = self.request.recv(1024)
    22             print(data)
    23             print(' ===== end ==== ')
    24 
    25 addr = ('127.0.0.1', 9999)
    26 server = socketserver.ThreadingTCPServer(addr, MyHandler) # 生成一个多线程的server
    27 
    28 server.serve_forever() # 永久开启
    29 # print(server.__dict__)
    添加循环实现

      结果:每次都阻塞在recv,当循环结束,客户端 断开,服务器端一直没有断开

     1 D:python3.7python.exe E:/code_pycharm/复习/t4.py
     2 ------------------------------
     3 <socketserver.ThreadingTCPServer object at 0x000000000294C438>
     4 <socket.socket fd=244, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 64303)>
     5 ('127.0.0.1', 64303)
     6 {'request': <socket.socket fd=244, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 64303)>, 'client_address': ('127.0.0.1', 64303), 'server': <socketserver.ThreadingTCPServer object at 0x000000000294C438>}
     7 {'server_address': ('127.0.0.1', 9999), 'RequestHandlerClass': <class '__main__.MyHandler'>, '_BaseServer__is_shut_down': <threading.Event object at 0x000000000294C860>, '_BaseServer__shutdown_request': False, 'socket': <socket.socket fd=228, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999)>, '_threads': [<Thread(Thread-1, started 4756)>]}
     8 [<_MainThread(MainThread, started 6112)>, <Thread(Thread-1, started 4756)>]
     9 <Thread(Thread-1, started 4756)>
    10 ------------------------------
    11 b'd'
    12  ===== end ==== 
    13 b'd'
    14  ===== end ==== 
    15 b'd'
    16  ===== end ==== 
    3次之后断开连接

      将ThreadingTCPServer 换成TCPServer, 同时连接2个客户端观察效果发现,是串行的,一个结束之后,才能另外一个处理

      并且每次都是阻塞到 recv 方法处

      ThreadingTCPServer 是异步的,可以同时处理多个请求。

      TCPServer 是同步的,一个连接处理完了,即一个连接的 handle方法 执行完了,才能处理另一个连接,且只有主线程。

      总结:

        创建服务器需要的几个步骤:

      1. 从BaseRequestHandler类派生出子类,并覆盖器handler() 方法来创建请求处理程序类,此方法将处理传入请求
      2. 实例化一个服务器类,传参服务器的地址和请求处理类
      3. 调用服务器实例的handle_request(),或server_forever() 方法
      4. 调用server_close()关闭套接字

    4、测试实例

      4.1、显示EchoServer

        顾名思义,回显消息  

     1 import threading
     2 from  socketserver import ThreadingTCPServer, TCPServer, BaseRequestHandler
     3 import  sys
     4 
     5 class EchoHandler(BaseRequestHandler):
     6     def setup(self):
     7         super().setup()
     8         self.event = threading.Event() # 初始化工作
     9 
    10     def finish(self):
    11         super().finish()
    12         self.event.set() # 清理工作
    13 
    14     def handle(self):
    15         super().handle()
    16 
    17         try:
    18             while not self.event.is_set():
    19                 data = self.request.recv(1024).decode()
    20                 msg = '{}{}'.format(self.client_address, data).encode()
    21                 self.request.send(msg)
    22         except Exception as e:
    23             print(e)
    24 
    25         finally:
    26             print('=== end ====')
    27 
    28 
    29 server = ThreadingTCPServer(('127.0.0.1', 9999), EchoHandler)
    30 
    31 server_thread = threading.Thread(target=server.serve_forever, name='EchoServer', daemon=True)
    32 server_thread.start()
    33 
    34 
    35 try:
    36     while True:
    37         cmd = input('>>')
    38         if cmd.strip() == 'quit':
    39             server.shutdown()
    40             break
    41         print(threading.enumerate())
    42 except Exception as e:
    43     print(e)
    44 except KeyboardInterrupt:
    45     pass
    46 finally:
    47     print('exit')
    48     sys.exit(0)
    View Code

       4.2、实现群聊

     1 import threading
     2 from socketserver import ThreadingTCPServer, BaseRequestHandler
     3 import sys
     4 import logging
     5 
     6 FOMAT = '%(asctime)s %(thread)s %(threadName)s %(message)s'
     7 logging.basicConfig(level=logging.INFO, format=FOMAT)
     8 
     9 class ChatServerHanlder(BaseRequestHandler):
    10     clients = {}
    11 
    12 
    13     def setup(self):
    14         super().setup()
    15         self.event = threading.Event()
    16         self.clients[self.request] = self.client_address
    17 
    18     def finish(self):
    19         super().finish()
    20         self.clients.pop(self.request)
    21         self.event.set()
    22         print(self.clients)
    23 
    24 
    25 
    26     def handle(self):
    27         super().handle()
    28 
    29         while not self.event.is_set():
    30             data = self.request.recv(1024)
    31 
    32             if data.strip() == b'quit' or data == b'':
    33                 break
    34 
    35             msg = ' your msg is {} '.format(data.decode()).encode()
    36 
    37             for s in self.clients:
    38                 s.send(msg)
    39 
    40 
    41 laddr = ('127.0.0.1', 9999)
    42 server = ThreadingTCPServer(laddr, ChatServerHanlder)
    43 print(server.socket)
    44 threading.Thread(target=server.serve_forever, name='server').start()
    45 
    46 try:
    47     while True:
    48         cmd = input(">>>>")
    49         if cmd == 'quit':
    50             server.shutdown()
    51             break
    52         print(threading.enumerate())
    53 except Exception as e:
    54     print(e)
    55 
    56 except KeyboardInterrupt:
    57     pass
    58 finally:
    59     print('exit')
    60     sys.exit(0)
    View Code

    总结:

      为每一个连接提供RequestHandlerClass 类实例,一次调用 setup, handle, finish 方法,且使用了try...finally结构保证finish方法一定鞥呢被调动,这些方法一次执行完成,如果想维持这个连接和客户端通信,就需要在handle函数中使用循环,

      socketserver模块提供的不同的,但是编程接口是一样的,即使多线程,多线程的类也是一样的 ,大大减少了编程难度。

      socket 是由self.request 管理的,不需要自己去close,只需要清理自己定义的一些资源。

      每一个请求,生成一个handler 类的实例。各自操作自己的实例属性,以及公用的类属性

      

    为什么要坚持,想一想当初!
  • 相关阅读:
    usb_submit_urb
    xilinx uboot网卡驱动分析
    kernel: swapper: page allocation failure. order:1, mode:0x20
    Linux格式化硬盘 常用命令小记
    spring3:对JDBC的支持 之 JDBC模板类
    spring3: AOP 之代理机制
    spring3: AOP 之切面实例化模型 ——跟我学spring3
    spring3: AOP 之 通知顺序
    spring3: AOP 之 通知参数
    spring3: 切面及通知实例 Aspectj的aop
  • 原文地址:https://www.cnblogs.com/JerryZao/p/9900514.html
Copyright © 2020-2023  润新知