TCP粘包問題

2022-07-11 12:36:09 字數 4121 閱讀 9206

tcp協議又叫流式協議,在我們利用tcp協議傳輸資料時,tcp協議有乙個優化機制,比如我們在極短的時間內連續輸入10次,每次只有乙個位元組,他會在我們記憶體中開闢乙個快取區,一次性傳輸在對方機器的快取區之中,當對方需要取資料時,再到自己的快取區取。就是因為這個原因產生粘包問題。

如昨天的學習筆記中,我們一次取1024位元組的資料,但在真實傳輸中不可能只有這麼少的資料,超過1024位元組的資料會留在快取區中,等待下次列印時候再取出,但是下次我們可能已經輸入了新的命令,但列印的是老命令的內容。

我們的解決思路是一次性取完每次的資料,但是又會有乙個新問題,我們如何知道每次傳輸過來的資料長度?如果不知道資料長度我們肯定無無法一次迴圈取完。所以我們需要在傳送這個正文資料前,先傳送一段報頭,報頭來標記後面正文的長度。

當然這只是乙個簡單的思路,我們的報頭不可能只包含資料長度,可能還需要包括資料的加密方法,資料的標題等,那麼我們又產生了乙個新問題,報頭的長度又不固定了。所以我們可以再在報頭前先發一段記錄報頭長度的資料,依次傳送,解決該問題。

須知:只有tcp有粘包現象,udp永遠不會粘包,為何,且聽我娓娓道來

首先需要掌握乙個socket收發訊息的原理

傳送端可以是一k一k地傳送資料,而接收端的應用程式可以兩k兩k地提走資料,當然也有可能一次提走3k或6k資料,或者一次只提走幾個位元組的資料,也就是說,應用程式所看到的資料是乙個整體,或說是乙個流(stream),一條訊息有多少位元組對應用程式是不可見的,因此tcp協議是面向流的協議,這也是容易出現粘包問題的原因。而udp是面向訊息的協議,每個udp段都是一條訊息,應用程式必須以訊息為單位提取資料,不能一次提取任意位元組的資料,這一點和tcp是很不同的。怎樣定義訊息呢?可以認為對方一次性write/send的資料為乙個訊息,需要明白的是當對方send一條資訊的時候,無論底層怎樣分段分片,tcp協議層會把構成整條訊息的資料段排序完成後才呈現在核心緩衝區。

例如基於tcp的套接字客戶端往服務端上傳檔案,傳送時檔案內容是按照一段一段的位元組流傳送的,在接收方看了,根本不知道該檔案的位元組流從何處開始,在何處結束

所謂粘包問題主要還是因為接收方不知道訊息之間的界限,不知道一次性提取多少位元組的資料所造成的。

此外,傳送方引起的粘包是由tcp協議本身造成的,tcp為提高傳輸效率,傳送方往往要收集到足夠多的資料後才傳送乙個tcp段。若連續幾次需要send的資料都很少,通常tcp會根據優化演算法把這些資料合成乙個tcp段後一次傳送出去,這樣接收方就收到了粘包資料。

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的協議資料不會丟,沒有收完包,下次接收,會繼續上次繼續接收,己端總是在收到ack時才會清除緩衝區內容。資料是可靠的,但是會粘包。

兩種情況下會發生粘包。

傳送端需要等緩衝區滿才傳送出去,造成粘包(傳送資料時間間隔很短,資料了很小,會合到一起,產生粘包)

服務端

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=

header_json=json.dumps(header_dic)

header_json_bytes=header_json.encode('utf-8')

# 二、傳送資料

# 1、先傳送報頭的長度

header_size=len(header_json_bytes)

conn.send(struct.pack('i',header_size))

# 2、再傳送報頭

conn.send(header_json_bytes)

# 3、最後傳送真實的資料

with open(r'%s' %file_path,mode='rb') as f:

for line in f:

conn.send(line)

except exception:

break

conn.close()

server.close()

客戶端

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、最後接收真實的資料

total_size=header_dic['total_size']

filename=header_dic['filename']

recv_size = 0

with open(r"d:\python全棧15期\day32\**\03 定製複雜的報頭\版本2\download\%s" %filename, mode='wb') as f:

while recv_size < total_size:

data = client.recv(1024)

f.write(data)

recv_size += len(data)

client.close()

tcp粘包問題

什麼是粘包問題 粘包問題的起因是socket的快取機制。簡而言之 粘包問題就是如何將連續的資料按照不同的資料幀截斷,以及如何處理殘包情況。分割資料需要按需分配。處理殘包也很簡單 等 等它發來下一包資料,不管他發來多少資料,先拿來512,接到上次那512後面。湊成完整的資料幀。當然也有可能你發現這次來...

TCP粘包問題

原因 tcp提供的是一種位元組流服務,沒有訊息保護邊界。傳送端需要等緩衝區滿才傳送出去,造成粘包 接收方不及時接收緩衝區的包,造成多個包接收 解決方法 一是對於傳送方引起的粘包現象,使用者可通過程式設計設定來避免,tcp提供了強制資料立即傳送的操作指令push,tcp軟體收到該操作指令後,就立即將本...

TCP粘包問題

tcp粘包是指傳送方傳送的若干包資料到接收方接收時粘成一包,從接收緩衝區看,後一包資料的頭緊接著前一包資料的尾。出現粘包現象的原因是多方面的,它既可能由傳送方造成,也可能由接收方造成。傳送方引起的粘包是由tcp協議本身造成的,tcp為提高傳輸效率,傳送方往往要收集到足夠多的資料後才傳送一包資料。若連...