標籤:客戶 request 效率 系統 tcp/ip協議 模組 recvfrom 演算法 用戶端串連
一、socket是什麼
在TCP/IP五層協議中,工作在應用程式層的軟體程式要想把它的資料發送給網路另一端的電腦並讓那台電腦能把接收到的資料正常解析出來傳遞給對應的程式就需要按照互連網協議在資料的前面依次加上每一層的頭部資訊,如果這個過程讓程式員自己去完成就需要去瞭解每一層的協議的工作原理,這無疑是非常耗費時間的極大的降低開發效率,因此前人為了避免這種情況就在應用程式層和下面四層之間加入了socket層。socket層對下四層做了封裝並給開發人員提供了方便使用的介面,開發人員只需要遵循socket的規則去寫程式,socket就會協助我們把程式的資料加上應該有的下四層的頭部資訊,所以我們無需再為資料的封裝操心了。
二、TCP協議的socket使用方法
服務端基本格式:
import socketphone=socket.socket() #建立socket對象,預設參數為family=AF_INET(基於網路的通訊端家族), type=SOCK_STREAM(使用TCP/IP協議),可以修改phone.bind((‘127.0.0.1‘,8081)) #服務端綁定IP和連接埠,方便用戶端串連phone.listen() #監聽是否有用戶端試圖串連while True: conn,addr=phone.accept() #建立串連,傳回值是一個包含連線物件以及用戶端IP、連接埠元祖的大元祖 while True: try: data=conn.recv(1024) #接收資料,bytes類型 conn.send(data.upper()) #發送資料,bytes類型 except Exception: break conn.close() #關閉串連phone.close() #關閉socket
用戶端基本格式:
import socketphone=socket.socket() #建立socket對象phone.connect((‘127.0.0.1‘,8081)) #串連服務端while True: msg=input(‘>>:‘).strip() if not msg:continue phone.send(msg.encode(‘utf-8‘)) #發送資料,bytes類型 data=phone.recv(1024) #接收資料,bytes類型 print(data)
三、UDP協議的socket使用方法
服務端基本格式:
import socketip_port=(‘127.0.0.1‘,9000)BUFSIZE=1024udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)udp_server_client.bind(ip_port)while True: msg,addr=udp_server_client.recvfrom(BUFSIZE) #接收的是發送端的訊息以及發送端地址的元祖 print(msg,addr) udp_server_client.sendto(msg.upper(),addr)
用戶端基本格式:
import socketip_port=(‘127.0.0.1‘,9000)BUFSIZE=1024udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)while True: msg=input(‘>>: ‘).strip() if not msg:continue udp_server_client.sendto(msg.encode(‘utf-8‘),ip_port) back_msg,addr=udp_server_client.recvfrom(BUFSIZE) print(back_msg.decode(‘utf-8‘),addr)
四、粘包
1、什麼是粘包
在一次發或者收的過程中多個資料包的資訊粘在了一起,造成資料混亂,要注意的是粘包只有TCP傳輸中會發生。
2、成因
發送端:TCP協議在傳輸資料的過程中會使用一種最佳化演算法,這種演算法為了減少網路延遲對傳輸資料的影響提高傳輸效率,在遇到短時間多個小資料包進入系統緩衝時會將這幾個資料包封裝在一起傳輸,這就造成了粘包。
接收端:接收端接收一個資料包的資料時在沒有接收完的情況下又有一個新的資料包進入緩衝,這時就可能會產生粘包。
3、解決方案
1)在發送小資料包時中間間隔一個大於網路延遲的時間
2)在發送真實資料前先告知接收端真實資料的長度
4、利用struct模組自訂傳輸協議
1、自訂前序,格式類似如下
head={‘cmd‘:‘put‘,‘filename‘:filepath,‘filesize‘:filesize}
2、利用json模組將前序序列化並且轉碼為bytes類型方便傳輸
3、計算轉碼後的前序長度
4、利用struct模組將前序長度資訊封裝為固定長度的bytes類型資料,然後發送給接收端
head_pack=struct.pack(‘i‘,len(head_json.encode(‘utf-8‘))) #將數字轉換為4個位元組的bytes類型資料
5、將轉碼後的前序發送給接收端
6、發送真實資料
五、socketserver
服務端:
import socketserverimport structimport jsonimport osclass FtpServer(socketserver.BaseRequestHandler): #定義自己的socketserver類,繼承BaseRequestHandler coding=‘utf-8‘ server_dir=‘file_upload‘ max_packet_size=1024 BASE_DIR=os.path.dirname(os.path.abspath(__file__)) def handle(self): #重寫handle方法,處理所有通訊過程 print(self.request) while True: data=self.request.recv(4) data_len=struct.unpack(‘i‘,data)[0] head_json=self.request.recv(data_len).decode(self.coding) head_dic=json.loads(head_json) # print(head_dic) cmd=head_dic[‘cmd‘] if hasattr(self,cmd): func=getattr(self,cmd) func(head_dic) def put(self,args): file_path = os.path.normpath(os.path.join( self.BASE_DIR, self.server_dir, args[‘filename‘] )) filesize = args[‘filesize‘] recv_size = 0 print(‘----->‘, file_path) with open(file_path, ‘wb‘) as f: while recv_size < filesize: recv_data = self.request.recv(self.max_packet_size) f.write(recv_data) recv_size += len(recv_data) print(‘recvsize:%s filesize:%s‘ % (recv_size, filesize))ftpserver=socketserver.ThreadingTCPServer((‘127.0.0.1‘,8080),FtpServer)#執行個體化一個server對象並將服務端的IP和連接埠以及自己定義的socketserver類的類名傳入ftpserver.serve_forever()#啟動socketserver並永久運行
用戶端:
只需要使用socket即可
python網路編程之socket