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
測試結果說明:
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 ====
將ThreadingTCPServer 換成TCPServer, 同時連接2個客戶端觀察效果發現,是串行的,一個結束之后,才能另外一個處理
並且每次都是阻塞到 recv 方法處
ThreadingTCPServer 是異步的,可以同時處理多個請求。
TCPServer 是同步的,一個連接處理完了,即一個連接的 handle方法 執行完了,才能處理另一個連接,且只有主線程。
總結:
創建服務器需要的幾個步驟:
-
- 從BaseRequestHandler類派生出子類,並覆蓋器handler() 方法來創建請求處理程序類,此方法將處理傳入請求
- 實例化一個服務器類,傳參服務器的地址和請求處理類
- 調用服務器實例的handle_request(),或server_forever() 方法
- 調用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)
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)
總結:
為每一個連接提供RequestHandlerClass 類實例,一次調用 setup, handle, finish 方法,且使用了try...finally結構保證finish方法一定鞥呢被調動,這些方法一次執行完成,如果想維持這個連接和客戶端通信,就需要在handle函數中使用循環,
socketserver模塊提供的不同的,但是編程接口是一樣的,即使多線程,多線程的類也是一樣的 ,大大減少了編程難度。
socket 是由self.request 管理的,不需要自己去close,只需要清理自己定義的一些資源。
每一個請求,生成一個handler 類的實例。各自操作自己的實例屬性,以及公用的類屬性