目錄python
注意:只有TCP有粘包現象,UDP永遠不會粘包,爲什麼,且聽我娓娓道來。算法
首先須要掌握一個socket收發消息的原理shell
發送端能夠是一K一K地發送數據,而接收端的應用程序能夠兩K兩K地提走數據,固然也有可能一次提走3K或6K數據,或者一次只提走幾個字節的數據,也就是說,應用程序所看到的數據是一個總體,或說是一個流(stream),一條消息有多少字節對應用程序是不可見的,所以TCP協議是面向流的協議,這也是容易出現粘包問題的緣由。而UDP是面向消息的協議,每一個UDP段都是一條消息,應用程序必須以消息爲單位提取數據,不能一次提取任意字節的數據,這一點和TCP是很不一樣的。怎樣定義消息呢?能夠認爲對方一次性write/send的數據爲一個消息,須要明白的是當對方send一條信息的時候,不管底層怎樣分段分片,TCP協議層會把構成整條消息的數據段排序完成後才呈如今內核緩衝區。json
例如基於TCP的套接字客戶端往服務端上傳文件,發送時文件內容是按照一段一段的字節流發送的,在接收方看了,根本不知道該文件的字節流從何處開始,在何處結束。windows
所謂粘包問題主要仍是由於接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所形成的。服務器
此外,發送方引發的粘包是由TCP協議自己形成的,TCP爲提升傳輸效率,發送方每每要收集到足夠多的數據後才發送一個TCP段。若連續幾回須要send的數據都不多,一般TCP會根據優化算法把這些數據合成一個TCP段後一次發送出去,這樣接收方就收到了粘包數據。socket
udp的recvfrom是阻塞的,一個recvfrom(x)必須對惟一一個sendinto(y),收完了x個字節的數據就算完成,如果y>x數據就丟失,這意味着udp根本不會粘包,可是會丟數據,不可靠tcp
TCP的協議數據不會丟,沒有收完包,下次接收,會繼續上次繼續接收,己端老是在收到ack時纔會清除緩衝區內容。數據是可靠的,可是會粘包。優化
假設客戶端分別發送了兩個數據包D1和D2給服務端,因爲服務端一次讀取到的字節數是不肯定的,故可能存在如下4種狀況。編碼
特例:若是此時服務端TCP接收滑窗很是小,而數據包D1和D2比較大,頗有可能會發生第五種可能,即服務端分屢次才能將D1和D2包接收徹底,期間發生屢次拆包。
server
import socket #生成一個socket對象 soc=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #綁定地址跟端口號 soc.bind(('127.0.0.1',8001)) #監聽(半鏈接池的大小),不是鏈接數 soc.listen(3) #等着客戶端來鏈接,conn至關於鏈接通道,addr是客戶端的地址 while True: print('等待客戶端鏈接') conn,addr=soc.accept() #卡主,若是沒有客戶端鏈接,會一直卡在這,當有鏈接,才繼續往下走 print('有個客戶端鏈接上了',addr) while True: try: data=conn.recv(1024) print(data) data2=conn.recv(1024) print(data2) data3=conn.recv(1024) print(data3) except Exception: break # 關閉通道 conn.close() # 關閉套接字 soc.close()
client
import socket soc=socket.socket() soc.connect(('127.0.0.1',8001)) while True: in_s=input('請輸入要發送的數據:') soc.send(b'a') soc.send(b'b') soc.send(b'c')
補充模塊
import struct #把一個數字打包成固定長度的4字節 obj=struct.pack('i',1098) print(obj) print(len(obj)) l=struct.unpack('i',obj)[0] print(l)
server
import socket import subprocess import struct soc=socket.socket(socket.AF_INET,socket.SOCK_STREAM) soc.bind(('127.0.0.1',8001)) soc.listen(3) while True: print('等待客戶端鏈接') conn,addr=soc.accept() print('有個客戶端鏈接上了',addr) while True: try: data=conn.recv(1024) if len(data)==0: break print(data) obj = subprocess.Popen(str(data,encoding='utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) #執行正確的結果 b 格式,gbk編碼(windows平臺) msg=obj.stdout.read() #發送的時候須要先把長度計算出來 #頭必須是固定長度 #10 #100 #先取出要發送數據長度l l=len(msg) #head 是固定四個字節 head=struct.pack('i',l) #發了頭 conn.send(head) #發了內容 conn.send(msg) except Exception: break # 關閉通道 conn.close() # 關閉套接字 soc.close()
client
import socket import struct soc=socket.socket() soc.connect(('127.0.0.1',8001)) while True: in_s=input('請輸入要執行的命令:') soc.send(in_s.encode('utf-8')) head=soc.recv(4) l=struct.unpack('i',head)[0] # data=soc.recv(l) count=0 data_total=b'' while count<l: if l<1024: #若是接受的數據小於1024 ,直接接受數據大小 data=soc.recv(l) else:#若是接受的數據大於1024 if l-count>=1024: #總數據長度-count(目前收到多少,count就是多少) 若是還大於1024 ,在收1024 data=soc.recv(1024) else: #總數據長度-count(目前收到多少,count就是多少) 若是小於1024,只收剩下的部分就可 data=soc.recv(l-count) data_total+=data count+=len(data) print(str(data_total,encoding='gbk'))
struct
# import struct # #把一個數字打包成固定長度的4字節 # obj=struct.pack('i',10980000000) # print(obj) # print(len(obj)) # # l=struct.unpack('i',obj)[0] # print(l) import json import struct # head={'size':100999999999999999999999990000000000000000 # 000000000000000000000000,'md5':'sdfsdfasdf','filename':'a.txt'} # head_str=json.dumps(head) # head_bytes=head_str.encode('utf-8') # print(len(head_bytes)) # obj=struct.pack('i',len(head_bytes)) # print(obj) # print(len(obj)) #發 send(obj) send(head_bytes) send(b'ddddddddddddddddddd') #收 obj=recv(4) head_len=struct.unpack('i',obj)[0] head_bytes=recv(head_len) head_dic=json.loads(head_bytes) l=head_dic['size']
server
import socket import subprocess import struct soc=socket.socket(socket.AF_INET,socket.SOCK_STREAM) soc.bind(('127.0.0.1',8001)) soc.listen(3) while True: print('等待客戶端鏈接') conn,addr=soc.accept() print('有個客戶端鏈接上了',addr) while True: try: data=conn.recv(1024) if len(data)==0: break print(data) obj = subprocess.Popen(str(data,encoding='utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) #執行正確的結果 b 格式,gbk編碼(windows平臺) msg=obj.stdout.read() #發送的時候須要先把長度計算出來 #頭必須是固定長度 #先發4位,頭的長度 import json dic={'size':len(msg)} dic_bytes=(json.dumps(dic)).encode('utf-8') #head_count是4個字節的長度 head_count=struct.pack('i',len(dic_bytes)) print(dic) conn.send(head_count) #發送頭部內容 conn.send(dic_bytes) #發了內容 conn.send(msg) except Exception: break # 關閉通道 conn.close() # 關閉套接字 soc.close()
client
import socket import struct import json soc=socket.socket() soc.connect(('127.0.0.1',8001)) while True: in_s=input('請輸入要執行的命令:') soc.send(in_s.encode('utf-8')) #頭部字典的長度 head_dic_len=soc.recv(4) #解出真正的長度 head_l=struct.unpack('i',head_dic_len)[0] #byte 字典的長度 #收真正的頭部字典 dic_byte=soc.recv(head_l) head=json.loads(dic_byte) print(head) l=head['size'] count=0 data_total=b'' print(l) while count<l: if l<1024: #若是接受的數據小於1024 ,直接接受數據大小 data=soc.recv(l) else:#若是接受的數據大於1024 if l-count>=1024: #總數據長度-count(目前收到多少,count就是多少) 若是還大於1024 ,在收1024 data=soc.recv(1024) else: #總數據長度-count(目前收到多少,count就是多少) 若是小於1024,只收剩下的部分就可 data=soc.recv(l-count) data_total+=data count+=len(data) print(str(data_total,encoding='gbk'))