• 粘包问题


    目录:

        粘包内存

        粘包原因

        远程执行系统命令

        解决粘包

        自定义报头

    粘包内存(仅TCP):

     粘包原因:

    所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

    TCP内部nagle算法,优化传输效率:数据量小且间隔时间小的数据会合并为一次进行发送

    未解决前:

    服务端:
    from socket import *
    
    server=socket(AF_INET,SOCK_STREAM)
    server.bind(('127.0.0.1',8080))
    server.listen(5)
    
    conn,addr=server.accept()
    
    res1=conn.recv(1024)
    print('第一次;',res1)
    
    res2=conn.recv(1024)
    print('第二次;',res2)
    
    res3=conn.recv(1024)
    print('第三次;',res3)
    客户端:
    from socket import *
    
    client = socket(AF_INET, SOCK_STREAM)
    client.connect(('127.0.0.1', 8080))
    
    client.send(b'hello')
    client.send(b'world')
    client.send(b'egon')

     运行结果:

    第一次; b'helloworldegon'#发生粘包问题
    第二次; b''
    第三次; b''

     解决之后:

    #添加时间睡眠,减慢运行过程,完成分单次发送
    服务端:
    from socket import *
    
    server=socket(AF_INET,SOCK_STREAM)
    server.bind(('127.0.0.1',8080))
    server.listen(5)
    
    conn,addr=server.accept()
    
    res1=conn.recv(1024)
    print('第一次;',res1)
    
    res2=conn.recv(1024)
    print('第二次;',res2)
    
    res3=conn.recv(1024)
    print('第三次;',res3)
    客户端:
    from socket import *
    import time
    
    client = socket(AF_INET, SOCK_STREAM)
    client.connect(('127.0.0.1', 8080))
    
    client.send(b'hello')
    time.sleep(0.2)
    client.send(b'world')
    time.sleep(0.2)
    client.send(b'egon')

     打印结果:

    第一次; b'hello'
    第二次; b'world'
    第三次; b'egon'

    远程执行系统命令:

    客户端:
     1 from socket import *
     2 import subprocess
     3 import struct
     4 
     5 server=socket(AF_INET,SOCK_STREAM)
     6 server.bind(('127.0.0.1',8080))
     7 server.listen(5)
     8 
     9 while True:
    10     conn,client_addr=server.accept()
    11     print('新的客户端',client_addr)
    12 
    13     while True:
    14         try:
    15             cmd=conn.recv(1024) #cmd=b'dir'
    16             if len(cmd) == 0:break
    17 
    18             # 运行系统命令
    19             obj=subprocess.Popen(cmd.decode('utf-8'),
    20                              shell=True,#命令解释器
    21                              stderr=subprocess.PIPE,
    22                              stdout=subprocess.PIPE
    23                              )
    24 
    25             stdout=obj.stdout.read()
    26             stderr=obj.stderr.read()
    27 
    28             #1、先制作报头(固定长度)
    29             total_size=len(stdout) + len(stderr)
    30 
    31             header=struct.pack('i',total_size)
    32 
    33             #2、先发送固定长度的报头
    34             conn.send(header)
    35 
    36             #3、再发送真实的数据
    37             conn.send(stdout)
    38             conn.send(stderr)
    39         except ConnectionResetError:
    40             break
    41 
    42     conn.close()
    服务端:
     1 from socket import *
     2 import struct
     3 
     4 client=socket(AF_INET,SOCK_STREAM)
     5 client.connect(('127.0.0.1',8080))
     6 '''
     7 如果执行的命令返回的数据过多,一次接收不完,只能打印部分信息.(剩余的会放在系统缓冲区,会导致执行新命令的时候还会打印上次命令的信息),
     8 所以需要分多次接收,接收到全部数据后,一次打印.从而避免不同的命令返回的信息发生粘包问题
     9 '''
    10 while True:
    11     cmd=input('>>: ').strip()
    12     if len(cmd) == 0:continue#输空,重新发
    13     client.send(cmd.encode('utf-8'))
    14 
    15     #1、先收固定长度的报头
    16     header=client.recv(4)
    17 
    18     #2、从报头中解析出对数据的描述信息
    19     total_size=struct.unpack('i',header)[0]#发送的数据的总长度
    20 
    21     #3、再收真实的数据
    22     recv_size=0
    23     res=b''
    24     while recv_size < total_size :#接收到的数据的长度如果小于总长度,说明没有接收完
    25         data=client.recv(1024)
    26         res+=data
    27         recv_size+=len(data)
    28 
    29     print(res.decode('gbk'))#系统返回的为gbk
    struct模块:
     1 import struct
     2 import json
     3 
     4 header_dic={
     5     'filename':'a.txt',
     6     'total_size':111123131232122222222221212121212121212211113122222222222222222222222222222222222222222222222,
     7     'hash':'asdf123123x123213x'
     8 }
     9 
    10 header_json=json.dumps(header_dic)
    11 
    12 header_bytes=header_json.encode('utf-8')
    13 
    14 obj=struct.pack('i',len(header_bytes))#i模式会将任意数据变为四位
    15 print(obj,len(obj))#b'xfbx01x00x00' 4

    解决粘包:

    服务端:
     1 from socket import *
     2 import subprocess
     3 import struct
     4 import json
     5 
     6 server = socket(AF_INET, SOCK_STREAM)
     7 server.bind(('127.0.0.1', 8080))
     8 server.listen(5)
     9 
    10 while True:
    11     conn, client_addr = server.accept()
    12     print('新的客户端', client_addr)
    13 
    14     while True:
    15         try:
    16             cmd = conn.recv(1024)  # cmd=b'dir'
    17             if len(cmd) == 0: break
    18             # 运行系统命令
    19             obj = subprocess.Popen(cmd.decode('utf-8'),
    20                                    shell=True,
    21                                    stderr=subprocess.PIPE,
    22                                    stdout=subprocess.PIPE
    23                                    )
    24 
    25             stdout = obj.stdout.read()
    26             stderr = obj.stderr.read()
    27             # 先制作报头,要包含发送数据的描述信息
    28             header_dic = {
    29                 'filename': 'a.txt',
    30                 'total_size': len(stdout) + len(stderr),
    31                 'hash': 'xasf123213123'
    32             }
    33             header_json = json.dumps(header_dic)
    34             header_bytes = header_json.encode('utf-8')
    35 
    36             # 1、先把报头的长度len(header_bytes)打包成4个bytes,然后发送
    37             conn.send(struct.pack('i', len(header_bytes)))
    38             # 2、发送报头
    39             conn.send(header_bytes)
    40             # 3、再发送真实的数据
    41             conn.send(stdout)
    42             conn.send(stderr)
    43         except ConnectionResetError:
    44             break
    45 
    46     conn.close()
    客户端:
     1 from socket import *
     2 import struct
     3 import json
     4 
     5 client = socket(AF_INET, SOCK_STREAM)
     6 client.connect(('127.0.0.1', 8080))
     7 
     8 while True:
     9     cmd = input('>>: ').strip()
    10     if len(cmd) == 0: continue
    11     client.send(cmd.encode('utf-8'))
    12 
    13     # 1、先收4个字节,该4个字节中包含报头的长度
    14     header_len = struct.unpack('i', client.recv(4))[0]
    15 
    16     # 2、再接收报头
    17     header_bytes = client.recv(header_len)
    18 
    19     # 从报头中解析出想要的内容
    20     header_json = header_bytes.decode('utf-8')
    21     header_dic = json.loads(header_json)
    22     print(header_dic)
    23     total_size = header_dic['total_size']
    24 
    25     # 3、再收真实的数据
    26     recv_size = 0
    27     res = b''
    28     while recv_size < total_size:
    29         data = client.recv(1024)
    30         res += data
    31         recv_size += len(data)
    32 
    33     print(res.decode('gbk'))
    struct模块:
     1 import struct
     2 import json
     3 
     4 header_dic={
     5     'filename':'a.txt',
     6     'total_size':11112313123212222222222222222222222222222222222222222222222222222221111111111111111111111111111,
     7     'hash':'asdf123123x123213x'
     8 }
     9 # print(len(header_dic))
    10 header_json=json.dumps(header_dic)
    11 
    12 header_bytes=header_json.encode('utf-8')
    13 print(len(header_bytes))
    14 obj=struct.pack('i',len(header_bytes))
    15 print(obj,len(obj))
    16 
    17 res=struct.unpack('i',obj)
    18 print(res[0])

     自定义报头:

    1.先用报头传输数据的长度
        对于我们远程CMD程序来说,只要先传输长度就能解决粘包的问题
        但是如果做得是一个文件上传下载,除了数据的长度,还需要传输文件的名字/md5(用来判断接收的数据和发送时是否相同是否)等信息
    
    2.自定义报头  完成发送一些额外的信息 例如文件名
        1.将要发送的额外数据打包成一个字典
        2.将字典转为bytes类型
        3.计算字典的bytes长度 并先发送
        4.发送字典数据
        5.发送真实数据

     代码:

    服务端:
     1     # 1.组装一个报头信息
     2     head_dic = {
     3         "name":"仓老师视频教学 如何做炸鸡!",
     4         "md5":"asasasasaas",
     5         "total_size":len(res),
     6         "type":"video"
     7     }
     8     # 2.转json字符串
     9     head_str = json.dumps(head_dic)
    10     # 3.转字节
    11     head_bytes = head_str.encode("utf-8")
    12     # 4.发送报头长度
    13     bytes_len = struct.pack("i",len(head_bytes))
    14     c.send(bytes_len)
    15     # 5.发送报头
    16     c.send(head_bytes)
    17     # 6.发送真实数据
    18     c.send(res)
    客户端:
     1 # 1.先获取报头长度
     2 bytes_len = c.recv(4) #对方是i格式 固定4字节
     3 # 2.转回整型
     4 head_len = struct.unpack("i",bytes_len)[0]
     5 
     6 # 3.接受报头数据
     7 head_bytes = c.recv(head_len)
     8 # 4.转为json字符串 并转为字典
     9 head_dic = json.loads(head_bytes.decode("utf-8"))
    10 print(head_dic)
    11 
    12 # 已经接收的长度
    13 recv_len = 0
    14 # 一个表示最终数据的bytes
    15 finally_data = b''
    16 # 3.收到的长度小于总长度就继续
    17 while recv_len < head_dic["total_size"]:
    18     # 循环收数据
    19     data = c.recv(1024)
    20     recv_len += len(data)
    21     finally_data += data
    22 # 整体解码
    23 print(finally_data.decode("gbk")) 
  • 相关阅读:
    Android开发之旅:组件生命周期(二)
    Android开发之旅:应用程序基础及组件
    解决wubi安装ubuntu时要下载系统映像文件问题
    Android开发之旅:应用程序基础及组件(续)
    Android开发之旅:android架构
    Android开发之旅:组件生命周期(一)
    全球首发Tech·Ed 2006中国 实况报道。全程跟踪。(三)
    全球首发Tech·Ed 2006中国 实况报道。全程跟踪。(四)
    全球首发Tech·Ed 2006中国 实况报道。全程跟踪。(二)
    SNAP的另类实现,采用js生成IFRAME内嵌框架的形式实现链接的网页'图像预览'
  • 原文地址:https://www.cnblogs.com/xuechengeng/p/9908147.html
Copyright © 2020-2023  润新知