上文中,我們自己使用socket和threading模塊實現了一個簡單的多線程服務器。在非正式環境,隨便用用還是可以的,但是如果要在生產環境中使用,那是萬萬不夠的。
Python考慮得很周到,爲了滿足我們對多線程網絡服務器的需求,提供了socketserver模塊。socketserver在內部使用IO多路複用以及多線程/進程機制,實現了併發處理多個客戶端請求的socket服務端。每個客戶端請求連接到服務器時,socketserver服務端都會創建一個“線程”或者“進程” 專門負責處理當前客戶端的所有請求。
讓我們來看看socketserver模塊的Python源碼(what!我連基本的都還沒明白,你就讓我看源代碼......):
import socket
import selectors
import os
import errno
import sys
try:
import threading
except ImportError:
import dummy_threading as threading
from io import BufferedIOBase
from time import monotonic as time
__all__ = ["BaseServer", "TCPServer", "UDPServer",
"ThreadingUDPServer", "ThreadingTCPServer",
"BaseRequestHandler", "StreamRequestHandler",
"DatagramRequestHandler", "ThreadingMixIn"]
if hasattr(os, "fork"):
__all__.extend(["ForkingUDPServer","ForkingTCPServer", "ForkingMixIn"])
if hasattr(socket, "AF_UNIX"):
__all__.extend(["UnixStreamServer","UnixDatagramServer",
"ThreadingUnixStreamServer",
"ThreadingUnixDatagramServer"])
# poll/select have the advantage of not requiring any extra file descriptor,
# contrarily to epoll/kqueue (also, they require a single syscall).
if hasattr(selectors, 'PollSelector'):
_ServerSelector = selectors.PollSelector
else:
_ServerSelector = selectors.SelectSelector
class BaseServer:
pass
class TCPServer(BaseServer):
pass
class UDPServer(TCPServer):
pass
if hasattr(os, "fork"):
pass
class ThreadingMixIn:
pass
if hasattr(os, "fork"):
class ForkingUDPServer(ForkingMixIn, UDPServer): pass
class ForkingTCPServer(ForkingMixIn, TCPServer): pass
class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
if hasattr(socket, 'AF_UNIX'):
pass
class StreamRequestHandler(BaseRequestHandler):
pass
class _SocketWriter(BufferedIOBase):
pass
class DatagramRequestHandler(BaseRequestHandler):
pass
我把每個類的具體代碼給省略後,就剩下這麼些內容。
在socketserver模塊的開頭,導入了socket和threading等一些Python內置模塊,和我們一樣樣的!然後,定義了一個all魔法變量,表示我們默認只能使用該模塊裏的這些類。
接下來,if hasattr(os, "fork"):判斷語句,用於測試當前操作系統是否支持fork操作,如果支持,好吧,你可以你解鎖更多功能。
if hasattr(socket, "AF_UNIX"):是針對UNIX系統的功能,同樣基於操作系統的支持。
if hasattr(selectors, 'PollSelector'):就是最關鍵的IO複用機制的選擇了,如果操作系統支持Poll,則啓用PollSelector,否則使用默認的SelectSelector。
拋開那些複雜的類名及其作用,從邏輯流程來講,其實socketserver也很簡單。
閱讀源代碼有助於我們理解模塊的運行機制,提高自己的代碼水平。但具體使用模塊,還要落到實處。對於socketserver模塊,我們最常使用的是ThreadingTCPServer類。其定義如下:
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
這裏的pass不是我省略了,而是它真的就只有pass,-。它本身一句代碼都沒有,一切的功能都從兩個父類裏繼承,ThreadingMixIn爲它提供了多線程能力,TCPServer爲它提供基本的socket通信能力。其繼承關係,如下圖所示:
ThreadingTCPServer實現的Soket服務器內部會爲每個客戶端創建一個線程,該線程用來和客戶端進行交互。服務器相當於一個總管,在接收連接並創建新的線程後,就撒手不管了,後面的通信就是線程和客戶端之間的連接了,一定要理解這一點!
使用ThreadingTCPServer的要點:
- 創建一個繼承自socketserver.BaseRequestHandler的類;
- 這個類中必須定義一個名字爲handle的方法,不能是別的名字!
- 將這個類,連同服務器的ip和端口,作爲參數傳遞給ThreadingTCPServer()構造器
- 手動啓動ThreadingTCPServer。
下面是一個ThreadingTCPServer使用的例子:
服務器端:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socketserver
class MyServer(socketserver.BaseRequestHandler):
"""
必須繼承socketserver.BaseRequestHandler類
"""
def handle(self):
"""
必須實現這個方法!
:return:
"""
conn = self.request # request裏封裝了所有請求的數據
conn.sendall('歡迎訪問socketserver服務器!'.encode())
while True:
data = conn.recv(1024).decode()
if data == "exit":
print("斷開與%s的連接!" % (self.client_address,))
break
print("來自%s的客戶端向你發來信息:%s" % (self.client_address, data))
conn.sendall(('已收到你的消息<%s>' % data).encode())
if __name__ == '__main__':
# 創建一個多線程TCP服務器
server = socketserver.ThreadingTCPServer(('127.0.0.1', 9999), MyServer)
print("啓動socketserver服務器!")
# 啓動服務器,服務器將一直保持運行狀態
server.serve_forever()
客戶端:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
客戶端依然使用socket模塊就可以了,不需要導入socketserver模塊
"""
import socket
ip_port = ('127.0.0.1', 9999)
sk = socket.socket()
sk.connect(ip_port)
sk.settimeout(5)
data = sk.recv(1024).decode()
print('服務器:', data)
while True:
inp = input('你:').strip()
if not inp:
continue
sk.sendall(inp.encode())
if inp == 'exit':
print("謝謝使用,再見!")
break
data = sk.recv(1024).decode()
print('服務器:', data)
sk.close()
客戶端的代碼很好理解,和前面一樣樣的,關鍵是服務器端。
分析一下服務器端的代碼,核心要點有這些:
- 連接數據封裝在self.request中!調用send()和recv()方法都是通過self.request對象。
- handle()方法是整個通信的處理核心,一旦它運行結束,當前連接也就斷開了(但其他的線程和客戶端還正常),因此一般在此設置一個無限循環。
- 注意server = socketServer.ThreadingTCPServer((‘127.0.0.1’,8009),MyServer)中參數傳遞的方法。
- server.serve_forever()表示該服務器在正常情況下將永遠運行。
socketserver模塊還提供了ThreadingUDPServer類,用於提供多線程的UDP服務。還有ForkingTCPServer類,當操作系統支持fork操作的時候,可以實現多進程服務器。他們的用法和ThreadingTCPServer基本類似,大家可以自行嘗試。