粘包問題

粘包問題

1、什麼是粘包

注意:只有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

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

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

TCP的協議數據不會丟,沒有收完包,下次接收,會繼續上次繼續接收,己端老是在收到ack時纔會清除緩衝區內容。數據是可靠的,可是會粘包。優化

2、TCP發送數據的四種狀況

假設客戶端分別發送了兩個數據包D1和D2給服務端,因爲服務端一次讀取到的字節數是不肯定的,故可能存在如下4種狀況。編碼

  1. 服務端分兩次讀取到了兩個獨立的數據包,分別是D1和D2,沒有粘包和拆包;
  2. 服務端一次接收到了兩個數據包,D1和D2粘合在一塊兒,被稱爲TCP粘包;
  3. 服務端分兩次讀取到了兩個數據包,第一次讀取到了完整的D1包和D2包的部份內容,第二次讀取到了D2包的剩餘內容,這被稱爲TCP拆包;
  4. 服務端分兩次讀取到了兩個數據包,第一次讀取到了D1包的部份內容D1_1,第二次讀取到了D1包的剩餘內容D1_2和D2包的整包。

特例:若是此時服務端TCP接收滑窗很是小,而數據包D1和D2比較大,頗有可能會發生第五種可能,即服務端分屢次才能將D1和D2包接收徹底,期間發生屢次拆包。

3、粘包

  1. 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()
  2. 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')

4、解決粘包

  1. 補充模塊

    import struct
    #把一個數字打包成固定長度的4字節
    obj=struct.pack('i',1098)
    print(obj)
    print(len(obj))
    
    l=struct.unpack('i',obj)[0]
    print(l)
  2. 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()
  3. 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'))

    5、解決粘包問題的最終方案

    1. 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']
    2. 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()
    3. 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'))
相關文章
相關標籤/搜索