一、文件上傳/下載python
學習了socket套接字,咱們如今能夠寫一個文件上傳/下載的程序,以下示例:算法
分析上邊代碼,咱們發現,client發送上傳文件相關信息的字典序列化以後,server又給client發送了一個狀態碼,而後client纔開始上傳文件數據,思考一下若是不發送狀態碼,直接發送文件數據且避免黏包現象發生該怎麼寫?沒錯!能夠用struct模塊解決,以下示例:併發
struct(四chua科特)打包如何用呢,簡單寫一下:socket
import struct num = 156 #將int類型的數據打包成4個字節的數據 num_stru = struct.pack('i',num) #pack(爬可) print(len(num_stru)) #他的長度四個字節 print(num_stru) print('11111111111111111111111111111111') #在經過int類型解包,將前面打包的數據解包成打包以前的int數據 num2 = struct.unpack('i',num_stru) #解包出來是個元組 unpack(嗯牌可) print(num2)#(156,) print(num2[0])
服務端代碼:tcp
客戶端代碼:學習
分析上面的代碼,看看是如何避免黏包現象的?client先給server發送字典的長度的pack包,再發送字典,再發送文件數據,server先接收4字節的pack包,進行unpack後獲得字典長度,再根據字典長度接收字典,最後再循環接收文件數據。而且該示例還進行了MD5算法來進行文件一致性校驗。線程
2、socketserver(併發)(騷k特點我)3d
經過這兩天學習socket套接字,咱們發如今寫服務端和客戶端的時候,在創建鏈接以前老是要寫一些固定的重複代碼來,好比socket.socket()(建立套接字對象)、bind()、acecept()等等,學習了socketserver(併發)以後,就能夠少寫一些代碼,而且實現併發,以下示例:code
服務端:server
import socketserver #1 定義一個類 class MyServer(socketserver.BaseRequestHandler): #2 類裏面繼承socketserver.BaseRequestHandler #BaseRequestHandler貝斯蕊快四特漢得了 # 3 類裏面定義一個handle方法,handle名稱不能變 def handle(self): #handle(漢得了) while 1: # self.request #conn連接通道 request(蕊快死特) from_client_data = self.request.recv(1024).decode('utf-8') print(from_client_data) server_input = input('明巍sb說>>>') self.request.send(server_input.encode('utf-8')) # self.request.close() if __name__ == '__main__': #服務端的IP地址和端口 ip_port = ('127.0.0.1',8001) socketserver.TCPServer.allow_reuse_address = True #容許地址重用 #綁定IP地址和端口,而且啓動我定義的上面這個類 server = socketserver.ThreadingTCPServer(ip_port,MyServer) #永久的給我執行下去 server.serve_forever()
客戶端:
import socket tcp_client = socket.socket() server_ip_port = ('127.0.0.1',8001) tcp_client.connect(server_ip_port) while 1: client_msg = input('大陽哥>>>') tcp_client.send(client_msg.encode('utf-8')) from_server_msg = tcp_client.recv(1024).decode('utf-8') print(from_server_msg)
3、解讀python中socketserver源碼
結合下圖中類的繼承關係和類中的方法,分析socketserver代碼的執行過程:
a、初始化相關過程:server = socketserver.ThreadingTCPServer(('127.0.0.1',8800),Myserver)
(1)TCPServer類中的__init__方法:
TCPServer類中主動執行BaseServer類中的__init__ 方法(把本身建立的Myserver類傳參);
建立socket.socket()對象;
server_bind() -- 在TCPServer類中執行了socket.bind(self.server_address)
server_activate() -- 在TCPServer類中執行了socket.listen(5)
(2)BaseServer類中的__init__ 方法:
將參數server_address(ip地址和端口號)賦值給了self.server_address;
將形參RequestHandlerClass(實參是咱們本身建立的Myserver類)賦值給了self.RequestHandlerClass;
b、執行serve_forever的相關代碼:
(1)執行BaseServer類中的serve_forever()方法:
注意看if ready後邊的那句self._handle_request_noblock(),其餘先忽略;
(2)執行BaseServer中的_handle_request_noblock(self)方法:
看第一個try中request,client_address = self.get_request(),
TCPServer中有get_request()方法,方法中是return self.socket.accept(),即等待鏈接;
再看第二個try中的self.process_request(request,client_address)
(3)鏈接成功以後拿到了request(即conn)和client_address(即addr)再去執行BaseServer類中的.process_request方法;
建立線程,執行方法process_request_thread()
(4)執行ThreadingMixIn 類中的process_request_thread(self, request, client_address)方法:
看try中self.finish_request(request,client_address)
(5)執行BaseServer中的finish_request(request,client_address)方法:
此時還記得RequestHandlerClass這個參數嗎?正是咱們執行BaseServer中__init__方法時傳過來的本身建立的類Myserver,類() 即實例化一個Myserver對象,而且傳了兩個參數(conn,addr),可是咱們本身寫的Myserver類中沒有__init__方法,因此執行Myserver父類BaseRequestHandler類中的__init__方法,而且帶了每一個鏈接的線程的conn和addr:
(6)執行BaseRequestHandler中的__init__方法:
此時self是Myserver類的對象,因此優先去執行Myserver類中handle方法。
附錄:如下是各種的繼承關係以及類中成員方法: