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.7\python.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.7\python.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 類的實例。各自操作自己的實例屬性,以及公用的類屬性

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM