Python Day33:粘包問題及粘包解決方案

## TCP粘包問題

```python
粘包指的是數據與數據之間沒有明確的分界線,致使不能正確讀取數據!
TCP協議也稱之爲流式協議(UDP稱爲數據報協議)
應用程序沒法直接操做硬件,應用程序想要發送數據則必須將數據交給操做系統,而操做系統須要須要同時爲全部應用程序提供數據傳輸服務,也就意味着,操做系統不可能立馬就能將應用程序的數據發送出去,就須要爲應用程序提供一個緩衝區,用於臨時存放數據,具體流程以下:
# 發送方:
當應用程序調用send函數時,應用程序會將數據從應用程序拷貝到操做系統緩存,再由操做系統從緩衝區讀取數據併發送出去
# 接收方:
對方計算機收到數據也是操做系統先收到,至於應用程序什麼時候處理這些數據,操做系統並不清楚,因此一樣須要將數據先存儲到操做系統的緩衝區中,當應用程序調用recv時,其實是從操做系統緩衝區中將數據拷貝到應用程序的過程

1.發送方發送的數據長度每一個操做系統會有不一樣的限制,數據超過限制則沒法發送
2.接收方接收數據時若是應用程序的提供的緩存容量小於數據包的長度將形成數據丟失,而緩衝區大小不可能無限大
# TCP:
當咱們須要傳輸較大的數據,或須要保證數據完整性時,最簡單的方式就是使用TCP協議了
與UDP不一樣的是,TCP增長了一套校驗規則來保證數據的完整性,會將超過TCP包最大長度的數據拆分爲多個TCP包 並在傳輸數據時爲每個TCP數據包指定一個順序號,接收方在收到TCP數據包後按照順序將數據包進行重組,重組後的數據全都是二進制數據,且每次收到的二進制數據之間沒有明顯的分界

'基於這種工做機制TCP在三種狀況下會發送粘包問題
1.當單個數據包較小時接收方可能一次性讀取了多個包的數據
2.當總體數據較大時接收方可能一次僅讀取了一個包的一部份內容 
3.另外TCP協議爲了提升效率,增長了一種優化機制,會將數據較小且發送間隔較短的數據合併發送,該機制也會致使發送方將兩個數據包粘在一塊兒發送
```



## 解決粘包問題

```python'
首先明確只有TCP會出現粘包問題,之因此粘包是由於接收方不知道一次該接收的數據長度,那如何才能讓接收方知道數據的長度呢?  
可是因爲negle優化機制的存在,長度信息和數據仍是有可能會粘包,而接受方並不知道長度信息具體幾個字節,因此如今的問題是如何可以長度信息作成一個固定長度的bytes數
 咱們能夠將字符串拼接爲一個固定長度的字符 可是這樣太麻煩,struct模塊爲咱們提供了一個功能,能夠將整數類型轉換爲固定長度的bytes,此時就派上用場了
```

## 解決粘包後:

## 客戶端

```PYTHO
import socket
import struct
c = socket.socket()
c.connect(("127.0.0.1",8888))
while True:
    cmd = input(">>>:").strip()
    c.send(cmd.encode("utf-8"))

    data = c.recv(4)
    length = struct.unpack("i",data)[0]
    
    print(length)
    size = 0
    res = b""
    while size < length:
        temp = c.recv(1024)
        size += len(temp)
        res += temp
    print(res.decode("gbk"))

```

## 服務端

```python
import socket
import subprocess
import struct
server = socket.socket()
server.bind(("127.0.0.1",8888))
server.listen()

while True:
    client, addr = server.accept()
    while True:
        cmd = client.recv(1024).decode("utf-8")
        p = subprocess.Popen(cmd,shell=True,stdout=-1,stderr=-1)
        data = p.stdout.read()+p.stderr.read()
        length = len(data)
        len_data = struct.pack("i",length)
        client.send(len_data)

        print(length)
        client.send(data)

```

## 2.自定義報頭解決粘包

上述方案已經完美解決了粘包問題,可是擴展性不高,例如咱們要實現文件上傳下載,不光要傳輸文件數據,還須要傳輸文件名字,md5值等等,如何能實現呢?

具體思路:

發送端:

1.先將全部的額外信息打包到一個頭中

2.而後先發送頭部數據

3.最後發送真實數據

接收端:

1.接收固定長度的頭部長度數據

2.根據長度數據獲取頭部數據

3.根據頭部數據獲取真實數據

### 客戶端

```python
import socket
import struct
import json
c = socket.socket()
c.connect(("127.0.0.1",8888))
while True:
    cmd = input(">>>:").strip()
    c.send(cmd.encode("utf-8"))
    
    # 頭部數據
    data = c.recv(4)
    head_length = struct.unpack("i",data)[0]
    head_data = c.recv(head_length).decode("utf-8")
    head = json.loads(head_data)
    print(head)

    # 真實數據長度
    data_length = head["data_size"]

    #接收真實數據
    size = 0
    res = b""
    while size < data_length:
        temp = c.recv(1024)
        size += len(temp)
        res += temp

    print(res.decode("gbk"))

```

### 服務器

```python
import socket
import subprocess
import struct
import json
server = socket.socket()
server.bind(("127.0.0.1",8888))
server.listen()

while True:
    client, addr = server.accept()
    while True:
        cmd = client.recv(1024).decode("utf-8")
        p = subprocess.Popen(cmd,shell=True,stdout=-1,stderr=-1)
        
        # 真實數據
        data = p.stdout.read() + p.stderr.read()
        
        # 頭部數據
        head = {"data_size":len(data),"額外信息":"額外的值"}
        head_data = json.dumps(head).encode("utf-8")
        #頭部長度
        head_len = struct.pack("i",len(head_data))

        #逐個發送
        client.send(head_len)
        client.send(head_data)
        client.send(data)

```
相關文章
相關標籤/搜索