黏包-黏包的成因、解決方式及struct模塊初識、文件的上傳和下載

黏包:python


同時執行多條命令以後,獲得的結果極可能只有一部分,在執行其餘命令的時候又接收到以前執行的另一部分結果,這種顯現就是黏包。面試

只有TCP協議中才會產生黏包,UDP協議中不會有黏包(udp協議中數據會直接丟失,俗稱丟包)算法

#面試
#首先只有在TCP協議中才有黏包現象,是由於TCP協議是面向流的協議,
#在發送的數據傳輸的過程當中還有緩存機制來避免數據丟失
#所以在連續發送小數據的時候,以及接收大小不符的時候,都容易產生黏包現象
#本質是不知道客戶端發送的數據長度
面試中解釋黏包
#連續send兩個小數據
#兩個recv,第一個recv特別小

#面試
#首先只有在TCP協議中才有黏包現象,是由於TCP協議是面向流的協議,
#在發送的數據傳輸的過程當中還有緩存機制來避免數據丟失
#所以在連續發送小數據的時候,以及接收大小不符的時候,都容易產生黏包現象
#本質是不知道客戶端發送的數據長度

#服務端發送數據,通過系統內存,客戶端接收,在TCP協議中,若是服務端發送數據過大,或者客戶端接收數據過少,超過的數據就會滯納在內存中
#從而產生了黏包。可是在UDP過程當中則直接丟包了
#本質問題,不知道客戶端發送的數據長度

#在TCP中  若是發送端發送了3次,3次總數據大小爲10,接收端若是隻接收一次,接收大小>10,那麼這3次會合起來一塊兒被接收
#覺得TCP中的優化算法,當幾回數據又小又連續,會合併發送(須要又小又連續)
#由於一次性接收,只須要經歷一次網絡延時,提升接收效率,
#而且只要接收方足夠大,它會一次性把這三次數據都給合併接收,即便此處有3次接收
#好比此處 ret1和ret2接收都爲空,連續的小數據被ret接收了
# ret = conn.recv(1024)
# print(ret)
# ret1 = conn.recv(1024) #此時ret1和ret2接收到的空,是由於數據都被ret接收了,原本是阻塞的,等待消息傳入。
# ret2 = conn.recv(1024) #可是此時客戶端close了,會默認發送空消息過來(根據windows版本不一樣,也可能直接報錯),恰好被ret1和ret2接收了。
# print(ret1,ret2)

#多個send小的數據連在一塊兒,會發生黏包現象,是TCP內部優化算法形成的
#注意這幾個send必須又短又連續且發送間隔很是短暫(0.01秒都不行)
黏包問題總結
#UDP不會黏包,可是udp會丟包
#tcp會黏包,可是不會丟包

# 黏包成因:
# TCP(transport control protocol,傳輸控制協議)是面向鏈接的,面向流的,提供高可靠性服務。
# 收發兩端(客戶端和服務器端)都要有一一成對的socket,所以,發送端爲了將多個發往接收端的包,更有效的發到對方,
# 使用了優化方法(Nagle算法),將屢次間隔較小且數據量小的數據,合併成一個大的數據塊,而後進行封包。
# 這樣,接收端,就難於分辨出來了,必須提供科學的拆包機制。 即面向流的通訊是無消息保護邊界的。

# 當發送端緩衝區的長度大於網卡的MTU時,tcp會將此次發送的數據拆成幾個數據包發送出去。
# MTU是Maximum Transmission Unit的縮寫。意思是網絡上傳送的最大數據包。MTU的單位是字節。
# 大部分網絡設備的MTU都是1500。若是本機的MTU比網關的MTU大,大的數據包就會被拆開來傳送,
# 這樣會產生不少數據包碎片,增長丟包率,下降網絡速度。

# 1.是由於tcp的拆包機制,使得消息沒有邊界
# 2.當發送端緩衝區的長度大於網卡的MTU時,產生了數據包碎片
黏包成因-蠻看看就好

 

黏包的發現:shell


socket模塊中,TCP協議的黏包問題發現:(用到了subprocess模塊)json

import socket
import subprocess
ip_port = ('127.0.0.1',8898)
buffer_size = 10240

sk = socket.socket()
sk.bind(ip_port)
sk.listen()

conn,addr = sk.accept()
while True:
    cmd = input('>>>>> ')
    if cmd == 'q':
        break
    conn.send(cmd.encode('utf-8'))
    ret = conn.recv(buffer_size).decode('utf-8')
    # ret2 = conn.recv(buffer_size).decode('utf-8')
    print(ret)
    # print(ret2)

conn.close()
sk.close()
serve端-黏包
import socket
import subprocess
sk = socket.socket()
sk.connect(('127.0.0.1',8898))

while True:
    cmd = sk.recv(1024).decode('gbk')
    ret = subprocess.Popen(cmd,shell=True,
                           stdout=subprocess.PIPE,
                           stderr=subprocess.PIPE
                           )
    std_out = 'stdout: '+(ret.stdout.read()).decode('gbk')
    std_err = 'stderr: '+(ret.stderr.read()).decode('gbk')
    print(std_out)
    print(std_err)
    sk.send(std_out.encode('utf-8'))
    sk.send(std_err.encode('utf-8'))
sk.close()
client端-黏包

黏包本身的小分析:windows

#服務端發送數據,通過系統內存,客戶端接收,在TCP協議中,若是服務端發送數據過大,或者客戶端接收數據過少,超過的數據就會滯納在內存中(內核態用戶態)
#從而產生了黏包。可是在UDP過程當中則直接丟包了
#本質問題,不知道客戶端發送的數據長度


import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8898))
sk.listen()

conn,addr = sk.accept()

#在TCP中  若是發送端發送了3次,3次總數據大小爲10,接收端若是隻接收一次,接收大小>10,那麼這3次會合起來一塊兒被接收
#覺得TCP中的優化算法,當幾回數據又小又連續,會合併發送(須要又小又連續)
#由於一次性接收,只須要經歷一次網絡延時,提升接收效率,
#而且只要接收方足夠大,它會一次性把這三次數據都給合併接收,即便此處有3次接收
#好比此處 ret1和ret2接收都爲空,連續的小數據被ret接收了
ret = conn.recv(1024)
print(ret)
ret1 = conn.recv(1024) #此時ret1和ret2接收到的空,是由於數據都被ret接收了,原本是阻塞的,等待消息傳入。
ret2 = conn.recv(1024) #可是此時客戶端close了,會默認發送空消息過來(根據windows版本不一樣,也可能直接報錯),恰好被ret1和ret2接收了。
print(ret1,ret2)

conn.close()
sk.close()

#多個send小的數據連在一塊兒,會發生黏包現象,是TCP內部優化算法形成的
#注意這幾個send必須又短又連續且發送間隔很是短暫(0.01秒都不行)
server-黏包問題分析
import socket,time
sk = socket.socket()
sk.connect(('127.0.0.1',8898))

sk.send(b'hello')
time.sleep(0.01) #此時 hello會單獨發送,下面兩句才一塊兒發送
sk.send(b'he22')
sk.send(b'33llo')

#多個send小的數據連在一塊兒,會發生黏包現象,是TCP內部優化算法形成的
#注意這幾個send必須又短又連續且發送間隔很是短暫(0.01秒都不行)

# import time
# time.sleep(5)
sk.close()
client-黏包問題分析

 

黏包的解決緩存


有兩種方式:服務器

1.比較low的方法:及優劣點分析網絡

import socket

sk = socket.socket()
sk.bind(('127.0.0.1',8898))
sk.listen()
conn,addr = sk.accept()

while True:
    cmd = input('>>>>> ')
    if cmd == 'q':
        break
    conn.send(cmd.encode('gbk'))
    len_num = conn.recv(1024).decode('utf-8')
    conn.send(b'ok')
    len_num = int(len_num)
    ret = conn.recv(len_num).decode('gbk')
    print(ret)


conn.close()
sk.close()

#好處:肯定了我到底要接收多大的數據
    #要在文件中配置了一個配置項:就是每一次recv的大小  buffer = 4096
    #當咱們要發送大數據的時候,要明確的告訴接收方要發送多大的數據,以便接收方可以準確的接收到全部數據
    #多用在文件傳輸的過程當中
        #大文件的傳輸 必定是按照字節讀 每一次讀固定的字節
        #傳輸的過程當中 發送端一邊讀一邊傳,接收端一邊收一邊寫
        #send這個大文件以前,35672字節 send(4096)-....-send(4096)---->0
        #resv這個大文件以前,35672字節 send(4096)-....-send(4096)---->0

#很差的地方:
    #多了一次交互
    #send sendto 在超過必定範圍的時候,都會報錯
    #程序的內存管理
server端-方法的好處和壞處
import socket
import subprocess

sk = socket.socket()
sk.connect(('127.0.0.1',8898))

while True:
    cmd = sk.recv(1024).decode('gbk')
    if cmd == 'q':
        break
    ret = subprocess.Popen(cmd,shell=True,
                           stdout=subprocess.PIPE,
                           stderr=subprocess.PIPE)
    std_out = ret.stdout.read()  #注意read自己就是bytes類型。
    std_err = ret.stderr.read()  #此處read完後,相似從list中pop,管道中的值就取出來沒了,相似隊列
    len_num = str(len(std_out) + len(std_err))
    sk.send(len_num.encode('utf-8'))
    sk.recv(1024)

    sk.send(std_out)
    sk.send(std_err)
sk.close()
client端

 

2.用struct模塊解決黏包問題,及struct模塊初識併發

import socket
import subprocess
import struct
sk = socket.socket()
sk.bind(('127.0.0.1',8898))
sk.listen()
conn,addr = sk.accept()

while True:
    cmd = input('>>>>> ')
    if cmd == 'q':
        break
    conn.send(cmd.encode('gbk'))
    len_byte = conn.recv(4)         #經過struct模塊,肯定知道傳過來的int爲4個字節,這裏只收取4個字節,保證不黏包
    len_num = struct.unpack('i',len_byte)[0]  #獲取到傳過來的數字,也就是接下來要接收數據的大小
    ret = conn.recv(len_num).decode('gbk')    #經過len,知道接下來要接收多大的數據,從而無論發送端發了幾回,這邊能夠一次性接收
    print(ret)


conn.close()
sk.close()
server端-struct
import socket
import subprocess
import struct
sk = socket.socket()
sk.connect(('127.0.0.1',8898))

while True:
    cmd = sk.recv(1024).decode('gbk')
    if cmd == 'q':
        break
    ret = subprocess.Popen(cmd,shell=True,
                           stdout=subprocess.PIPE,
                           stderr=subprocess.PIPE)
    std_out = ret.stdout.read()  #注意read自己就是bytes類型。
    std_err = ret.stderr.read()  #此處read完後,相似從list中pop,管道中的值就取出來沒了,相似隊列
    len_num = len(std_out) + len(std_err)
    len_byt = struct.pack('i',len_num)
    sk.send(len_byt)
    sk.send(std_out)
    sk.send(std_err)
sk.close()
client端-struct
import struct
#模塊能夠把一個數字 轉換爲 一個bytes類型 (二進制)  而後再轉回數字類型
#以int爲例  int轉換後的bytes類型長度爲 4字節,以元組的方式返回 (int,)
#除了int 還有 float double  long  等等
#其中,int將在之後很長的時間裏都只使用int

num_byte = struct.pack('i',4096)
print(num_byte)      #b'\x00\x10\x00\x00'
print(len(num_byte)) #長度爲4

num_tup = struct.unpack('i',num_byte)
print(num_tup)          #(4096,)
print(type(num_tup[0])) #<class 'int'>

#經過struct模塊,咱們就將int轉換爲長度爲4的bytes,就能夠經過先recv(4),獲取到一個int類型
# 這個int類型表示接下來要接收的數據大小
#和黏包問題解決中比較low的方法比較,省去了一個交互步驟,不用我收到len大小後,再send(b'ok')來避免 len 被黏包
struct模塊初識

 

練習:


大文件的上傳和下載

import os
import json
import socket
import struct
bufer = 1024
ip_port = ('127.0.0.1',8898)
sk = socket.socket()
sk.bind(ip_port)
sk.listen()
conn,addr = sk.accept()

len_bytes = conn.recv(4)   #!!!!!這裏怎麼能夠出錯,struct傳過來的int 就是4字節
len_head = struct.unpack('i',len_bytes)[0]
head_json = conn.recv(len_head).decode('utf-8')
head = json.loads(head_json)
filesize = head['filesize']
with open(head['file_name'],'wb') as f:
    while filesize:
        print(filesize)
        if filesize >= bufer:
            file_write = conn.recv(bufer)
            f.write(file_write)
            filesize -= bufer
        else:
            file_write = conn.recv(filesize)
            f.write(file_write)
            break
conn.close()
sk.close()
server
import os
import json
import socket
import struct
bufer = 1024
ip_port = ('127.0.0.1',8898)
sk = socket.socket()
sk.connect(ip_port)

head = {'file_path':r'D:\python-全棧九期\day33',
        'file_name':'04 python fullstack s9day33 ftp做業分析.mp4',
        'filesize':None}
file_path = os.path.join(head['file_path'],head['file_name']) #join的用法寫錯了 !!!
filesize = os.path.getsize(file_path)
head['filesize'] = filesize
head_json = json.dumps(head)
head_bytes = head_json.encode('utf-8')

#開始用struct轉換報頭長度
len_head = len(head_bytes)
head_pack = struct.pack('i',len_head)
sk.send(head_pack)
sk.send(head_bytes)
with open(file_path,'rb') as f:  #!!!這裏rb寫成了wb
    while filesize:
        print(filesize)
        if filesize >= bufer:
            content = f.read(bufer)
            sk.send(content)
            filesize -= bufer
        else:
            content = f.read(filesize)
            sk.send(content)
            break
sk.close()
client
相關文章
相關標籤/搜索