1. 程式人生 > >006---粘包現象分析以及解決粘包問題

006---粘包現象分析以及解決粘包問題

size 字節流 inpu 問題 字節數 con sub while 丟失

粘包

什麽是粘包?

須知:只有TCP有粘包現象、UDP永遠不會粘包。

socket收發消息的原理

技術分享圖片

模擬ssh遠程執行的命令

# 服務端
import subprocess, socket

sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

sk.bind(('127.0.0.1', 8011))

sk.listen(5)

print('starting...1')
while True:
    conn, addr = sk.accept()
    while True:
        try:
            # 收命令
            cmd = conn.recv(1024)
            if not cmd:
                break
            print('客戶端發來的數據', cmd.decode('utf-8'))

            # 執行命令,拿到結果
            obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            res = obj.stdout.read()
            res_error = obj.stderr.read()
            if not res and not res_error:
                conn.send('呵呵呵'.encode('gbk'))
            # 返回執行命令的結果給客戶端
            conn.send(res + res_error)  # 可以優化
            print(len(res) + len(res_error))
        except ConnectionResetError:
            break
    conn.close()

sk.close()

# 客戶端
import socket

sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

sk.connect(('127.0.0.1', 8011))
while True:
    # 發命令
    cmd = input('輸入你的cmd:').strip()
    if not cmd: continue
    sk.send(cmd.encode('utf-8'))

    # 接收執行命令的結果
    data = sk.recv(1024)

    print(data.decode('gbk'))

sk.close()

技術分享圖片
技術分享圖片
技術分享圖片

分析

正常的cmd命令ipconfig應該顯示完全,可我們的tcp協議模擬的服務器和客戶端並沒有顯示完全。

發送端可以是1k1k的發送數據,而接收端可以2k2k的提取數據,甚至10k10k的提取。也就是說應用程序看到的數據是一個流。

只有tcp有粘包現象,udp永遠不會有粘包現象。

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

tcp的協議數據不會丟,沒有收完包,下次接收,會繼續上次繼續接收,己端總是在收到ack時才會清除緩沖區內容。數據是可靠的,但是會粘包。

分類:

  • 發送端粘包:需要等到緩沖區滿了一定字節的數據才發給服務端。造成粘包。發送時間的間隔很短,數據很小,合到一起就粘包了。
    技術分享圖片
# 服務端
import socket

sk = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)

sk.bind(('127.0.0.1',8011))

sk.listen(5)

conn,addr = sk.accept()

data1 = conn.recv(1024)
print('第一次',data1)

data2 = conn.recv(1024)
print('第二次',data2)

# 客戶端
import socket

client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)

client.connect(('127.0.0.1',8011))

client.send('hello'.encode('utf-8'))
client.send('p1111'.encode('utf-8'))
client.send('p1111'.encode('utf-8'))
client.send('p1111'.encode('utf-8'))
  • 接收方粘包(之前的模擬的cmd命令):接收的字節數小於發送方發送的字節數。所以,接收方的緩沖區有積壓數據,下次接收就是直接取已殘留的數據。

解決粘包問題

為字節流加上自定義固定長度報頭,報頭中包含字節流長度,然後一次send到對端,對端在接收時,先從緩存中取出定長的報頭,然後再取真實數據。

struct模塊

該模塊可以把一個類型,如數組轉化為固定長度的bytes。

服務端

import subprocess

import socket

sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

sk.bind(('127.0.0.1', 8005))

sk.listen(5)

print('starting...1')
while True:
    conn, addr = sk.accept()
    while True:
        try:
            cmd = conn.recv(1024)
            if not cmd:
                break
            print('客戶端發來的命令:', cmd.decode('utf-8'))

            obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            res = obj.stdout.read()
            res_error = obj.stderr.read()

            if not res and not res_error:
                conn.send('呵呵呵'.encode('gbk'))

            length = len(res + res_error)
            print('給客戶端發送的執行命令結果的長度:', length)

            import struct

            data_length = struct.pack('i', length)
            conn.send(data_length)
            conn.send(res + res_error)

        except ConnectionResetError:
            break
    conn.close()

sk.close()

客戶端

import socket

sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

sk.connect(('127.0.0.1', 8005))
while True:
    # 發命令
    cmd = input('輸入你的cmd:').strip()
    if not cmd: continue
    sk.send(cmd.encode('utf-8'))

    import struct

    data_length = sk.recv(4)
    length = struct.unpack('i', data_length)[0]
    print('客戶端接收服務端發來的數據長度:', length)
    recv_size = 0
    recv_msg = b''
    while recv_size < length:
        recv_msg += sk.recv(1024)
        recv_size = len(recv_msg)

    print('執行結果為:', recv_msg.decode('gbk'))

sk.close()

006---粘包現象分析以及解決粘包問題