socketserver 是 python 標(biāo)準(zhǔn)庫中的模塊鸟召,作為一個網(wǎng)絡(luò)服務(wù)器框架(A framework for network servers)拾酝,其作用是簡化編寫網(wǎng)絡(luò)服務(wù)器流程,并且擔(dān)當(dāng)標(biāo)準(zhǔn)庫中其它模塊跟第三方模塊的底層服務(wù)器基礎(chǔ)犬缨。
socketserver 是 python3 中的新名字扮匠,在 python2 中名為 SocketServer吠架,本次閱讀的是 0.4 版本。
該框架主要是使用 ServerClass 跟 RequestHandlerClass 兩大類精居。
- ServerClass 處理服務(wù)端與客戶端的通訊
- RequestHandlerClass 處理數(shù)據(jù)的解析诲侮,接收和發(fā)送;主要的業(yè)務(wù)邏輯
ServerClass
-
BaseServer
抽象基類 -
TCPServer
處理流式套接字 -
UnixStreamServer
處理本地處理流式套接字箱蟆,只適用UNIX平臺 -
UDPServer
處理數(shù)據(jù)報套接字 -
UnixDatagramServer
處理本地處理數(shù)據(jù)報套接字沟绪,只適用UNIX平臺
RequestHandlerClass
-
BaseRequestHandler
處理基類 -
StreamRequestHandler
處理流式套接字 -
DatagramRequestHandler
處理數(shù)據(jù)報套接字
通過對通訊和數(shù)據(jù)處理的解耦,可以實現(xiàn)不同組合的服務(wù)器用于處理不同的套接字空猜,但這樣的服務(wù)器绽慈,僅僅是同步處理請求,如果要實現(xiàn)異步處理請求辈毯,還需使用 MixINClass
-
ForkingMixIn
利用多進(jìn)程實現(xiàn)異步坝疼,接收一個請求后,fork 一個進(jìn)程進(jìn)行響應(yīng)處理 -
ThreadingMixIn
利用多線程實現(xiàn)異常谆沃,接收一個請求后钝凶,使用一個線程進(jìn)行響應(yīng)處理
關(guān)于 MixIN,Mixin 掃盲班
Server 類的繼承關(guān)系
+------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+
UDPServer 繼承 TCPServer唁影,TCPServer 繼承 BaseServer耕陷,UnixStreamServer 繼承自 TCPServer,UnixDatagramServer 繼承自 UDPServer据沈。
if hasattr(socket, 'AF_UNIX'):
class UnixStreamServer(TCPServer):
address_family = socket.AF_UNIX
class UnixDatagramServer(UDPServer):
address_family = socket.AF_UNIX
從源碼看哟沫,UnixStreamServer 跟 TCPServer,UnixDatagramServer 跟 UDPServer锌介,僅僅是改變了 address_family
嗜诀。
關(guān)于 socket.AF_UNIX猾警,主要是用于同一臺機器上的進(jìn)程間通信。
AF_INET域與AF_UNIX域socket通信原理對比
if hasattr(os, "fork"):
class ForkingUDPServer(ForkingMixIn, UDPServer): pass
class ForkingTCPServer(ForkingMixIn, TCPServer): pass
class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
而多進(jìn)程服務(wù)器跟多線程服務(wù)器隆敢,也僅僅是把 TCPServer发皿、UDPServer 跟 ForkingMixIn、ThreadingMixIn 組合在一起而已拂蝎。
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
RequestHandler 類的繼承關(guān)系
+--------------------+ +----------------------+
| BaseRequestHandler |------->| StreamRequestHandler |
+--------------------+ +----------------------+
|
v
+------------------------+
| DatagramRequestHandler |
+------------------------+
StreamRequestHandler 跟 DatagramRequestHandler 是繼承 BaseRequestHandler雳窟,它們都重寫了 setup 跟 finish,本質(zhì)上是用緩存對套接字提供服務(wù)匣屡。
調(diào)用流程
以 示例 來看調(diào)用流程
import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
def handle(self):
# self.request is the TCP socket connected to the client
self.data = self.request.recv(1024).strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
# just send back the same data, but upper-cased
self.request.sendall(self.data.upper())
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
server.serve_forever()
這里定義了一個繼承自 BaseRequestHandler 的 MyTCPHandler封救,重寫了 handle 方法,然后主機捣作、端口一同傳入 TCPServer誉结。
class BaseServer:
def __init__(self, server_address, RequestHandlerClass):
"""Constructor. May be extended, do not override."""
self.server_address = server_address
self.RequestHandlerClass = RequestHandlerClass
self.__is_shut_down = threading.Event()
self.__shutdown_request = False
class TCPServer(BaseServer):
def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
"""Constructor. May be extended, do not override."""
BaseServer.__init__(self, server_address, RequestHandlerClass)
self.socket = socket.socket(self.address_family,
self.socket_type)
if bind_and_activate:
try:
self.server_bind()
self.server_activate()
except:
self.server_close()
raise
1.TCPServer 實例化,調(diào)用了自身 server_bind()
方法券躁,綁定了傳入的 server_address
參數(shù)惩坑,而 server_activate()
方法是重寫自 BaseServer
類,主要是請求隊列大小的設(shè)置也拜。
2.啟動服務(wù) 以舒,調(diào)用 BaseServer
類的 serve_forever()
方法,用于不斷監(jiān)聽請求慢哈,當(dāng)有請求時調(diào)用 _handle_request_noblock()
方法完成請求處理蔓钟,然后調(diào)用 service_actions()
處理后的操作;這個方法在 ForkingMixIn
類會被重寫卵贱,用于解決完成請求處理的進(jìn)程滥沫。
def _handle_request_noblock(self):
try:
request, client_address = self.get_request()
except OSError:
return
if self.verify_request(request, client_address):
try:
self.process_request(request, client_address)
except Exception:
self.handle_error(request, client_address)
self.shutdown_request(request)
except:
self.shutdown_request(request)
raise
else:
self.shutdown_request(request)
_handle_request_noblock()
方法處理請求時,會調(diào)用下列方法
-
get_request()
方法在TCPServer
類中定義键俱,調(diào)用socket.accept()
方法兰绣,返回request
參數(shù)和client_address
參數(shù) -
verify_request(request, client_address)
方法在BaseServer
類中定義,可以在子類中重寫做一些驗證處理 -
process_request(request, client_address)
方法請求處理函數(shù)编振,用于調(diào)用finish_request()
方法缀辩,在ForkingMixIn
類和ThreadingMixIn
類中會被重寫,用進(jìn)程或線程發(fā)起處理 -
finish_request(request, client_address)
方法具體的請求處理過程踪央,這里是自定義MyTCPHandler
類實例化臀玄,該類繼承了BaseRequestHandler
類,遵傳它的定義杯瞻,當(dāng)自己被實例化時镐牺,會調(diào)用handle()
方法 -
shutdown_request(request)
方法在TCPServer
類中定義,會先request.shutdown(socket.SHUT_WR)
魁莉,出錯再調(diào)用close_request(request)
方法做request.close()
,而在UDPServer
類中,該函數(shù)會重寫pass
掉
3.關(guān)閉服務(wù)旗唁,要結(jié)束服務(wù)畦浓,可以手動調(diào)用 BaseServer
類中的 shutdown()
方法,示例說直接用 Ctrl-C
def shutdown(self):
self.__shutdown_request = True
self.__is_shut_down.wait()
主要是
__shutdown_request
標(biāo)識更換检疫,并阻塞等待serve_forever()
線程返回
這便是示例中 TCPServer
的生命周期讶请。
些許問題
request.shutdowm(socket.SHUT_WR)
跟 request.close()
區(qū)別
1.調(diào)用shutdown會馬上關(guān)閉指定鏈接, 而close會等到描述符的引用計數(shù)器為0時才會開始關(guān)閉鏈接
2.close會同時關(guān)閉兩個鏈接, 而shutdown值關(guān)閉指定鏈接
3.close后文件描述符不再可用(引用基數(shù)為0,釋放資源), shutdown后文件描述符是可用的.
具體
poll屎媳、epoll 選擇
if hasattr(selectors, 'PollSelector'):
_ServerSelector = selectors.PollSelector
else:
_ServerSelector = selectors.SelectSelector
## Lib/selectors.py
# Choose the best implementation, roughly:
# epoll|kqueue|devpoll > poll > select.
# select() also can't accept a FD > FD_SETSIZE (usually around 1024)
if 'KqueueSelector' in globals():
DefaultSelector = KqueueSelector
elif 'EpollSelector' in globals():
DefaultSelector = EpollSelector
elif 'DevpollSelector' in globals():
DefaultSelector = DevpollSelector
elif 'PollSelector' in globals():
DefaultSelector = PollSelector
else:
DefaultSelector = SelectSelector
socketserver 模塊中手動選擇了 poll 庫夺溢,而不是使用模塊提供的 DefaultSelector 自適應(yīng)選擇。