粘包現象與解決辦法

本節導讀shell

  • 什麼是粘包現象
  • 發生粘包的兩種狀況
  • 解決粘包現象的辦法                  

 

一 什麼是粘包現象json

須知:只有TCP有粘包現象,UDP永遠不會粘包服務器

粘包不必定會發生,若是發生了:1.多是在客戶端已經粘了,2.客戶端沒有粘,多是在服務端粘了socket

粘包現象:TCP粘包是指發送方發送的若干包數據到接收方接收時粘成一包,從接收緩衝區看,後一包數據的頭緊接着前一包數據的尾。ide

成因:所謂粘包問題主要仍是由於接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所形成的,應用程序所看到的數據是一個總體,或說是一個流(stream),一條消息有多少字節對應用程序是不可見的,所以TCP協議是面向流的協議,這也是容易出現粘包問題的緣由。(由於TCP是流式協議,不知道啥時候開始,啥時候結束)。而UDP是面向消息的協議,每一個UDP段都是一條消息,應用程序必須以消息爲單位提取數據,不能一次提取任意字節的數據,這一點和TCP是很不一樣的。怎樣定義消息呢?能夠認爲對方一次性write/send的數據爲一個消息,須要明白的是當對方send一條信息的時候,不管底層怎樣分段分片,TCP協議層會把構成整條消息的數據段排序完成後才呈如今內核緩衝區。spa

 

二 發生粘包的兩種狀況code

  •  發送端須要等緩衝區滿才發送出去,形成粘包
    (發送數據時間間隔很短,數據了很小,會當作一個包發出去,產生粘包)
    from socket import *
    phone = socket(AF_INET,SOCK_STREAM)
    phone.setsockopt(SOL_SOCKET,SOCK_STREAM,1)
    phone.bind(('127.0.0.1',8080))
    phone.listen(5)
    print('start running...')
    
    coon,addr = phone.accept() #等待鏈接
    
    data1 = coon.recv(10)
    data2 = coon.recv(10)
    
    print('------------>',data1.decode('utf-8'))
    print('------------>',data2.decode('utf-8'))
    coon.close()
    phone.close()
    服務端
    from socket import *
    import time
    phone = socket(AF_INET,SOCK_STREAM)
    phone.connect(('127.0.0.1',8080))
    
    phone.send('hello'.encode('utf-8'))
    phone.send('helloworld'.encode('utf-8'))
    phone.close()
    客戶端
  • 接收方不及時接收緩衝區的包,形成多個包接收
    (客戶端發送了一段數據,服務端只收了一小部分,服務端下次再收的時候仍是從緩衝區拿上次遺留的數據,產生粘包)
    from socket import *
    phone = socket(AF_INET,SOCK_STREAM)
    phone.setsockopt(SOL_SOCKET,SOCK_STREAM,1)
    phone.bind(('127.0.0.1',8080))
    phone.listen(5)
    print('start running...')
    
    coon,addr = phone.accept() #等待鏈接
    
    data1 = coon.recv(2) #一次沒有接收完整
    data2 = coon.recv(10)  #下一次接收的時候會先取舊的數據,而後取新的
    # data3 = coon.recv(1024)  #接收等5秒後的信息
    print('------------>',data1.decode('utf-8'))
    print('------------>',data2.decode('utf-8'))
    # print('------------>',data3.decode('utf-8'))
    coon.close()
    phone.close()
    服務端
    from socket import *
    import time
    phone = socket(AF_INET,SOCK_STREAM)
    phone.connect(('127.0.0.1',8080))
    
    phone.send('hello'.encode('utf-8'))
    time.sleep(5)
    phone.send('haiyan'.encode('utf-8'))
    phone.close()
    客戶端

 

三 解決粘包現象的辦法        blog

  • 發送數據前先發送該數據的大小

     粘包的問題的根源在於,接收端不知道發送端將要傳送的字節流的長度,因此解決粘包的方法就是圍繞,如何讓發送端在發送數據前,把本身將要發送的字節流總大小讓接收端知曉,而後接收端來一個死循環接收完全部數據排序

    import socket
    import subprocess
    import struct
    phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #買手機
    phone.bind(('127.0.0.1',8080)) #綁定手機卡
    phone.listen(5) #阻塞的最大數
    print('start runing.....')
    while True: #連接循環
        coon,addr = phone.accept()# 等待接電話
        print(coon,addr)
        while True: #通訊循環
            # 收發消息
            cmd = coon.recv(1024) #接收的最大數
            print('接收的是:%s'%cmd.decode('utf-8'))
            #處理過程
            res = subprocess.Popen(cmd.decode('utf-8'),shell = True,
                                              stdout=subprocess.PIPE, #標準輸出
                                              stderr=subprocess.PIPE #標準錯誤
                                    )
            stdout = res.stdout.read()
            stderr = res.stderr.read()
            #先發報頭(轉成固定長度的bytes類型,那麼怎麼轉呢?就用到了struct模塊)
            #len(stdout) + len(stderr)#統計數據的長度
            header = struct.pack('i',len(stdout)+len(stderr))#製做報頭
            coon.send(header)
            #再發命令的結果
            coon.send(stdout)
            coon.send(stderr)
        coon.close()
    phone.close()
    服務端
    import socket
    import struct
    phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    phone.connect(('127.0.0.1',8080)) #鏈接服
    while True:
        # 發收消息
        cmd = input('請你輸入命令>>:').strip()
        if not cmd:continue
        phone.send(cmd.encode('utf-8')) #發送
        #先收報頭
        header_struct = phone.recv(4) #收四個
        unpack_res = struct.unpack('i',header_struct)
        total_size = unpack_res[0]  #總長度
        #後收數據
        recv_size = 0
        total_data=b''
        while recv_size<total_size: #循環的收
            recv_data = phone.recv(1024) #1024只是一個最大的限制
            recv_size+=len(recv_data) #
            total_data+=recv_data #
        print('返回的消息:%s'%total_data.decode('gbk'))
    phone.close()
    客戶端
  • 發送數據前先發送經過struct處理後的固定字節的包含報頭大小的數據
    具體流程爲:
    1 先收報頭的長度
    2 再收報頭
    3 分析報頭獲取數據長度
    4 接收真實數據
    import socket
    import subprocess
    import struct
    import json
    phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #買手機
    phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    phone.bind(('127.0.0.1',8080)) #綁定手機卡
    phone.listen(5) #阻塞的最大數
    print('start runing.....')
    while True: #連接循環
        coon,addr = phone.accept()# 等待接電話
        print(coon,addr)
        while True: #通訊循環
            # 收發消息
            cmd = coon.recv(1024) #接收的最大數
            print('接收的是:%s'%cmd.decode('utf-8'))
            #處理過程
            res = subprocess.Popen(cmd.decode('utf-8'),shell = True,
                                              stdout=subprocess.PIPE, #標準輸出
                                              stderr=subprocess.PIPE #標準錯誤
                                    )
            stdout = res.stdout.read()
            stderr = res.stderr.read()
            # 製做報頭
            header_dic = {
                'total_size': len(stdout)+len(stderr),  # 總共的大小
                'filename': None,
                'md5': None
            }
            header_json = json.dumps(header_dic) #字符串類型
            header_bytes = header_json.encode('utf-8')  #轉成bytes類型(可是長度是可變的)
            #先發報頭的長度
            coon.send(struct.pack('i',len(header_bytes))) #發送固定長度的報頭
            #再發報頭
            coon.send(header_bytes)
            #最後發命令的結果
            coon.send(stdout)
            coon.send(stderr)
        coon.close()
    phone.close()
    服務端
    import socket
    import struct
    import json
    phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    phone.connect(('127.0.0.1',8080)) #鏈接服務器
    while True:
        # 發收消息
        cmd = input('請你輸入命令>>:').strip()
        if not cmd:continue
        phone.send(cmd.encode('utf-8')) #發送
        #先收報頭的長度
        header_len = struct.unpack('i',phone.recv(4))[0]  #吧bytes類型的反解
        #在收報頭
        header_bytes = phone.recv(header_len) #收過來的也是bytes類型
        header_json = header_bytes.decode('utf-8')   #拿到json格式的字典
        header_dic = json.loads(header_json)  #反序列化拿到字典了
        total_size = header_dic['total_size']  #就拿到數據的總長度了
        #最後收數據
        recv_size = 0
        total_data=b''
        while recv_size<total_size: #循環的收
            recv_data = phone.recv(1024) #1024只是一個最大的限制
            recv_size+=len(recv_data) #有可能接收的不是1024個字節,或許比1024多呢,
            # 那麼接收的時候就接收不全,因此還要加上接收的那個長度
            total_data+=recv_data #最終的結果
        print('返回的消息:%s'%total_data.decode('gbk'))
    phone.close()
    客戶端

    struct 模塊ip

    #該模塊能夠把一個類型,如數字,轉成固定長度的bytes類型
    import struct
    res = struct.pack('i',12345)   # i 表示4位,l 表示8位
    print(res,len(res),type(res))  #長度是4
    
    res2 = struct.pack('i',12345111)
    print(res,len(res),type(res2))  #長度也是4
    
    unpack_res =struct.unpack('i',res2)
    print(unpack_res)  #(12345111,)
    print(unpack_res[0]) #12345111
    struct 
相關文章
相關標籤/搜索