1. 程式人生 > >python/socket編程之粘包

python/socket編程之粘包

處理機制 滿了 bytes true src exceptio 協議 粘包問題 時間間隔

python/socket編程之粘包

粘包:

只有TCP有尿包現象,UDP永遠不會粘包。

首先需要掌握一個socket收發消息的原理

技術分享

發送端可以是1k,1k的發送數據而接受端的應用程序可以2k,2k的提取數據,當然也有可能是3k或者多k提取數據,也就是說,應用程序是不可見的,因此TCP協議是面來那個流的協議,這也是容易出現粘包的原因而UDP是面向笑死的協議,每個UDP段都是一條消息,應用程序必須以消息為單位提取數據,不能一次提取任一字節的數據,這一點和TCP是很同的。怎樣定義消息呢?認為對方一次性write/send的數據為一個消息,需要命的是當對方send一條信息的時候,無論鼎城怎麽樣分段分片,TCP協議層會把構成整條消息的數據段排序完成後才呈現在內核緩沖區。

例如基於TCP的套接字客戶端往服務器端上傳文件,發送時文件內容是按照一段一段的字節流發送的,在接收方看來更笨不知道文件的字節流從何初開始,在何處結束。

所謂粘包問題主要還是因為接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所造成的

發送方引起的粘包是由TCP協議本身造成的,TCP為提高傳輸效率,發送方往往要收集到足夠多數據後才發上一個TCP段。如連續幾次下需要send的數據都很少,通常TCP會根據優化算法把 這些數據合成一個TCP段後 一次發送出去,這樣接收方就收到了粘包數據

  1. TCP(transport control protocol,傳輸控制協議)是面向連接的,面向流的,提供高可靠性服務。收發兩端(客戶端和服務器端)都要有一一成對的socket,因此,發送端為了將多個發往接收端的包,更有效的發到對方,使用了優化方法(Nagle算法),將多次間隔較小且數據量小的數據,合並成一個大的數據塊,然後進行封包。這樣,接收端,就難於分辨出來了,必須提供科學的拆包機制。 即面向流的通信是無消息保護邊界的。
  2. UDP(user datagram protocol,用戶數據報協議)是無連接的,面向消息的,提供高效率服務。不會使用塊的合並優化算法,, 由於UDP支持的是一對多的模式,所以接收端的skbuff(套接字緩沖區)采用了鏈式結構來記錄每一個到達的UDP包,在每個UDP包中就有了消息頭(消息來源地址,端口等信息),這樣,對於接收端來說,就容易進行區分處理了。 即面向消息的通信是有消息保護邊界的。
  3. tcp是基於數據流的,於是收發的消息不能為空,這就需要在客戶端和服務端都添加空消息的處理機制,防止程序卡住,而udp是基於數據報的,即便是你輸入的是空內容(直接回車),那也不是空消息,udp協議會幫你封裝上消息頭,實驗略

    udp的recvfrom是阻塞的,一個recvfrom(x)必須對一個一個sendinto(y),收完了x個字節的數據就算完成,若是y>x數據就丟失,這意味著udp根本不會粘包,但是會丟數據,不可靠

    tcp的協議數據不會丟,沒有收完包,下次接收,會繼續上次繼續接收,己端總是在收到ack時才會清除緩沖區內容。數據是可靠的,但是會粘包。

    兩種情況下會發生粘包:

1.發送端需要等本機的緩沖區滿了以後才發送出去,造成粘包(發送數據時間間隔很端,數據很小,會合在一個起,產生粘包)

2.接收端不及時接收緩沖區的包,造成多個包接受(客戶端發送一段數據,服務端只收了一小部分,服務端下次再收的時候還是從緩沖區拿上次遺留的數據 ,就產生粘包)

粘包實例:

服務端
import socket
import subprocess
din=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ip_port=(127.0.0.1,8080)
din.bind(ip_port)
din.listen(5)
conn,deer=din.accept()
data1=conn.recv(1024)
data2=conn.recv(1024)
print(data1)
print(data2)

客戶端
import socket
import subprocess
din=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ip_port=(127.0.0.1,8080)
din.connect(ip_port)
din.send(helloworld.encode(utf-8))
din.send(sb.encode(utf-8))

low比的解決粘包的方法:

在客戶端發送下邊添加一個時間睡眠,就可以避免粘包現象。在服務端接收的時候也要進行時間睡眠,才能有效的避免粘包情況

#客戶端
import socket
import time
import subprocess
din=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ip_port=(127.0.0.1,8080)
din.connect(ip_port)
din.send(helloworld.encode(utf-8))
time.sleep(3)
din.send(sb.encode(utf-8))
#服務端
import socket
import time
import subprocess
din=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ip_port=(127.0.0.1,8080)
din.bind(ip_port)
din.listen(5)
conn,deer=din.accept()
data1=conn.recv(1024)
time.sleep(4)
data2=conn.recv(1024)
print(data1)
print(data2)

大聲解決粘包的方法:

為字節流加上自定義固定長度報頭,報頭中包含字節流長度,然後依次send到對端,對端在接受時,先從緩存中取出定長的報頭,然後再取真是數據。

使用struct模塊

該模塊可以把一個類型,如數字,轉成固定長度的bytes字節

struct模塊

該模塊可以把一個類型,如數字,轉成固定長度的bytes

>>> res=struct.pack(‘i‘,1111111111111) #打包成固定長度的bytes

>>> struct.unpack(“I”,res) #解包

技術分享

采用自定義報頭的形式:

#服務端
import socket      #導入模塊
import struct      #導入模塊
import subprocess  #導入模塊
din=socket.socket(socket.AF_INET,socket.SOCK_STREAM)   #基於網路家族以數據流的形式傳輸
ip=(127.0.0.1,8080)   #標識服務端唯一的地址
din.bind(ip)            #綁定地址
din.listen(5)           #設置鏈接緩沖池最大數量
while True:               
    conn,addr=din.accept()   #等待鏈接進入
    print(------>,addr)
    while True: 
        try:                 #開始捕捉異常
            cmd=conn.recv(1024)   #把接收的內容賦值給變量cmd
            res=subprocess.Popen(cmd.decode(utf-8),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)    #設置一個管道,以shell的方式寫入,判斷如果是正確的命令就放到標準輸出的管道中,否則就放到錯誤輸出的管道中
            dui=res.stdout.read()    #讀取標準輸出管道中的內容賦值給dui
            cuo=res.stderr.read()    #讀取錯誤輸出管道中的內容賦值給cuo
            data_bat=len(dui)+len(cuo)  #把dui和cuo的內容長度相加賦值給data_bat
            conn.send(struct.pack(i,data_bat))  #通過模塊模仿報頭形式發送給客戶端
            conn.send(dui)    #把dui的發送給客戶端
            conn.send(cuo)    #把cuo的發送給客戶端
        except Exception:     #結束捕捉異常
            break             #跳出
    conn.close()              #斷開鏈接
din.close()                   #關閉基於網絡家族傳輸的鏈接
#客戶端
import socket
import struct
din=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ip_port=(127.0.0.1,8080)
din.connect(ip_port)
while True:
    cmd=input(請輸入命令:).strip()
    if not cmd:continue
    din.send(bytes(cmd,encoding=utf-8))
    baotou=din.recv(4)
    data_size=struct.unpack(i,baotou)[0]
    rec_size=0
    rec_data=b‘‘
    while rec_size < data_size:
        data=din.recv(1024)
        rec_size+=len(data)
        rec_data+=data

    print(rec_data.decode(gbk))

din.close()

牛逼報頭的形式:

服務端
import
struct import socket import json import subprocess din=socket.socket(socket.AF_INET,socket.SOCK_STREAM) ip=(127.0.0.1,8080) din.bind(ip) din.listen(5) while True: conn,addr=din.accept() print(------->>>) while True: try: dr=conn.recv(1024) res=subprocess.Popen(dr.decode(utf-8),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) dui=res.stdout.read() cuo=res.stderr.read() data_lik=len(dui)+len(cuo) head_dic={data_lik:data_lik} head_json=json.dumps(head_dic) head_bytes=head_json.encode(utf-8) head_len=len(head_bytes) #發送報頭長度 conn.send(struct.pack(i,head_len)) # 發送報頭 conn.send(head_bytes) #發送真是數據 conn.send(dui) conn.send(cuo) except Exception: break conn.close() din.close()
客戶端
import
struct import socket import json din=socket.socket(socket.AF_INET,socket.SOCK_STREAM) ip=(127.0.0.1,8080) din.connect(ip) while True: run=input(請輸入命令:) if not run:continue din.send(bytes(run,encoding=utf-8)) data=din.recv(4) head_len=struct.unpack(i,data)[0] head_bytes=din.recv(head_len) head_json=head_bytes.decode(utf-8) head_dic=json.loads(head_json) data_lik=head_dic[data_lik] recv_size=0 recv_data=b‘‘ while recv_size < data_lik: data=din.recv(1024) recv_size+=len(data) recv_data+=data print(recv_data.decode(gbk)) din.close()

python/socket編程之粘包