1. 程式人生 > >Python網路程式設計之socketserver模組實現併發

Python網路程式設計之socketserver模組實現併發

為什麼要講socketserver?我們之前寫的tcp協議的socket是不是一次只能和一個客戶端通訊,如果用socketserver可以實現和多個客戶端通訊。它是在socket的基礎上進行了一層封裝,也就是說底層還是呼叫的socket,在py2.7裡面叫做SocketServer也就是大寫了兩個S,在py3裡面就小寫了。

  那麼我們先看socketserver怎麼用呢,然後在分析,先看下面的程式碼

import socketserver                              #1、引入模組
class MyServer(socketserver.BaseRequestHandler): #
2、自己寫一個類,類名自己隨便定義,然後繼承socketserver這個模組裡面的BaseRequestHandler這個類 def handle(self): #3、寫一個handle方法,必須叫這個名字 #self.request #6、self.request 相當於一個conn self.request.recv(1024) #7、收訊息 msg = '親,學會了嗎' self.request.send(bytes(msg,encoding
='utf-8')) #8、發訊息 self.request.close() #9、關閉連線 # 拿到了我們對每個客戶端的管道,那麼我們自己在這個方法裡面的就寫我們接收訊息傳送訊息的邏輯就可以了 pass if __name__ == '__mian__': #thread 執行緒,現在只需要簡單理解執行緒,彆著急,後面很快就會講到啦,看下面的圖 server = socketserver.ThreadingTCPServer(('127.0.0.1',8090),MyServer)#4、使用socketserver的ThreadingTCPServer這個類,將IP和埠的元祖傳進去,還需要將上面咱們自己定義的類傳進去,得到一個物件,相當於我們通過它進行了bind、listen
server.serve_forever() #5、使用我們上面這個類的物件來執行serve_forever()方法,他的作用就是說,我的服務一直開啟著,就像京東一樣,不能關閉網站,對吧,並且serve_forever()幫我們進行了accept #注意: #有socketserver 那麼有socketclient的嗎? #當然不會有,我要作為客戶去訪問京東的時候,京東幫我也客戶端了嗎,客戶端是不是在我們自己的電腦啊,並且socketserver對客戶端沒有太高的要求,只需要自己寫一些socket就行了。

  通過上面的程式碼,我們來分析socket的原始碼:(大家還記得面向物件的繼承嗎,來,實戰的時候來啦)

在整個socketserver這個模組中,其實就幹了兩件事情:1、一個是迴圈建立連結的部分,每個客戶連結都可以連線成功  2、一個通訊迴圈的部分,就是每個客戶端連結成功之後,要迴圈的和客戶端進行通訊。
看程式碼中的:server=socketserver.ThreadingTCPServer(('127.0.0.1',8090),MyServer)
 
還記得面向物件的繼承嗎?來,大家自己嘗試著看看原始碼:
 
查詢屬性的順序:ThreadingTCPServer->ThreadingMixIn->TCPServer->BaseServer
 
例項化得到server,先找ThreadMinxIn中的__init__方法,發現沒有init方法,然後找類ThreadingTCPServer的__init__,在TCPServer中找到,在裡面建立了socket物件,進而執行server_bind(相當於bind),server_active(點進去看執行了listen)
找server下的serve_forever,在BaseServer中找到,進而執行self._handle_request_noblock(),該方法同樣是在BaseServer中
執行self._handle_request_noblock()進而執行request, client_address = self.get_request()(就是TCPServer中的self.socket.accept()),然後執行self.process_request(request, client_address)
在ThreadingMixIn中找到process_request,開啟多執行緒應對併發,進而執行process_request_thread,執行self.finish_request(request, client_address)
上述四部分完成了連結迴圈,本部分開始進入處理通訊部分,在BaseServer中找到finish_request,觸發我們自己定義的類的例項化,去找__init__方法,而我們自己定義的類沒有該方法,則去它的父類也就是BaseRequestHandler中找....
原始碼分析總結:
 
基於tcp的socketserver我們自己定義的類中的
 
  self.server即套接字物件
  self.request即一個連結
  self.client_address即客戶端地址
基於udp的socketserver我們自己定義的類中的
 
  self.request是一個元組(第一個元素是客戶端發來的資料,第二部分是服務端的udp套接字物件),如(b'adsf', <socket.socket fd=200, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('127.0.0.1', 8080)>)
  self.client_address即客戶端地址

 一個完整的sockeserver程式碼示例:

    服務端程式碼示例:
import socketserver
class Myserver(socketserver.BaseRequestHandler):
    def handle(self):
        self.data = self.request.recv(1024).strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        self.request.sendall(self.data.upper())
 
if __name__ == "__main__":
    HOST, PORT = "127.0.0.1", 9999
 
    # 設定allow_reuse_address允許伺服器重用地址
    socketserver.TCPServer.allow_reuse_address = True
    # 建立一個server, 將服務地址繫結到127.0.0.1:9999
    #server = socketserver.TCPServer((HOST, PORT),Myserver)
    server = socketserver.ThreadingTCPServer((HOST, PORT),Myserver)
    # 讓server永遠執行下去,除非強制停止程式
    server.serve_forever()

  客戶端程式碼示例:

import socket
 
HOST, PORT = "127.0.0.1", 9999
data = "hello"
 
# 建立一個socket連結,SOCK_STREAM代表使用TCP協議
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    sock.connect((HOST, PORT))          # 連結到客戶端
    sock.sendall(bytes(data + "\n", "utf-8")) # 向服務端傳送資料
    received = str(sock.recv(1024), "utf-8")# 從服務端接收資料
 
print("Sent:     {}".format(data))
print("Received: {}".format(received))