• 20181226粘包问题


    粘包问题
    一、什么是粘包:

    粘包指的是数据与数据之间没有明确的分界线,导致程序不能正确读取数据。

    TCP或UDP协议下,程序要将收发的数据交由操作系统处理,操作系统会设立缓冲区, 用于收发各个程序的数据

    UDP(用户数据报协议): 是无连接的、面向消息的,面向消息的通信是有信息保护边界的。 基于数据包收发数据,数据包之间相互独立。 存在的问题: 数据包过大时,受制于发送方系统限制,数据包可能无法发送。 受制于接收方的系统缓存大小,当数据包超过限制时,数据包会丢失

    TCP(传输控制协议): 可以传输较大数据并保证完整性, 是面向流的、面向连接的,但面向流的通信是无信息保护边界的。

    两者区别: TCP是基于数据流的,收发的消息不能为空,客户端和服务器端都要添加空消息的处理机制,防止程序卡死 UDP是基于数据报的,即使输入空内容,也不是空消息,UDP协议会封装消息头。

     

    二、粘包为什么会发生:

    粘包只会发生在TCP下: 1、当单个数据包较小时,接收方可能一次读取多个包的数据 2、当整体数据较大时接收方可能一次仅读取一个包的一部分内容 3、另外TCP协议为了提高效率,增加了一种优化机制,会将数据较小且发送间隔较短的数据合并发送, 该机制也会导致发送方将两个数据包粘在一起发送

    粘包问题产生的根本原因是接收方无法预知接受到的数据长度

     

     

    三、如何解决粘包

    自定义报头。

    具体思路:

    发送端: 1.先将所有的额外信息打包到一个头中 2.然后先发送头部数据 3.最后发送真实数据

    接收端: 1.接收固定长度的头部长度数据 2.根据长度数据获取头部数据 3.根据头部数据获取真实数据

    代码如下:

    # 服务器端
    import socket
    import subprocess
    import struct
    import json

    serve = socket.socket()
    serve.bind(("127.2.1.1",9898))
    serve.listen()
    while True:
       client,addr = serve.accept()
       while True:
           try:
               cmd = client.recv(1024).decode("utf-8")
               if not cmd:continue  # 接受命令不能为空
               p = subprocess.Popen(cmd,
                                    shell=True,
                                    stdout=subprocess.PIPE,
                                    stderr=-1
                                    )  # subprocess调取子进程
               data = p.stdout.read()
               err_data = p.stderr.read()
               length = len(data)+len(err_data)  # 得到数据长度
               head = {"size":length,"xxx":"yyy"} # 自定义报头
               head_data = json.dumps(head).encode("utf_8")  # 序列化报头
               len_head_data = struct.pack("i",len(head_data))  # 以元组的形式打包,struct是bytes类型
               client.send(len_head_data)  # 先发送报头长度,此处信息长度固定,i模式为4
               client.send(head_data)  #发送报头
               client.send(data)  #发送数据,正常输出和错误输出一般只会发出一个
               client.send(err_data)
           except ConnectionResetError:
               client.close()
               break
    serve.close()

     

    # 客户端
    import socket
    import json
    import struct

    client = socket.socket()
    client.connect(("127.2.1.1",9898))
    while True:
       msg = input(">>>:").strip()
       if not msg:continue  # 命令不能为空
       client.send(msg.encode("utf-8"))  # 发送命令,编码为utf-8
       data = client.recv(4)  # 先接收4个bytes长度的数据,得到报头的长度
       head_length = struct.unpack("i",data)[0]   #收到的是一个元组,报头的长度
       head_data = client.recv(head_length).decode("utf-8")  #再次接收长度为报头的数据,获取报头
       head = json.loads(head_data)  # 反序列化报头
       data_length = head["size"]  # 获取字典中报头的长度

       size = 0
       res = b""  #预设空的bytes变量
       while size < data_length:
           real_data = client.recv(1024)
           size += len(real_data)
           res += real_data  # 变量完整相加,即得到所需数据
       print(res.decode("gbk"))   # win平台下,解码为gbk
    client.close()
    四、简单的视频上传和下载

    1、应该使用TCP协议,因为必须保证文件的完整性

    2、上传与下载都是bytes类型,而视频也正好是bytes类型

    3、逻辑思路

    自定义报头 ---》发送文件名----》文件大小-----》MD5值

    读取文件数据,发送给对方

    服务器端代码:

    import socket
    import struct
    import json

    server = socket.socket()
    server.bind(("127.0.0.1",6767))
    server.listen()
    client,addr = server.accept()

    f = open("用户上传文件","wb")  # 创建一个新的文件,注意此时会放到程序运行的当前文件夹下,且没有后缀

    head_len = client.recv(4)  # 接收固定长度的报头长度数据
    json_len = struct.unpack("i",head_len)[0] # 解码,获得元组eg:(43,),43即为报头的长度
    json_str = client.recv(json_len).decode("utf-8")  # 获取报头信息并解码
    head = json.loads(json_str)  # 反序列化,得到真正的报头信息


    recv_size = 0
    while recv_size < head["size"]:  # 获取文件大小
       data = client.recv(1024)
       f.write(data)  # 没有关闭状态下,会继续写入而不是覆盖
       recv_size += len(data)
    print("接收完成")
    f.close()  # 关闭文件
    serve.close()  # 关闭程序

    客户端代码:

    import json
    import struct
    import socket
    import os

    client = socket.socket()
    client.connect(("127.0.0.1",6767))

    filepath = r"C:UserswangtDocumentsday276 操作系统与进程发展史.mp4"  # 获取文件路径
    f = open(filepath,"rb")  # 只读模式打开文件

    head = {"size":os.path.getsize(filepath),"filename":"操作系统与进程发展史.mp4"}  # 关注报头获取文件大小的方式

    json_data = json.dumps(head).encode("utf-8")  # 报头序列化,用utf-8编码

    json_len = struct.pack("i",len(json_data))  # 报头长度打包
    client.send(json_len)  # 发送报头长度
    client.send(json_data)  # 发送报头数据

    while True:
       data = f.read(1024)  # 循环读取文件内容,每次读取1024bytes
       if not data:
           break
       client.send(data)
    print("上传完成")

    f.close()
    client.close()

     

  • 相关阅读:
    BZO4197 & 洛谷2150 & UOJ129:[NOI2015]寿司晚宴——题解
    BZOJ4198 & 洛谷2168 & UOJ130:[NOI2015]荷马史诗——题解
    BZOJ4651 & 洛谷1173 & UOJ220:[NOI2016]网格——题解(附debug数据)
    BZOJ4653 & 洛谷1712 & UOJ222:[NOI2016]区间——题解
    BZOJ4898 & BZOJ5367 & 洛谷3778:[APIO2017]商旅——题解
    BZOJ5340 & 洛谷4564 & LOJ2552:[CTSC2018]假面——题解
    举例分析 Makefile 中的 patsubst、wildcard、notdir 函数
    伪指令 ENTRY 与 END
    伪指令 ADR 与 LDR 的区别
    μC/OS-II 信号量集
  • 原文地址:https://www.cnblogs.com/realadmin/p/10181315.html
Copyright © 2020-2023  润新知