欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解python中TCP協(xié)議中的粘包問(wèn)題

 更新時(shí)間:2019年03月22日 17:24:58   作者:小錦毛  
這篇文章主要介紹了python中TCP協(xié)議中的粘包問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

TCP協(xié)議中的粘包問(wèn)題

1.粘包現(xiàn)象

基于TCP實(shí)現(xiàn)一個(gè)簡(jiǎn)易遠(yuǎn)程cmd功能

#服務(wù)端
import socket
import subprocess
sever = socket.socket()
sever.bind(('127.0.0.1', 33521))
sever.listen()
while True:
 client, address = sever.accept()
 while True:
  try:
   cmd = client.recv(1024).decode('utf-8')
   p1 = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr= subprocess.PIPE)
   data = p1.stdout.read()
   err_data = p1.stderr.read()
   client.send(data)
   client.send(err_data)
  except ConnectionResetError:
   print('connect broken')
   client.close()
   break
sever.close()
​
​
​
#客戶端
import socket
client = socket.socket()
client.connect(('127.0.0.1', 33521))
while True:
 cmd = input('請(qǐng)輸入指令(Q\q退出)>>:').strip().lower()
 if cmd == 'q':
  break
 client.send(cmd.encode('utf-8'))
 data = client.recv(1024)
 print(data.decode('gbk'))
client.close()

上述是基于TCP協(xié)議的遠(yuǎn)程cmd簡(jiǎn)單功能,在運(yùn)行時(shí)會(huì)發(fā)生粘包。

2、什么是粘包?

只有TCP會(huì)發(fā)生粘包現(xiàn)象,UDP協(xié)議永遠(yuǎn)不會(huì)發(fā)生粘包;

TCP:(transport control protocol,傳輸控制協(xié)議)流式協(xié)議。在socket中TCP協(xié)議是按照字節(jié)數(shù)進(jìn)行數(shù)據(jù)的收發(fā),數(shù)據(jù)的發(fā)送方發(fā)出的數(shù)據(jù)往往接收方不知道數(shù)據(jù)到底長(zhǎng)度是多長(zhǎng),而TCP協(xié)議由于本身為了提高傳輸?shù)男?,發(fā)送方往往需要收集到足夠的數(shù)據(jù)才會(huì)進(jìn)行發(fā)送。使用了優(yōu)化方法(Nagle算法),將多次間隔較小且數(shù)據(jù)量小的數(shù)據(jù),合并成一個(gè)大的數(shù)據(jù)塊,然后進(jìn)行封包。這樣,接收端,就難于分辨出來(lái)了,必須提供科學(xué)的拆包機(jī)制。 即面向流的通信是無(wú)消息保護(hù)邊界的。

UDP:(user datagram protocol,用戶數(shù)據(jù)報(bào)協(xié)議)數(shù)據(jù)報(bào)協(xié)議。在socket中udp協(xié)議收發(fā)數(shù)據(jù)是以數(shù)據(jù)報(bào)為單位,服務(wù)端和客戶端收發(fā)數(shù)據(jù)是以一個(gè)單位,所以不會(huì)使用塊的合并優(yōu)化算法,, 由于UDP支持的是一對(duì)多的模式,所以接收端的skbuff(套接字緩沖區(qū))采用了鏈?zhǔn)浇Y(jié)構(gòu)來(lái)記錄每一個(gè)到達(dá)的UDP包,在每個(gè)UDP包中就有了消息頭(消息來(lái)源地址,端口等信息),這樣,對(duì)于接收端來(lái)說(shuō),就容易進(jìn)行區(qū)分處理了。 即面向消息的通信是有消息保護(hù)邊界的。

TCP協(xié)議不會(huì)丟失數(shù)據(jù),UDP協(xié)議會(huì)丟失數(shù)據(jù)。

udp的recvfrom是阻塞的,一個(gè)recvfrom(x)必須對(duì)唯一一個(gè)sendinto(y),收完了x個(gè)字節(jié)的數(shù)據(jù)就算完成,若是y>x數(shù)據(jù)就丟失,這意味著udp根本不會(huì)粘包,但是會(huì)丟數(shù)據(jù),不可靠。

tcp的協(xié)議數(shù)據(jù)不會(huì)丟,沒(méi)有收完包,下次接收,會(huì)繼續(xù)上次繼續(xù)接收,己端總是在收到ack時(shí)才會(huì)清除緩沖區(qū)內(nèi)容。數(shù)據(jù)是可靠的,但是會(huì)粘包。

3、什么情況下會(huì)發(fā)生粘包?

1.由于TCP協(xié)議的優(yōu)化算法,當(dāng)單個(gè)數(shù)據(jù)包較小的時(shí)候,會(huì)等到緩沖區(qū)滿才會(huì)發(fā)生數(shù)據(jù)包前后數(shù)據(jù)疊加在一起的情況。然后取的時(shí)候就分不清了到底是哪段數(shù)據(jù),這是第一種粘包。

2.當(dāng)發(fā)送的單個(gè)數(shù)據(jù)包較大超過(guò)緩沖區(qū)時(shí),收數(shù)據(jù)方一次就只能取一部分的數(shù)據(jù),下次再收數(shù)據(jù)方再收數(shù)據(jù)將會(huì)延續(xù)上次為接收數(shù)據(jù)。這是第二種粘包。

粘包的本質(zhì)問(wèn)題就是接收方不知道發(fā)送數(shù)據(jù)方一次到底發(fā)送了多少數(shù)據(jù),解決問(wèn)題的方向也是從控制數(shù)據(jù)長(zhǎng)度著手,也就是如何設(shè)置緩沖區(qū)的問(wèn)題

4、如何解決粘包問(wèn)題?

解決問(wèn)題思路:上述已經(jīng)明確粘包的產(chǎn)生是因?yàn)榻邮諗?shù)據(jù)時(shí)不知道數(shù)據(jù)的具體長(zhǎng)度。所以我們應(yīng)該先發(fā)送一段數(shù)據(jù)表明我們發(fā)送的數(shù)據(jù)長(zhǎng)度,那么就不會(huì)產(chǎn)生數(shù)據(jù)沒(méi)有發(fā)送或者沒(méi)有收取完全的情況。

1.struct 模塊(結(jié)構(gòu)體)

struct模塊的功能可以將python中的數(shù)據(jù)類型轉(zhuǎn)換成C語(yǔ)言中的結(jié)構(gòu)體(bytes類型)

import struct
s = 123456789
res = struct.pack('i', s)
print(res)
​
res2 = struct.unpack('i', res)
print(res2)
print(res2[0])

2.粘包的解決方案基本版

既然我們拿到了一個(gè)可以固定長(zhǎng)度的辦法,那么應(yīng)用struct模塊,可以固定長(zhǎng)度了。

為字節(jié)流加上自定義固定長(zhǎng)度報(bào)頭,報(bào)頭中包含字節(jié)流長(zhǎng)度,然后一次send到對(duì)端,對(duì)端在接收時(shí),先從緩存中取出定長(zhǎng)的報(bào)頭,然后再取真實(shí)數(shù)據(jù)

#服務(wù)器端
import socket
import subprocess
import struct
sever = socket.socket()
sever.bind(('127.0.0.1', 33520))
sever.listen()
while True:
 client, address = sever.accept()
 while True:
  try:
   cmd = client.recv(1024).decode('utf-8')
   #利用子進(jìn)程模塊啟動(dòng)程序
   p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
   #管道輸出的信息有正確和錯(cuò)誤的
   data = p.stdout.read()
   err_data = p.stderr.read()
   #先將數(shù)據(jù)的長(zhǎng)度發(fā)送給客戶端
   length = len(data)+len(err_data)
   #利用struct模塊將數(shù)據(jù)的長(zhǎng)度信息轉(zhuǎn)化成固定的字節(jié)
   len_data = struct.pack('i', length)
   #以下將信息傳輸給客戶端
   #1.數(shù)據(jù)的長(zhǎng)度
   client.send(len_data)
   #2.正確的數(shù)據(jù)
   client.send(data)
   #2.錯(cuò)誤管道的數(shù)據(jù)
   client.send(err_data)
  except Exception as e:
   client.close()
   print('連接中斷。。。。')
   break
   
​
#客戶端   
import socket
import struct
​
client = socket.socket()
client.connect(('127.0.0.1', 33520))
while True:
 cmd = input('請(qǐng)輸入指令>>:').strip().encode('utf-8')
 client.send(cmd)
 #1.先接收傳過(guò)來(lái)數(shù)據(jù)的長(zhǎng)度是多少,我們通過(guò)struct模塊固定了字節(jié)長(zhǎng)度為4
 length = client.recv(4)
 #將struct的字節(jié)再轉(zhuǎn)回去整型數(shù)字
 len_data = struct.unpack('i', length)
 print(len_data)
 len_data = len_data[0]
 print('數(shù)據(jù)長(zhǎng)度為%s:' % len_data)
​
 all_data = b''
 recv_size = 0
 #2.接收真實(shí)的數(shù)據(jù)
 #循環(huán)接收直到接收到數(shù)據(jù)的長(zhǎng)度等于數(shù)據(jù)的真實(shí)長(zhǎng)度(總長(zhǎng)度)
 while recv_size < len_data:
  data = client.recv(1024)
  recv_size += len(data)
  all_data += data
​
 print('接收長(zhǎng)度%s' % recv_size)
 print(all_data.decode('gbk'))

#總結(jié):

服務(wù)器端:

  1. 1.在服務(wù)器端先收到命令,打開(kāi)子進(jìn)程,然后計(jì)算返回的數(shù)據(jù)的長(zhǎng)度
  2. 2.先利用struct模塊將數(shù)據(jù)長(zhǎng)度轉(zhuǎn)成固定4個(gè)字節(jié)傳給客戶端
  3. 3.再向客戶端發(fā)送真實(shí)的數(shù)據(jù)。

 客戶端(兩次接收):

  1. 1.第一次只接受4個(gè)字節(jié),因?yàn)殚L(zhǎng)度數(shù)據(jù)就是4個(gè)字節(jié)。這樣防止了數(shù)據(jù)粘包。解碼得到長(zhǎng)度數(shù)據(jù)
  2. 2.第二次循環(huán)接收真實(shí)數(shù)據(jù),拼接真實(shí)數(shù)據(jù)完成解碼讀取數(shù)據(jù)。

很顯然,如果僅僅只是這樣肯定無(wú)法滿足在實(shí)際生產(chǎn)中一些需求。那么該怎么修改?

我們可以把報(bào)頭做成字典,字典里包含將要發(fā)送的真實(shí)數(shù)據(jù)的詳細(xì)信息,然后json序列化,然后用struck將序列化后的數(shù)據(jù)長(zhǎng)度打包成4個(gè)字節(jié)(4個(gè)字節(jié)足夠用了)

我們可以將自定義的報(bào)頭設(shè)置成這種這種格式。

發(fā)送時(shí):

1先發(fā)報(bào)頭長(zhǎng)度

2再編碼報(bào)頭內(nèi)容然后發(fā)送

3最后發(fā)真實(shí)內(nèi)容

接收時(shí):

1先收?qǐng)?bào)頭長(zhǎng)度,用struct取出來(lái)

2根據(jù)取出的長(zhǎng)度收取報(bào)頭內(nèi)容,然后解碼,反序列化

3從反序列化的結(jié)果中取出待取數(shù)據(jù)的詳細(xì)信息,然后去取真實(shí)的數(shù)據(jù)內(nèi)容

#服務(wù)器端
import socket
import subprocess
import datetime
import json
import struct
sever = socket.socket()
sever.bind(('127.0.0.1', 33520))
sever.listen()
while True:
 client, address = sever.accept()
 while True:
  try:
   cmd = client.recv(1024).decode('utf-8')
   #啟動(dòng)子進(jìn)程
   p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
   #得到子進(jìn)程運(yùn)行的數(shù)據(jù)
   data = p.stdout.read() #子進(jìn)程運(yùn)行正確的輸出管道數(shù)據(jù),數(shù)據(jù)讀出來(lái)后是字節(jié)
   err_data = p.stderr.read() #子進(jìn)程運(yùn)行錯(cuò)誤的輸出管道數(shù)據(jù)
   #計(jì)算數(shù)據(jù)的總長(zhǎng)度
   length = len(data) + len(err_data)
   print('數(shù)據(jù)總長(zhǎng)度:%s' % length)
​
   #先需要發(fā)送報(bào)頭信息,以下為創(chuàng)建報(bào)頭信息(至第一次發(fā)送)
​
​
   #需要添加時(shí)間信息
   time_info = datetime.datetime.now()
   #設(shè)置一個(gè)字典將一些額外的信息和長(zhǎng)度信息放進(jìn)去然后json序列化,報(bào)頭字典
   masthead = {}
   #將時(shí)間數(shù)據(jù)放入報(bào)頭字典中
   masthead['time'] = str(time_info) #時(shí)間格式不能被json序列化,所以將其轉(zhuǎn)化為字符串形式
   masthead['length'] = length
​
   #將報(bào)頭字典json序列化
   json_masthead = json.dumps(masthead)   #得到j(luò)son格式的報(bào)頭
   # 將json格式的報(bào)頭編碼成字節(jié)形式
   masthead_data = json_masthead.encode('utf-8')
   #利用struct將報(bào)頭編碼的字節(jié)的長(zhǎng)度轉(zhuǎn)成固定的字節(jié)(4個(gè)字節(jié))
   masthead_length = struct.pack('i', len(masthead_data))
​
​
   #1.發(fā)送報(bào)頭的長(zhǎng)度(第一次發(fā)送)
   client.send(masthead_length)
   #2.發(fā)送報(bào)頭信息(第二次發(fā)送)
   client.send(masthead_data)
   #3.發(fā)送真實(shí)數(shù)據(jù)(第三次發(fā)送)
   client.send(data)
   client.send(err_data)
  except ConnectionResetError:
   print('客戶端斷開(kāi)連接。。。')
   client.close()
   break
   
   
   
#客戶端
import socket
import struct
import json
client = socket.socket()
client.connect(('127.0.0.1', 33520))
while True:
 cmd = input('請(qǐng)輸入cmd指令(Q\q退出)>>:').strip()
 if cmd == 'q':
  break
​
 #發(fā)送CMD指令至服務(wù)器
 client.send(cmd.encode('utf-8'))
​
​
 #1.第一次接收,接收?qǐng)?bào)頭信息的長(zhǎng)度,由于struct模塊固定長(zhǎng)度為4字節(jié),括號(hào)內(nèi)直接填4
 len_masthead = client.recv(4)
 #利用struct反解報(bào)頭長(zhǎng)度,由于是元組形式,取值得到整型數(shù)字masthead_length
 masthead_length = struct.unpack('i', len_masthead)[0]
​
​
 #2.第二次接收,接收?qǐng)?bào)頭信息,接收長(zhǎng)度為報(bào)頭長(zhǎng)度masthead_length 被編碼成字節(jié)形式的json格式的字典,
 # 解字符編碼得到j(luò)son格式的字典masthead_data
 masthead_data = client.recv(masthead_length).decode('utf-8')
 #得到報(bào)頭字典masthead
 masthead = json.loads(masthead_data)
 print('執(zhí)行時(shí)間%s' % masthead['time'])
 #通過(guò)報(bào)頭字典得到數(shù)據(jù)長(zhǎng)度
 data_length = masthead['length']
​
 #3.第三次接收,接收真實(shí)數(shù)據(jù),真實(shí)數(shù)據(jù)長(zhǎng)度為data_length
 # data = client.recv(data_length) #有可能真實(shí)數(shù)據(jù)長(zhǎng)度太大會(huì)撐爆內(nèi)存。
 #所以循環(huán)讀取數(shù)據(jù)
 all_data = b''
 length = 0
 #循環(huán)直到長(zhǎng)度大于等于數(shù)據(jù)長(zhǎng)度
 while length < data_length:
  data = client.recv(1024)
  length += len(data)
  all_data += data
 print('數(shù)據(jù)的總長(zhǎng)度:%s' % data_length)
​
 #我的電腦是Windows系統(tǒng),所以用gbk解碼系統(tǒng)發(fā)出的信息
 print(all_data.decode('gbk'))

總結(jié):

1.TCP協(xié)議中,會(huì)產(chǎn)生粘包現(xiàn)象。粘包現(xiàn)象產(chǎn)生本質(zhì)就是讀取數(shù)據(jù)長(zhǎng)度未知。

2.解決粘包現(xiàn)象本質(zhì)就是處理讀取數(shù)據(jù)長(zhǎng)度。

3.報(bào)頭的作用就是解決數(shù)據(jù)傳輸過(guò)程中數(shù)據(jù)長(zhǎng)度怎么計(jì)算傳達(dá)和傳輸其他額外信息的。

以上所述是小編給大家介紹的python中TCP協(xié)議中的粘包問(wèn)題詳解整合,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!

相關(guān)文章

  • python第三方庫(kù)visdom的使用入門(mén)教程

    python第三方庫(kù)visdom的使用入門(mén)教程

    Visdom:一個(gè)靈活的可視化工具,可用來(lái)對(duì)于 實(shí)時(shí),富數(shù)據(jù)的 創(chuàng)建,組織和共享,本文主要介紹了python第三方庫(kù)visdom的使用入門(mén)教程,分享給大家,感興趣的可以了解一下
    2021-05-05
  • 理解Python數(shù)據(jù)離散化手寫(xiě)if-elif語(yǔ)句與pandas中cut()方法實(shí)現(xiàn)

    理解Python數(shù)據(jù)離散化手寫(xiě)if-elif語(yǔ)句與pandas中cut()方法實(shí)現(xiàn)

    這篇文章主要介紹了通過(guò)手寫(xiě)if-elif語(yǔ)句與pandas中cut()方法實(shí)現(xiàn)示例理解Python數(shù)據(jù)離散化詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • python如何使用Redis構(gòu)建分布式鎖

    python如何使用Redis構(gòu)建分布式鎖

    這篇文章主要介紹了python如何使用Redis構(gòu)建分布式鎖,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-01-01
  • Go/Python/Erlang編程語(yǔ)言對(duì)比分析及示例代碼

    Go/Python/Erlang編程語(yǔ)言對(duì)比分析及示例代碼

    這篇文章主要介紹了Go/Python/Erlang編程語(yǔ)言對(duì)比分析及示例代碼,本文重點(diǎn)是給大家介紹go語(yǔ)言,從語(yǔ)言對(duì)比分析的角度切入介紹,需要的朋友可以參考下
    2018-04-04
  • pyqt5 實(shí)現(xiàn)多窗口跳轉(zhuǎn)的方法

    pyqt5 實(shí)現(xiàn)多窗口跳轉(zhuǎn)的方法

    今天小編就為大家分享一篇pyqt5 實(shí)現(xiàn)多窗口跳轉(zhuǎn)的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2019-06-06
  • python 讀取修改pcap包的例子

    python 讀取修改pcap包的例子

    今天小編就為大家分享一篇python 讀取修改pcap包的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2019-07-07
  • Python爬蟲(chóng)之爬取二手房信息

    Python爬蟲(chóng)之爬取二手房信息

    這篇文章主要介紹了Python爬蟲(chóng)之爬取二手房信息,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)python爬蟲(chóng)的小伙伴們有非常好的幫助,需要的朋友可以參考下
    2021-04-04
  • Python List cmp()知識(shí)點(diǎn)總結(jié)

    Python List cmp()知識(shí)點(diǎn)總結(jié)

    在本篇內(nèi)容里小編給大家整理了關(guān)于Python List cmp()用法相關(guān)知識(shí)點(diǎn),有需要的朋友們跟著學(xué)習(xí)下。
    2019-02-02
  • 用Python寫(xiě)個(gè)新年賀卡生成器

    用Python寫(xiě)個(gè)新年賀卡生成器

    大家好,本篇文章主要講的是用Python寫(xiě)個(gè)新年賀卡生成器,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下
    2022-01-01
  • 淺析Python中線程以及線程阻塞

    淺析Python中線程以及線程阻塞

    這篇文章主要為大家簡(jiǎn)單介紹一下Python中線程以及線程阻塞的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解一下
    2023-04-04

最新評(píng)論