zoukankan      html  css  js  c++  java
  • tcp协议下粘包问题的产生及解决方案

    1 tcp有粘包及udp无粘包

    - TCP 是面向连接的,面向流的可靠协议;发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,
    合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制面向流的通信是无消息保护边界的。
    - UDP(用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来
    记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
    
    注:tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,
    而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头
    

    2 产生原因:

    1、接收端不知道消息的界限,不知道一次提取多少字节数据
    2、TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,
    通常TCP会根据优化算法(Nagle算法)把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据
    
    产生粘包场景:(1)发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
    (2)接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包) 
    
    

    3 解决方案

    为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据
    struct:
    注:struct 模块 把一个数字类型转化为固定长度的bytes
    (struct.pack)打包       (struct.unpack)解包
    res=(struct.pack('i',4855524))   #b'x04xe6xe4x02' 打包
    print(res)
    print(struct .unpack('i',res)[0]) #解包
    服务端端:
    import subprocess
    import socket
    import struct
    import json
    phone= socket.socket(socket.AF_INET ,socket.SOCK_STREAM )
    phone.bind(('127.0.0.1',8080))
    phone.listen(5)
    while True :
        conn,client=phone.accept()
        while True :
            try:
                cmd = conn.recv(1024)
                if len(cmd) == 0: break
                # 远程执行命令
                obj = subprocess.Popen(cmd.decode('utf-8'), shell=True,  # 解码
                    stdout=subprocess.PIPE,  # 正确信息
                    stderr=subprocess.PIPE  # 错误信息
                    )
                stdout = obj.stdout.read()
                stderr = obj.stderr.read()
                #先制作报头
                head_dic= {'filename':'a.txt',
                 'total_size':len(stdout)+len(stderr),
                 'hash':'asdf165485221'
                 }
                head_json = json.dumps(head_dic)
                head_bytes= head_json.encode('utf-8')
                #1、先把报头的长度打包成四个bytes,然后发送
                conn.send(struct.pack('i',len(head_bytes)))
    
                #2、发送报头
                conn.send(head_bytes)
                #3、发送真实数据
                conn.send(stdout )
                conn.send(stderr)
            except ConnectionResetError:
                break
            conn.close()
        phone.close()
    
    客户端:
    import struct
    import socket
    import json
    phone= socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    phone. connect(('127.0.0.1',8080))
    while True :
        msg = input('<<<')
        if msg == 0:continue
        #phone.send(msg.encode('utf-8'))
        phone.send(bytes(msg,encoding='utf-8'))
        #1、先收4个字节,该4个字节包含报头的长度  解包
        header_len=struct .unpack('i',phone.recv(4))[0]
        #2、通过报头长度,再接受报头内容
        header_bytes=phone.recv(header_len) #通过报头长度,拿到bytes内容
        #从报头中解析出想要的内容
        header_json=header_bytes .decode('utf-8')  #报头内容解码得到字符串类型
        header_dic=json .loads(header_json)  #反序列化得到字典
        print(header_dic)
        total_size = header_dic['total_size']
        #3、再收真实的数据
        recv_size =0    #初始值长度
        res=b''  #接收的具体值
        while  recv_size< total_size:
            data= phone.recv(1024)
            res+=data  # 拼接具体的值
            recv_size += len(data)  #累加长度
        print(res.decode('gbk'))   #收到的信息用GBK解码
    phone.close()
     
  • 相关阅读:
    UIView常见方法总结
    ios UIview And Button TomCat
    (转)OC各种数据类型之间的转换方法
    8个改变让大脑变年轻
    (转)UIImageView响应点击事件
    ios UI 图片排列(简单实现)
    ios UIImage
    ios UIView 按钮
    html第一天--html常用标签
    工具软件类
  • 原文地址:https://www.cnblogs.com/quqinchao/p/9290646.html
Copyright © 2011-2022 走看看