day32-粘包問題和報頭的定制

粘包問題

TCP協(xié)議作為流式協(xié)議奏寨,只有TCP協(xié)議存在粘包問題鲸沮。

發(fā)送端可以是一K一K地發(fā)送數(shù)據(jù),而接收端的應(yīng)用程序可以兩K兩K地提走數(shù)據(jù)弯淘,當(dāng)然也有可能一次提走3K或6K數(shù)據(jù)榆综,或者一次只提走幾個字節(jié)的數(shù)據(jù)妙痹,也就是說,應(yīng)用程序所看到的數(shù)據(jù)是一個整體鼻疮,或說是一個流(stream)怯伊,一條消息有多少字節(jié)對應(yīng)用程序是不可見的,因此TCP協(xié)議是面向流的協(xié)議判沟,這也是容易出現(xiàn)粘包問題的原因耿芹。而UDP是面向消息的協(xié)議,每個UDP段都是一條消息挪哄,應(yīng)用程序必須以消息為單位提取數(shù)據(jù)吧秕,不能一次提取任意字節(jié)的數(shù)據(jù),這一點和TCP是很不同的迹炼。怎樣定義消息呢砸彬?可以認(rèn)為對方一次性write/send的數(shù)據(jù)為一個消息,需要明白的是當(dāng)對方send一條信息的時候斯入,無論底層怎樣分段分片砂碉,TCP協(xié)議層會把構(gòu)成整條消息的數(shù)據(jù)段排序完成后才呈現(xiàn)在內(nèi)核緩沖區(qū)。

例如基于tcp的套接字客戶端往服務(wù)端上傳文件刻两,發(fā)送時文件內(nèi)容是按照一段一段的字節(jié)流發(fā)送的增蹭,在接收方看了,根本不知道該文件的字節(jié)流從何處開始闹伪,在何處結(jié)束

所謂粘包問題主要還是因為接收方不知道消息之間的界限沪铭,不知道一次性提取多少字節(jié)的數(shù)據(jù)所造成的壮池。

此外偏瓤,發(fā)送方引起的粘包是由TCP協(xié)議本身造成的杀怠,TCP為提高傳輸效率,發(fā)送方往往要收集到足夠多的數(shù)據(jù)后才發(fā)送一個TCP段厅克。若連續(xù)幾次需要send的數(shù)據(jù)都很少赔退,通常TCP會根據(jù)優(yōu)化算法把這些數(shù)據(jù)合成一個TCP段后一次發(fā)送出去,這樣接收方就收到了粘包數(shù)據(jù)证舟。

  1. TCP(transport control protocol硕旗,傳輸控制協(xié)議)是面向連接的,面向流的女责,提供高可靠性服務(wù)漆枚。收發(fā)兩端(客戶端和服務(wù)器端)都要有一一成對的socket,因此抵知,發(fā)送端為了將多個發(fā)往接收端的包墙基,更有效的發(fā)到對方,使用了優(yōu)化方法(Nagle算法)刷喜,將多次間隔較小且數(shù)據(jù)量小的數(shù)據(jù)残制,合并成一個大的數(shù)據(jù)塊,然后進(jìn)行封包掖疮。這樣初茶,接收端,就難于分辨出來了浊闪,必須提供科學(xué)的拆包機(jī)制恼布。 即面向流的通信是無消息保護(hù)邊界的。
  2. UDP(user datagram protocol搁宾,用戶數(shù)據(jù)報協(xié)議)是無連接的桥氏,面向消息的,提供高效率服務(wù)猛铅。不會使用塊的合并優(yōu)化算法字支,, 由于UDP支持的是一對多的模式,所以接收端的skbuff(套接字緩沖區(qū))采用了鏈?zhǔn)浇Y(jié)構(gòu)來記錄每一個到達(dá)的UDP包奸忽,在每個UDP包中就有了消息頭(消息來源地址堕伪,端口等信息),這樣栗菜,對于接收端來說欠雌,就容易進(jìn)行區(qū)分處理了。 即面向消息的通信是有消息保護(hù)邊界的疙筹。
  3. tcp是基于數(shù)據(jù)流的富俄,于是收發(fā)的消息不能為空禁炒,這就需要在客戶端和服務(wù)端都添加空消息的處理機(jī)制,防止程序卡住霍比,而udp是基于數(shù)據(jù)報的幕袱,即便是你輸入的是空內(nèi)容(直接回車),那也不是空消息悠瞬,udp協(xié)議會幫你封裝上消息頭们豌,實驗略

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

tcp的協(xié)議數(shù)據(jù)不會丟凌外,沒有收完包辩尊,下次接收,會繼續(xù)上次繼續(xù)接收康辑,己端總是在收到ack時才會清除緩沖區(qū)內(nèi)容摄欲。數(shù)據(jù)是可靠的,但是會粘包晾捏。

一蒿涎、遠(yuǎn)程執(zhí)行命令程序解決粘包問題

server端

import subprocess
import struct
from socket import *

server = socket(AF_INET, SOCK_STREAM)
# print(server)
server.bind(('127.0.0.1', 8082))
server.listen(5)
while True:
    conn, client_addr = server.accept()
    print(conn)
    print(client_addr)

    while True:
        try:
            cmd = conn.recv(1024)
            obj = subprocess.Popen(cmd.decode('utf-8'),
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   )

            stdout = obj.stdout.read()
            stderr = obj.stdout.read()
            total_size = len(stdout) + len(stderr)

            # 先發(fā)送數(shù)據(jù)的長度
            conn.send(struct.pack('i',total_size))
            # 發(fā)送真正的數(shù)據(jù)
            conn.send(stdout)
            conn.send(stderr)
        except Exception:
            break
    conn.close()

server.close()

client端

import struct
from socket import *

client = socket(AF_INET, SOCK_STREAM)
# print(client)
client.connect(('127.0.0.1', 8082))

while True:
    cmd = input(">>: ").strip()
    if len(cmd) == 0:
        continue
    client.send(cmd.encode('utf-8'))

    # 先收數(shù)據(jù)的長度
    n = 0
    header = b''
    while n < 4:
        data = client.recv(1)
        header += data
        n += len(data)

    total_size = struct.unpack('i', header)[0]

    # 收真正的數(shù)據(jù)
    recv_size = 0
    res = b''
    while recv_size < total_size:
        data = client.recv(1024)
        res += data
        recv_size += len(data)

    print(res.decode('gbk'))

client.close()

二、定制復(fù)雜的報頭

server.py端

import subprocess
import os
import struct
import json
from socket import *

server = socket(AF_INET, SOCK_STREAM)
# print(server)
server.bind(('127.0.0.1', 8082))
server.listen(5)
while True:
    conn, client_addr = server.accept()
    print(conn)
    print(client_addr)

    while True:
        try:
            msg = conn.recv(1024).decode('utf-8')
            cmd,file_path=msg.split()
            if cmd == "get":
                # 一惦辛、制作報頭
                header_dic={
                    "total_size":os.path.getsize(file_path),
                    "filename":os.path.basename(file_path),
                    "md5":"1231231231232132131232311"  # 加密
                }
                header_json=json.dumps(header_dic)
                header_json_bytes=header_json.encode('utf-8')


                # 二劳秋、發(fā)送數(shù)據(jù)
                # 1、先發(fā)送報頭的長度
                header_size=len(header_json_bytes)
                conn.send(struct.pack('i',header_size))
                # 2胖齐、再發(fā)送報頭
                conn.send(header_json_bytes)
                # 3玻淑、最后發(fā)送真實的數(shù)據(jù)
                with open(r'%s' %file_path,mode='rb') as f:
                    for line in f:
                        conn.send(line)
        except Exception:
            break
    conn.close()

server.close()

client端

import struct
import json
from socket import *

client = socket(AF_INET, SOCK_STREAM)
# print(client)
client.connect(('127.0.0.1', 8082))

while True:
    cmd = input(">>: ").strip()  # get 文件路徑
    if len(cmd) == 0:
        continue
    client.send(cmd.encode('utf-8'))

    # 1、先接收報頭的長度
    res=client.recv(4)
    header_size=struct.unpack('i',res)[0]
    # 2呀伙、再接收報頭
    header_json_bytes=client.recv(header_size)
    header_json=header_json_bytes.decode('utf-8')
    header_dic=json.loads(header_json)
    print(header_dic)
    # 3补履、最后接收真實的數(shù)據(jù)
    total_size=header_dic['total_size']
    filename=header_dic['filename']
    recv_size = 0
    with open(r"D:\%s" %filename, mode='wb') as f:
        while recv_size < total_size:
            data = client.recv(1024)
            f.write(data)
            recv_size += len(data)


client.close()
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市剿另,隨后出現(xiàn)的幾起案子箫锤,更是在濱河造成了極大的恐慌,老刑警劉巖雨女,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谚攒,死亡現(xiàn)場離奇詭異,居然都是意外死亡氛堕,警方通過查閱死者的電腦和手機(jī)馏臭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來讼稚,“玉大人括儒,你說我怎么就攤上這事绕沈。” “怎么了帮寻?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵乍狐,是天一觀的道長。 經(jīng)常有香客問我规婆,道長澜躺,這世上最難降的妖魔是什么蝉稳? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任抒蚜,我火速辦了婚禮,結(jié)果婚禮上耘戚,老公的妹妹穿的比我還像新娘嗡髓。我一直安慰自己,他們只是感情好收津,可當(dāng)我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布饿这。 她就那樣靜靜地躺著,像睡著了一般撞秋。 火紅的嫁衣襯著肌膚如雪长捧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天吻贿,我揣著相機(jī)與錄音串结,去河邊找鬼。 笑死舅列,一個胖子當(dāng)著我的面吹牛肌割,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播帐要,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼把敞,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了榨惠?” 一聲冷哼從身側(cè)響起奋早,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎赠橙,沒想到半個月后耽装,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡简烤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年剂邮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片横侦。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡挥萌,死狀恐怖绰姻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情引瀑,我是刑警寧澤狂芋,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站憨栽,受9級特大地震影響帜矾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜屑柔,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一屡萤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧掸宛,春花似錦死陆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至饰序,卻和暖如春领虹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背求豫。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工塌衰, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人注祖。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓猾蒂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親是晨。 傳聞我的和親對象是個殘疾皇子肚菠,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,066評論 2 355