Python的socket模塊源碼中的一些實現(xiàn)要點分析
BaseServer 和 BaseRequestHandler
Python為網(wǎng)絡編程提高了更高級的封裝。SocketServer.py 提供了不少網(wǎng)絡服務的類。它們的設計很優(yōu)雅。Python把網(wǎng)絡服務抽象成兩個主要的類,一個是Server類,用于處理連接相關的網(wǎng)絡操作,另外一個則是RequestHandler類,用于處理數(shù)據(jù)相關的操作。并且提供兩個MixIn 類,用于擴展 Server,實現(xiàn)多進程或多線程。在構建網(wǎng)絡服務的時候,Server 和 RequestHandler 并不是分開的,RequestHandler的實例對象在Server 內(nèi)配合 Server工作。
改模塊的主要幾個Server關系如下:
+------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+
BaseServer 分析
BaseServer 通過__init__初始化,對外提供serve_forever和 handler_request方法。
init 初始化:
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
__init__源碼很簡單。主要作用是創(chuàng)建server對象,并初始化server地址和處理請求的class。熟悉socket編程應該很清楚,server_address是一個包含主機和端口的元組。
serve_forever
創(chuàng)建了server對象之后,就需要使用server對象開啟一個無限循環(huán),下面來分析serve_forever的源碼。
def serve_forever(self, poll_interval=0.5):
self.__is_shut_down.clear()
try:
while not self.__shutdown_request:
r, w, e = _eintr_retry(select.select, [self], [], [],
poll_interval)
if self in r:
self._handle_request_noblock()
finally:
self.__shutdown_request = False
self.__is_shut_down.set()
serve_forever接受一個參數(shù)poll_interval,用于表示select輪詢的時間。然后進入一個無限循環(huán),調(diào)用select方式進行網(wǎng)絡IO的監(jiān)聽。
如果select函數(shù)返回,表示有IO連接或數(shù)據(jù),那么將會調(diào)用_handle_request_noblock方法。
_handle_request_noblock
def _handle_request_noblock(self):
try:
request, client_address = self.get_request()
except socket.error:
return
if self.verify_request(request, client_address):
try:
self.process_request(request, client_address)
except:
self.handle_error(request, client_address)
self.shutdown_request(request)
_handle_request_noblock方法即開始處理一個請求,并且是非阻塞。該方法通過get_request方法獲取連接,具體的實現(xiàn)在其子類。一旦得到了連接,調(diào)用verify_request方法驗證請求。驗證通過,即調(diào)用process_request處理請求。如果中途出現(xiàn)錯誤,則調(diào)用handle_error處理錯誤,以及shutdown_request結(jié)束連接。
verify_request
def verify_request(self, request, client_address):
return True
該方法對request進行驗證,通常會被子類重寫。簡單的返回True即可,然后進入process_request方法處理請求。
process_request
def process_request(self, request, client_address):
self.finish_request(request, client_address)
self.shutdown_request(request)
process_request方法是mixin的入口,MixIn子類通過重寫該方法,進行多線程或多進程的配置。調(diào)用finish_request完成請求的處理,同時調(diào)用shutdown_request結(jié)束請求。
finish_request
def finish_request(self, request, client_address):
self.RequestHandlerClass(request, client_address, self)
finish_request方法將會處理完畢請求。創(chuàng)建requestHandler對象,并通過requestHandler做具體的處理。
BaseRequestHandler 分析
所有requestHandler都繼承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()
該類會處理每一個請求。初始化對象的時候,設置請求request對象。然后調(diào)用setup方法,子類會重寫該方法,用于處理socket連接。接下來的將是handler和finish方法。所有對請求的處理,都可以重寫handler方法。
至此,整個Python提供的Server方式即介紹完畢??偨Y(jié)一下,構建一個網(wǎng)絡服務,需要一個BaseServer用于處理網(wǎng)絡IO,同時在內(nèi)部創(chuàng)建requestHandler對象,對所有具體的請求做處理。
BaseServer - BaseRequestHandler
__init__(server_address, RequestHandlerClass):
BaseServer.server_address
BaseServer.RequestHandlerClass
serve_forever():
select()
BaseServer._handle_request_noblock()
BaseServer.get_request() -> request, client_addres
BaseServer.verify_request()
BaseServer.process_request()
BaseServer.process_request()
BaseServer.finish_request()
BaseServer.RequestHandlerClass()
BaseRequestHandler.__init__(request)
BaseRequestHandler.request
BaseRequestHandler.client_address = client_address
BaseRequestHandler.setup()
BaseRequestHandler.handle()
BaseServer.shutdown_request()
BaseServer.close_request()
BaseServer.shutdown_request()
BaseServer.close_request()
BaseServer 和 BaseRequestHandler是網(wǎng)絡處理的兩個基類。實際應用中,網(wǎng)絡操作更多是使用 TCP 或 HTTP 協(xié)議。SocketServer.py 也提供了更高級的TCP、UDP封裝。下面就來看下關于TCP方面的網(wǎng)絡模塊(UDP和TCP的在代碼組織上差別不是特別大,暫且忽略)。
TCPServer
TCPServer 繼承了BaseServer,初始化的時候,進行了socket套接字的創(chuàng)建。
def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
BaseServer.__init__(self, server_address, RequestHandlerClass)
self.socket = socket.socket(self.address_family,
self.socket_type)
if bind_and_activate:
self.server_bind()
self.server_activate()
__init__ 方法通過 socket模塊創(chuàng)建了socket對象,然后進行調(diào)用server_bind和server_activate。
server_bind
def server_bind(self):
if self.allow_reuse_address:
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind(self.server_address)
self.server_address = self.socket.getsockname()
server_bind 方法進行socket對象的bind操作,以及設置socket相關屬性,如網(wǎng)絡地址的復用。
server_activate def server_activate(self): self.socket.listen(self.request_queue_size)
server_activate 方法也比較簡單,添加socket對象的listen。
get_request
該類最重要的方法就是 get_request。該方法進行返回socket對象的請求連接。
def get_request(self): """Get the request and client address from the socket. """ return self.socket.accept()
get_request方法是在BaseServer基類中的_handle_request_noblock中調(diào)用,從那里里傳入套接字對象獲取的連接信息。如果是UDPServer,這里獲取的就是UDP連接。
此外,TCPServer還提供了一個 fileno 方法,提供給基類的select調(diào)用返回文件描述符。
StreamRequestHandler
TCPServer實現(xiàn)了使用tcp套接字的網(wǎng)絡服務,Handler方面則是對應的StreamRequestHandler。它繼承了BaseRequestHandler?;惖膕etup方法和finish方法被它重寫,用于通過連接實現(xiàn)緩存文件的讀寫操作。
setup方法:
def setup(self):
self.connection = self.request
if self.timeout is not None:
self.connection.settimeout(self.timeout)
if self.disable_nagle_algorithm:
self.connection.setsockopt(socket.IPPROTO_TCP,
socket.TCP_NODELAY, True)
self.rfile = self.connection.makefile('rb', self.rbufsize)
self.wfile = self.connection.makefile('wb', self.wbufsize)
setup判斷了是否使用nagle算法。然后設置對應的連接屬性。最重要的就是創(chuàng)建了一個可讀(rfile)和一個可寫(wfile)的“文件”對象,他們實際上并不是創(chuàng)建了文件,而是封裝了讀取數(shù)據(jù)和發(fā)送數(shù)據(jù)的操作,抽象成為對文件的操作??梢岳斫鉃?self.rfile 就是讀取客戶端數(shù)據(jù)的對象,它有一些方法可以讀取數(shù)據(jù)。self.wfile則是用來發(fā)送數(shù)據(jù)給客戶端的對象。后面的操作,客戶端數(shù)據(jù)到來會被寫入緩沖區(qū)可讀,需要向客戶端發(fā)送數(shù)據(jù)的時候,只需要向可寫的文件中write數(shù)據(jù)即可。
實現(xiàn)TCP服務需要使用TCPServer和StreamRequestHandler共同協(xié)作。大致函數(shù)調(diào)用流程如下,函數(shù)調(diào)用用括號表示,賦值不帶括號,沒有類前綴的表示系統(tǒng)調(diào)用:
TCPServer - StreamRequestHandler
__init__(server_address, RequestHandlerClass):
BaseServer.server_address
BaseServer.RequestHandlerClass
TCPServer.socket = socket.socket(self.address_family, self.socket_type)
TCPServer.server_bind()
TCPServer.server_activate()
serve_forever():
select()
BaseServer._handle_request_noblock()
TCPServer.get_request() -> request, client_addres
socket.accept()
BaseServer.verify_request()
BaseServer.process_request()
BaseServer.process_request()
BaseServer.finish_request(request, client_address)
BaseServer.RequestHandlerClass()
BaseRequestHandler.__init__(request)
BaseRequestHandler.request
BaseRequestHandler.client_address = client_address
StreamRequestHandler.setup()
StreamRequestHandler.connection = StreamRequestHandler.request
StreamRequestHandler.rfile
StreamRequestHandler.wfile
BaseRequestHandler.handle()
StreamRequestHandler.finsih()
StreamRequestHandler.wfile.close()
StreamRequestHandler.rfile.close()
BaseServer.shutdown_request(request)
TCPServer.shutdown()
request.shutdown()
TCPServer.close_request(request)
request.close()
TCPServer.shutdown_request(request)
TCPServer.shutdown(request)
request.shutdown()
TCPServer.close_request(request)
request.close()
最早關于介紹BaseServer的時候,我們知道python對BaseServer設計的時候,預留了可用于Mixin擴展多線程或多進程的接口。mixin通過復寫父類的parse_request方法實現(xiàn)。
ThreadingMixIn
ThreadingMixIn 類實現(xiàn)了多線程的方式,它只有兩個方法,分別是process_request和 process_request_thread方法。多進程的方式是ForkingMixIn,暫且略過。
process_request
def process_request(self, request, client_address):
t = threading.Thread(target = self.process_request_thread,
args = (request, client_address))
t.daemon = self.daemon_threads
t.start()
process_request方法復寫了父類的此方法。以此為接口入口,對每一個請求,調(diào)用Thread開啟一個新的線程。每一個線程都綁定process_request_thread方法。
process_request_thread
def process_request_thread(self, request, client_address):
try:
self.finish_request(request, client_address)
self.shutdown_request(request)
except:
self.handle_error(request, client_address)
self.shutdown_request(request)
process_request_thread方法和BaseServer里的parse_request幾乎一樣。只不過是多線程的方式調(diào)用。
使用的時候,通過多繼承調(diào)用接口,例如:
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
具體的調(diào)用過程如下:
ThreadingMixIn -- TCPServer - StreamRequestHandler
__init__(server_address, RequestHandlerClass):
BaseServer.server_address
BaseServer.RequestHandlerClass
TCPServer.socket = socket.socket(self.address_family, self.socket_type)
TCPServer.server_bind()
TCPServer.server_activate()
serve_forever():
select()
BaseServer._handle_request_noblock()
TCPServer.get_request() -> request, client_addres
socket.accept()
BaseServer.verify_request()
BaseServer.process_request()
ThreadingMixIn.process_request()
t = threading.Thread(target = ThreadingMixIn.process_request_thread)
ThreadingMixIn.process_request_thread
BaseServer.finish_request(request, client_address)
BaseServer.RequestHandlerClass()
BaseRequestHandler.__init__(request)
BaseRequestHandler.request
BaseRequestHandler.client_address = client_address
StreamRequestHandler.setup()
StreamRequestHandler.connection = StreamRequestHandler.request
StreamRequestHandler.rfile
StreamRequestHandler.wfile
BaseRequestHandler.handle()
StreamRequestHandler.finsih()
StreamRequestHandler.wfile.close()
StreamRequestHandler.rfile.close()
BaseServer.shutdown_request(request)
TCPServer.shutdown()
request.shutdown()
TCPServer.close_request(request)
request.close()
TCPServer.shutdown_request(request)
TCPServer.shutdown(request)
request.shutdown()
TCPServer.close_request(request)
request.close()
相關文章
python寫入csv時writerow()和writerows()函數(shù)簡單示例
這篇文章主要給大家介紹了關于python寫入csv時writerow()和writerows()函數(shù)的相關資料,writerows和writerow是Python中csv模塊中的兩個函數(shù),用于將數(shù)據(jù)寫入CSV文件,需要的朋友可以參考下2023-07-07
Python中PyExecJS(執(zhí)行JS代碼庫)的具體使用
pyexecjs是一個用Python來執(zhí)行JavaScript代碼的工具庫,本文主要介紹了Python中PyExecJS(執(zhí)行JS代碼庫)的具體使用,具有一定的參考價值,感興趣的可以了解一下2024-02-02
python數(shù)據(jù)預處理之數(shù)據(jù)標準化的幾種處理方式
這篇文章主要介紹了python數(shù)據(jù)預處理之數(shù)據(jù)標準化的幾種處理方式,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-07-07
簡單介紹一下pyinstaller打包以及安全性的實現(xiàn)
這篇文章主要介紹了簡單介紹一下pyinstaller打包以及安全性的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-06-06
JavaScript實現(xiàn)一維數(shù)組轉(zhuǎn)化為二維數(shù)組
下面小編就為大家分享一篇JavaScript實現(xiàn)一維數(shù)組轉(zhuǎn)化為二維數(shù)組,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-04-04

