亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

python TCP Socket的粘包和分包的處理詳解

 更新時(shí)間:2018年02月09日 16:52:04   作者:閼男秀  
這篇文章主要介紹了python TCP Socket的粘包和分包的處理詳解,分享了相關(guān)代碼示例,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下

概述

在進(jìn)行TCP Socket開(kāi)發(fā)時(shí),都需要處理數(shù)據(jù)包粘包和分包的情況。本文詳細(xì)講解解決該問(wèn)題的步驟。使用的語(yǔ)言是Python。實(shí)際上解決該問(wèn)題很簡(jiǎn)單,在應(yīng)用層下,定義一個(gè)協(xié)議:消息頭部+消息長(zhǎng)度+消息正文即可。

那什么是粘包和分包呢?

關(guān)于分包和粘包

粘包:發(fā)送方發(fā)送兩個(gè)字符串”hello”+”world”,接收方卻一次性接收到了”helloworld”。

分包:發(fā)送方發(fā)送字符串”helloworld”,接收方卻接收到了兩個(gè)字符串”hello”和”world”。

雖然socket環(huán)境有以上問(wèn)題,但是TCP傳輸數(shù)據(jù)能保證幾點(diǎn):

  • 順序不變。例如發(fā)送方發(fā)送hello,接收方也一定順序接收到hello,這個(gè)是TCP協(xié)議承諾的,因此這點(diǎn)成為我們解決分包、黏包問(wèn)題的關(guān)鍵。
  • 分割的包中間不會(huì)插入其他數(shù)據(jù)。

因此如果要使用socket通信,就一定要自己定義一份協(xié)議。目前最常用的協(xié)議標(biāo)準(zhǔn)是:消息頭部(包頭)+消息長(zhǎng)度+消息正文

TCP為什么會(huì)分包

TCP是以段(Segment)為單位發(fā)送數(shù)據(jù)的,建立TCP鏈接后,有一個(gè)最大消息長(zhǎng)度(MSS)。如果應(yīng)用層數(shù)據(jù)包超過(guò)MSS,就會(huì)把應(yīng)用層數(shù)據(jù)包拆分,分成兩個(gè)段來(lái)發(fā)送。這個(gè)時(shí)候接收端的應(yīng)用層就要拼接這兩個(gè)TCP包,才能正確處理數(shù)據(jù)。

相關(guān)的,路由器有一個(gè)MTU( 最大傳輸單元),一般是1500字節(jié),除去IP頭部20字節(jié),留給TCP的就只有MTU-20字節(jié)。所以一般TCP的MSS為MTU-20=1460字節(jié)。

當(dāng)應(yīng)用層數(shù)據(jù)超過(guò)1460字節(jié)時(shí),TCP會(huì)分多個(gè)數(shù)據(jù)包來(lái)發(fā)送。

擴(kuò)展閱讀

TCP的RFC定義MSS的默認(rèn)值是536,這是因?yàn)?RFC 791里說(shuō)了任何一個(gè)IP設(shè)備都得最少接收576尺寸的大小(實(shí)際上來(lái)說(shuō)576是撥號(hào)的網(wǎng)絡(luò)的MTU,而576減去IP頭的20個(gè)字節(jié)就是536)。
TCP為什么會(huì)粘包

有時(shí)候,TCP為了提高網(wǎng)絡(luò)的利用率,會(huì)使用一個(gè)叫做Nagle的算法。該算法是指,發(fā)送端即使有要發(fā)送的數(shù)據(jù),如果很少的話,會(huì)延遲發(fā)送。如果應(yīng)用層給TCP傳送數(shù)據(jù)很快的話,就會(huì)把兩個(gè)應(yīng)用層數(shù)據(jù)包“粘”在一起,TCP最后只發(fā)一個(gè)TCP數(shù)據(jù)包給接收端。

開(kāi)發(fā)環(huán)境

  • Python版本:3.5.1
  • 操作系統(tǒng):Windows 10 x64

消息頭部(包含消息長(zhǎng)度)

消息頭部不一定只能是一個(gè)字節(jié)比如0xAA什么的,也可以包含協(xié)議版本號(hào),指令等,當(dāng)然也可以把消息長(zhǎng)度合并到消息頭部里,唯一的要求是包頭長(zhǎng)度要固定的,包體則可變長(zhǎng)。下面是我自定義的一個(gè)包頭:

版本號(hào)(ver) 消息長(zhǎng)度(bodySize) 指令(cmd)

版本號(hào),消息長(zhǎng)度,指令數(shù)據(jù)類(lèi)型都是無(wú)符號(hào)32位整型變量,于是這個(gè)消息長(zhǎng)度固定為4×3=12字節(jié)。在Python由于沒(méi)有類(lèi)型定義,所以一般是使用struct模塊生成包頭。示例:

import struct
import json

ver = 1
body = json.dumps(dict(hello="world"))
print(body) # {"hello": "world"}
cmd = 101
header = [ver, body.__len__(), cmd]
headPack = struct.pack("!3I", *header)
print(headPack) # b'\x00\x00\x00\x01\x00\x00\x00\x12\x00\x00\x00e'

關(guān)于用自定義結(jié)束符分割數(shù)據(jù)包

有的人會(huì)想用自定義的結(jié)束符分割每一個(gè)數(shù)據(jù)包,這樣傳輸數(shù)據(jù)包時(shí)就不需要指定長(zhǎng)度甚至也不需要包頭了。但是如果這樣做,網(wǎng)絡(luò)傳輸性能損失非常大,因?yàn)槊恳蛔x取一個(gè)字節(jié)都要做一次if判斷是否是結(jié)束符。所以建議還是選擇消息頭部+消息長(zhǎng)度+消息正文這種方式。

而且,使用自定義結(jié)束符的時(shí)候,如果消息正文中出現(xiàn)這個(gè)符號(hào),就會(huì)把后面的數(shù)據(jù)截止,這個(gè)時(shí)候還需要處理符號(hào)轉(zhuǎn)義,類(lèi)比于\r\n的反斜杠。所以非常不建議使用結(jié)束符分割數(shù)據(jù)包。

消息正文

消息正文的數(shù)據(jù)格式可以使用Json格式,這里一般是用來(lái)存放獨(dú)特信息的數(shù)據(jù)。在下面代碼中,我使用{"hello","world"}數(shù)據(jù)來(lái)測(cè)試。在Python使用json模塊來(lái)生成json數(shù)據(jù)

Python示例

下面使用Python代碼展示如何處理TCP Socket的粘包和分包。核心在于用一個(gè)FIFO隊(duì)列接收緩沖區(qū)dataBuffer和一個(gè)小while循環(huán)來(lái)判斷。

具體流程是這樣的:把從socket讀取出來(lái)的數(shù)據(jù)放到dataBuffer后面(入隊(duì)),然后進(jìn)入小循環(huán),如果dataBuffer內(nèi)容長(zhǎng)度小于消息長(zhǎng)度(bodySize),則跳出小循環(huán)繼續(xù)接收;大于消息長(zhǎng)度,則從緩沖區(qū)讀取包頭并獲取包體的長(zhǎng)度,再判斷整個(gè)緩沖區(qū)是否大于消息頭部+消息長(zhǎng)度,如果小于則跳出小循環(huán)繼續(xù)接收,如果大于則讀取包體的內(nèi)容,然后處理數(shù)據(jù),最后再把這次的消息頭部和消息正文從dataBuffer刪掉(出隊(duì))。

下面用Markdown畫(huà)了一個(gè)流程圖。

服務(wù)器端代碼

# Python Version:3.5.1
import socket
import struct

HOST = ''
PORT = 1234

dataBuffer = bytes()
headerSize = 12

sn = 0
def dataHandle(headPack, body):
  global sn
  sn += 1
  print("第%s個(gè)數(shù)據(jù)包" % sn)
  print("ver:%s, bodySize:%s, cmd:%s" % headPack)
  print(body.decode())
  print("")

if __name__ == '__main__':
  with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen(1)
    conn, addr = s.accept()
    with conn:
      print('Connected by', addr)
      while True:
        data = conn.recv(1024)
        if data:
          # 把數(shù)據(jù)存入緩沖區(qū),類(lèi)似于push數(shù)據(jù)
          dataBuffer += data
          while True:
            if len(dataBuffer) < headerSize:
              print("數(shù)據(jù)包(%s Byte)小于消息頭部長(zhǎng)度,跳出小循環(huán)" % len(dataBuffer))
              break

            # 讀取包頭
            # struct中:!代表Network order,3I代表3個(gè)unsigned int數(shù)據(jù)
            headPack = struct.unpack('!3I', dataBuffer[:headerSize])
            bodySize = headPack[1]

            # 分包情況處理,跳出函數(shù)繼續(xù)接收數(shù)據(jù)
            if len(dataBuffer) < headerSize+bodySize :
              print("數(shù)據(jù)包(%s Byte)不完整(總共%s Byte),跳出小循環(huán)" % (len(dataBuffer), headerSize+bodySize))
              break
            # 讀取消息正文的內(nèi)容
            body = dataBuffer[headerSize:headerSize+bodySize]

            # 數(shù)據(jù)處理
            dataHandle(headPack, body)

            # 粘包情況的處理
            dataBuffer = dataBuffer[headerSize+bodySize:] # 獲取下一個(gè)數(shù)據(jù)包,類(lèi)似于把數(shù)據(jù)pop出

測(cè)試服務(wù)器端的客戶(hù)端代碼

下面附上測(cè)試粘包和分包的客戶(hù)端代碼

# Python Version:3.5.1
import socket
import time
import struct
import json

host = "localhost"
port = 1234

ADDR = (host, port)

if __name__ == '__main__':
  client = socket.socket()
  client.connect(ADDR)

  # 正常數(shù)據(jù)包定義
  ver = 1
  body = json.dumps(dict(hello="world"))
  print(body)
  cmd = 101
  header = [ver, body.__len__(), cmd]
  headPack = struct.pack("!3I", *header)
  sendData1 = headPack+body.encode()

  # 分包數(shù)據(jù)定義
  ver = 2
  body = json.dumps(dict(hello="world2"))
  print(body)
  cmd = 102
  header = [ver, body.__len__(), cmd]
  headPack = struct.pack("!3I", *header)
  sendData2_1 = headPack+body[:2].encode()
  sendData2_2 = body[2:].encode()

  # 粘包數(shù)據(jù)定義
  ver = 3
  body1 = json.dumps(dict(hello="world3"))
  print(body1)
  cmd = 103
  header = [ver, body1.__len__(), cmd]
  headPack1 = struct.pack("!3I", *header)

  ver = 4
  body2 = json.dumps(dict(hello="world4"))
  print(body2)
  cmd = 104
  header = [ver, body2.__len__(), cmd]
  headPack2 = struct.pack("!3I", *header)

  sendData3 = headPack1+body1.encode()+headPack2+body2.encode()


  # 正常數(shù)據(jù)包
  client.send(sendData1)
  time.sleep(3)

  # 分包測(cè)試
  client.send(sendData2_1)
  time.sleep(0.2)
  client.send(sendData2_2)
  time.sleep(3)

  # 粘包測(cè)試
  client.send(sendData3)
  time.sleep(3)
  client.close()

服務(wù)器端打印結(jié)果

下面是測(cè)試出來(lái)的打印結(jié)果,可見(jiàn)接收方已經(jīng)完美的處理粘包和分包問(wèn)題了。

Connected by ('127.0.0.1', 23297)
第1個(gè)數(shù)據(jù)包
ver:1, bodySize:18, cmd:101
{"hello": "world"}

數(shù)據(jù)包(0 Byte)小于包頭長(zhǎng)度,跳出小循環(huán)
數(shù)據(jù)包(14 Byte)不完整(總共31 Byte),跳出小循環(huán)
第2個(gè)數(shù)據(jù)包
ver:2, bodySize:19, cmd:102
{"hello": "world2"}

數(shù)據(jù)包(0 Byte)小于包頭長(zhǎng)度,跳出小循環(huán)
第3個(gè)數(shù)據(jù)包
ver:3, bodySize:19, cmd:103
{"hello": "world3"}

第4個(gè)數(shù)據(jù)包
ver:4, bodySize:19, cmd:104
{"hello": "world4"}

在框架下處理粘包和分包

其實(shí)無(wú)論是使用阻塞還是異步socket開(kāi)發(fā)框架,框架本身都會(huì)提供一個(gè)接收數(shù)據(jù)的方法提供給開(kāi)發(fā)者,一般來(lái)說(shuō)開(kāi)發(fā)者都要覆寫(xiě)這個(gè)方法。下面是在Twidted開(kāi)發(fā)框架處理粘包和分包的示例,只上核心程序:

# Twiested
class MyProtocol(Protocol):
  _data_buffer = bytes()

  # 代碼省略

  def dataReceived(self, data):
    """Called whenever data is received."""
    self._data_buffer += data
    headerSize = 12

    while True:
      if len(self._data_buffer) < headerSize:
        return

      # 讀取消息頭部
      # struct中:!代表Network order,3I代表3個(gè)unsigned int數(shù)據(jù)
      headPack = struct.unpack('!3I', self._data_buffer[:headerSize])
      # 獲取消息正文長(zhǎng)度
      bodySize = headPack[1]

      # 分包情況處理
      if len(self._data_buffer) < headerSize+bodySize :
        return

      # 讀取消息正文的內(nèi)容
      body = self._data_buffer[headerSize:headerSize+bodySize]
      # 處理數(shù)據(jù)
      self.dataHandle(headPack, body)
      # 粘包情況的處理
      self._data_buffer = self._data_buffer[headerSize+bodySize:]

總結(jié)

以上就是本文關(guān)于python TCP Socket的粘包和分包的處理詳解的全部?jī)?nèi)容,希望對(duì)大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關(guān)專(zhuān)題,如有不足之處,歡迎留言指出。感謝朋友們對(duì)本站的支持!

相關(guān)文章

  • Python 匹配任意字符(包括換行符)的正則表達(dá)式寫(xiě)法

    Python 匹配任意字符(包括換行符)的正則表達(dá)式寫(xiě)法

    Python 正則表達(dá)式匹配任意字符(包括換行符)的寫(xiě)法
    2009-10-10
  • 利用Python腳本寫(xiě)端口掃描器socket,python-nmap

    利用Python腳本寫(xiě)端口掃描器socket,python-nmap

    這篇文章主要介紹了利用Python腳本寫(xiě)端口掃描器socket,python-nmap,文章圍繞主題展開(kāi)詳細(xì)介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-07-07
  • Pandas sample隨機(jī)抽樣的實(shí)現(xiàn)

    Pandas sample隨機(jī)抽樣的實(shí)現(xiàn)

    隨機(jī)抽樣,是統(tǒng)計(jì)學(xué)中常用的一種方法,本文主要介紹了Pandas sample隨機(jī)抽樣的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-06-06
  • Python腳本調(diào)試工具安裝過(guò)程

    Python腳本調(diào)試工具安裝過(guò)程

    這篇文章主要介紹了Python腳本調(diào)試工具非常好用,本文給大家介紹pycharm工具的安裝過(guò)程及使用詳解,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-01-01
  • Jupyter Notebook 遠(yuǎn)程訪問(wèn)配置詳解

    Jupyter Notebook 遠(yuǎn)程訪問(wèn)配置詳解

    這篇文章主要介紹了Jupyter Notebook 遠(yuǎn)程訪問(wèn)配置詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • jupyter notebook快速入門(mén)及使用詳解

    jupyter notebook快速入門(mén)及使用詳解

    這篇文章主要介紹了jupyter notebook快速入門(mén)及使用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • 基于python實(shí)現(xiàn)上傳文件到OSS代碼實(shí)例

    基于python實(shí)現(xiàn)上傳文件到OSS代碼實(shí)例

    這篇文章主要介紹了基于python實(shí)現(xiàn)上傳文件到OSS,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-05-05
  • 查看Python安裝路徑幾種方法

    查看Python安裝路徑幾種方法

    在使用python的時(shí)候,有時(shí)候會(huì)需要找到python包的安裝位置,本文主要介紹了查看Python安裝路徑幾種方法,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-09-09
  • python使用openpyxl實(shí)現(xiàn)對(duì)excel表格相對(duì)路徑的超鏈接的創(chuàng)建方式

    python使用openpyxl實(shí)現(xiàn)對(duì)excel表格相對(duì)路徑的超鏈接的創(chuàng)建方式

    這篇文章主要介紹了python使用openpyxl實(shí)現(xiàn)對(duì)excel表格相對(duì)路徑的超鏈接的創(chuàng)建方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • opencv+playwright滑動(dòng)驗(yàn)證碼的實(shí)現(xiàn)

    opencv+playwright滑動(dòng)驗(yàn)證碼的實(shí)現(xiàn)

    滑動(dòng)驗(yàn)證碼是常見(jiàn)的驗(yàn)證碼之一,本文主要介紹了opencv+playwright滑動(dòng)驗(yàn)證碼的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-11-11

最新評(píng)論