粘包

粘包

【一】什么是粘包

  • 须知:只有TCP有粘包现象,UDP永远不会粘包

【二】什么是粘包问题

  • 客户端发送需要执行的代码
  • 服务端接收到客户端传过来的代码
    • 服务端调用方法执行代码并拿到执行后的结果
    • 服务端将执行后的结果进行返回
  • 客户端接收到服务端返回的结果并做打印输出

【1】服务端

from socket import *
import subprocess

server = socket()
server.bind(('127.0.0.1', 8888))
server.listen(5)

while True:
    conn, client_addr = server.accept()
    while True:
        try:
            cmd_from_client = conn.recv(1024)
            if len(cmd_from_client) == 0:
                break
            msg_server = subprocess.Popen(cmd_from_client.decode('utf-8'),
                                          shell=True,
                                          stdout=subprocess.PIPE,
                                          stderr=subprocess.PIPE,
                                          )
            true_msg = msg_server.stdout.read()
            false_msg = msg_server.stderr.read()
            conn.send(true_msg)
            conn.send(false_msg)

        except Exception as e:
            break
    conn.close()

【2】客户端

from socket import *

while True:
    client = socket()
    client.connect(('127.0.0.1', 8888))
    msg = input('enter msg :>>>').strip()
    if len(msg) == 0: continue
    msg = msg.encode('utf-8')
    client.send(msg)
    msg_from_server = client.recv(1024)
    msg_from_server = msg_from_server.decode('gbk')
    print(msg_from_server)
    client.close()

【3】问题引入

  • 服务端:
    • 执行代码,代码为空会报错
    • 执行代码,返回的数据可能存在空/报错信息
  • 客户端:
    • 输入的指令长度,可能会超出范围
    • 接受到的服务端反馈的结果可能会特别多
    • 如何打印超出数据范围(缓存到系统里)的数据

【4】粘包问题

  • 在 TCP 协议中是流式协议,数据是源源不断的传入到客户端中,但是客户端可以接受到的信息的长度是有限的
  • 当接收到指定长度的信息后,客户端进行打印输出
    • 剩余的其他数据会被缓存到 内存中
  • 当再次执行其他命令时
    • 新的数据的反馈结果,会叠加到上一次没有完全打印完全的信息的后面,造成数据的错乱
  • 当客户端想打印新的命令的数据时,打印的其实是上一次没有打印完的数据
    • 对数据造成的错乱

【5】粘包问题解决思路

  • 拿到数据的总大小 recv_total_size
  • recv_size = 0 ,循环接收,每接收一次,recv_size += 接收的长度
  • 直到 recv_size = recv_total_size 表示接受信息完毕,结束循环

【三】UDP协议不存在粘包问题

  • 粘包问题出现的原因

    • TCP 协议是流式协议,数据像水流一样粘在一起,没有任何边界之分
    • 收数据没有接收干净,有残留,就会和下一次的结果混淆在一起
  • 解决粘包问题的核心法门就是

    • 每次都收干净
    • 不造成数据的混淆
  • 当我们启动udp服务端后,由udp客户端向服务端发送两条数据

  • 但是在udp服务端只接收到了一条数据

  • 这是因为 udp 是报式协议,传送数据过程中会将数据打包直接发走,不会对数据进行拼接操作(没有Nagle算法)

【四】TCP协议解决粘包问题

【1】解决思路

  • 利用 struct 模块将传输过去的数据的总长度 打包 + 到头部进行发送

【2】服务端

import json
from socket import *
import subprocess
import struct

server = socket()
server.bind(('127.0.0.1', 8888))
server.listen(5)

while True:
    conn, client_addr = server.accept()
    while True:
        try:
            cmd_from_client = conn.recv(1024)
            if len(cmd_from_client) == 0:
                break
            msg_server = subprocess.Popen(cmd_from_client.decode('utf-8'),
                                          shell=True,
                                          stdout=subprocess.PIPE,
                                          stderr=subprocess.PIPE,
                                          )
            true_msg = msg_server.stdout.read()
            false_msg = msg_server.stderr.read()
            total_size_len = len(true_msg) + len(false_msg)
            headers = {
                'total_size': total_size_len
            }
            json_data = json.dumps(headers)
            json_data_bytes = json_data.encode('utf-8')
            json_data_size_pack = struct.pack('i', len(json_data_bytes))
            conn.send(json_data_size_pack)
            conn.send(json_data_bytes)

            conn.send(true_msg)
            conn.send(false_msg)

        except Exception as e:
            break
    conn.close()

【3】客户端

from socket import *
import struct,json

while True:
    client = socket()
    client.connect(('127.0.0.1', 8888))
    msg = input('enter msg :>>>').strip()
    if len(msg) == 0: continue
    msg = msg.encode('utf-8')
    client.send(msg)
    json_data_size_unpack = client.recv(4)
    json_data_size = struct.unpack('i', json_data_size_unpack)[0]
    json_data_bytes = client.recv(json_data_size)
    json_dict=json_data_bytes.decode('utf8')
    headers_dict=json.loads(json_dict)
    total_len=headers_dict['total_size']

    recv_size=0
    while recv_size < total_len:
        msg_from_server = client.recv(1024)
        print(msg_from_server.decode('gbk'),end='')
        recv_size+=1024
    client.close()

【五】数据传输练习

【1】服务端

import json
import struct
from socket import *
import os

server = socket()
server.bind(('127.0.0.1', 9696))
server.listen(5)

while True:
    client, addr = server.accept()
    while True:

        file_name_data = client.recv(1024)
        file_name = file_name_data.decode('utf8')
        print(f"这是来自客户端想要下载的文件 :>>>> {file_name}")
        file_path = os.path.join(os.path.dirname(__file__), file_name)

        with open(file_path, 'rb') as f:
            video_data = f.read()

        message = {
            "data_length": len(video_data)
        }

        header_bytes = bytes(json.dumps(message), encoding='utf-8')

        header_len_bytes = struct.pack('i', len(header_bytes))

        # 发送消息头长度
        client.send(header_len_bytes)
        # 发送消息头
        client.send(header_bytes)
        # 发送视频数据
        client.sendall(video_data)

    conn.close()

【2】客户端

import os
from socket import *
import struct
import json

while True:
    client = socket()

    client.connect(('127.0.0.1', 9696))
    cmd_data = input("需要下载的文件名 :>>>> ").strip()
    new_file_name = input("请输入需要保存的文件名 :>>>> ").strip()
    file_path = os.path.join(os.path.dirname(__file__), new_file_name)
    cmd_data = cmd_data.encode('utf8')
    client.send(cmd_data)

    head_len_bytes = client.recv(4)
    head_len = struct.unpack('i', head_len_bytes)[0]

    head_bytes = client.recv(head_len)
    json_data = head_bytes.decode('utf-8')
    data = json.loads(json_data)

    video_size = data['data_length']
    video_data = b''
    while len(video_data) < video_size:
        video_data += client.recv(1024)

    with open(file_path, 'wb') as f:
        f.write(video_data)
posted @ 2024-01-16 16:18  蓝幻ﹺ  阅读(15)  评论(0编辑  收藏  举报