zoukankan      html  css  js  c++  java
  • python 黏包现象及其解决方案

    一、数据缓冲区

      缓冲区(buffer),它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区,显然缓冲区是具有一定大小的。

    二、为什么要缓冲区 (详情参考:https://www.cnblogs.com/mlgjb/p/7991903.html)

      1.可以解除高速设备与低速设备的不匹配,高速设备需要等待低速设备的制约关系,数据可以直接送往缓冲区,高速设备不用再等待低速设备,提高了计算机的效率。

      2.可以减少数据的读写次数,如果每次数据只传输一点数据,就需要传送很多次,这样会浪费很多时间,因为开始读写与终止读写所需要的时间很长,如果将数据送往缓冲区,待缓冲区满后再进行传送会大大减少读写次数,这样就可以节省很多时间。

    三、粘包现象

      1、小数据传输粘包

    源代码:

    import socket
    server=socket.socket()
    server.bind(('127.0.0.1',8001))
    
    server.listen()
    conn,addr=server.accept()
    msg_1=conn.recv(1024).decode("utf-8")
    msg_2=conn.recv(1024).decode("utf-8")
    print("客户端>>>",msg_1)
    print("客户端>>>",msg_2)
    conn.close()
    server.close()
    黏包现象服务端
    import socket
    import time
    client=socket.socket()
    client.connect(('127.0.0.1', 8001))
    client.send(b'123')
    # time.sleep(0.1) 加入时间缓冲,让每次传入和接受的数据有序
    client.send(b'456')
    client.close()
    黏包现象客户端

      

    黏包现象粗略的解释:数据在传输过程中,未来得及按照先后次序传输和接受,数据都进入了数据缓冲区中,再接受的数据时候,不知道按照怎么的数据长度接受,就按照给定的1024的长度接收,因此出现了黏包.

      解决方案:

        方案一、传输过程中每次都告诉对方应该如何接收数据。

        方案二、把传输和接受的间隔加大,保证每次都能顺利的满足一个接着一个传输。(time.sleep(0.1))

      2、大数据传输粘包(模拟cmd指令)

    源代码:

    import subprocess
    import socket
    import time
    server=socket.socket()#创建socket对象
    
    server.bind(('127.0.0.1',8001))#绑定ip_port
    server.listen()#监听
    conn,addr=server.accept()#等待连接,获取连接通道和地址
    
    while 1:
        time.sleep(0.1)#减少内存占用
        cmd_msg=conn.recv(1024).decode("utf-8")
        #判断是否结束doc指令读取
        if cmd_msg=="exit" or  cmd_msg=="exit()":
            break
        else:
            #创建读取cmd指令对象
            obj_sub=subprocess.Popen(
                cmd_msg,
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
            out_info=obj_sub.stdout.read()
            err_info=obj_sub.stderr.read()
    
            if err_info:
                #不存在该命令则输出错误信息,并打印字节数
                conn.send(err_info)
                print(len(err_info))
            else:
                #存在输入的命令则发送指令执行的结果,并打印字节数
                conn.send(out_info)
                print(len(out_info))
    
    conn.close()
    server.close()
    大数据黏包现象服务器
    import socket
    client=socket.socket()#创建客户端对象
    client.connect(('127.0.0.1', 8001))#连接服务器的ip_port
    
    while 1:
    
        cmd=input("cmd>>>")
        client.send(cmd.encode("utf-8"))
        #判断是否为退出指令
        if cmd=="exit" or cmd=="exit()":
            break
        else:
            # 接受客服端返回信息
            out=client.recv(1024).decode("gbk")
            print(out)
    
    client.close()
    大数据黏包现象客户端

     结果显示:

     1 C:Python36python.exe "F:/qishi/day 28 黏包 合法性链接/黏包现象/黏包现象2客户端.py"
     2 cmd>>>ipconfig -all
     3 
     4 Windows IP 配置
     5 
     6    主机名  . . . . . . . . . . . . . : DESKTOP-MT7JLPA
     7    主 DNS 后缀 . . . . . . . . . . . : 
     8    节点类型  . . . . . . . . . . . . : 混合
     9    IP 路由已启用 . . . . . . . . . . : 否
    10    WINS 代理已启用 . . . . . . . . . : 否
    11 
    12 无线局域网适配器 WLAN:
    13 
    14    媒体状态  . . . . . . . . . . . . : 媒体已断开连接
    15    连接特定的 DNS 后缀 . . . . . . . : 
    16    描述. . . . . . . . . . . . . . . : Qualcomm Atheros AR9485WB-EG Wireless Network Adapter
    17    物理地址. . . . . . . . . . . . . : BC-30-7D-96-2D-2B
    18    DHCP 已启用 . . . . . . . . . . . : 是
    19    自动配置已启用. . . . . . . . . . : 是
    20 
    21 无线局域网适配器 本地连接* 1:
    22 
    23    媒体状态  . . . . . . . . . . . . : 媒体已断开连接
    24    连接特定的 DNS 后缀 . . . . . . . : 
    25    描述. . . . . . . . . . . . . . . : Microsoft Wi-Fi Direct Virtual Adapter
    26    物理地址. . . . . . . . . . . . . : 1E-30-7D-96-2D-2B
    27    DHCP 已启用 . . . . . . . . . . . : 是
    28    自动配置已启用. . . . . . . . . . : 是
    29 
    30 以太网适配器 以太网:
    31 
    32    连接特定的 DNS 后缀 . . . . . 
    33 cmd>>>dir
    34 . . : 
    35    描述. . . . . . . . . . . . . . . : Realtek PCIe GBE Family Controller
    36    物理地址. . . . . . . . . . . . . : C4-54-44-F5-84-6A
    37    DHCP 已启用 . . . . . . . . . . . : 是
    38    自动配置已启用. . . . . . . . . . : 是
    39    本地链接 IPv6 地址. . . . . . . . : fe80::64a8:6af:a71b:edad%5(首选) 
    40    IPv4 地址 . . . . . . . . . . . . : 192.168.12.51(首选) 
    41    子网掩码  . . . . . . . . . . . . : 255.255.255.0
    42    获得租约的时间  . . . . . . . . . : 2018年11月26日 15:01:29
    43    租约过期的时间  . . . . . . . . . : 2018年11月27日 15:01:28
    44    默认网关. . . . . . . . . . . . . : 192.168.12.254
    45    DHCP 服务器 . . . . . . . . . . . : 192.168.12.254
    46    DHCPv6 IAID . . . . . . . . . . . : 63198276
    47    DHCPv6 客户端 DUID  . . . . . . . : 00-01-00-01-23-7C-84-72-C4-54-44-F5-84-6A
    48    DNS 服务器  . . . . . . . . . . . : 114.114.114.114
    49    TCPIP 上的 NetBIOS  . . . . . . . : 已启用
    50 
    51 cmd>>>
    客户端显示结果
    C:Python36python.exe "F:/qishi/day 28 黏包 合法性链接/黏包现象/黏包现象2服务器.py"
    ipconfig -all 命令执行结果长度 1912
    dir 命令执行结果长度 505
    服务端显示结果

      现象简述:首先执行了一次ipconfig -all,他的结果数据长度是1912,而我们接受的数据长度是1024,之后又执行了一次dir,dir结果长度是505,我们再一次接受的数据,依旧是ipconfig -all 结果数据.出现了黏包现象.

      黏包现象成因:执行的数据进入缓冲区,并且数据的大小大于接受数据的大小,因此我们一次接受只能接受1024个字节,之后又执行一个命令后,命令的结果又会进入数据缓冲区,所有我们接受的数据显示的是上一个命令残余的数据.我们每次接受的数据都是1024,还有可能出现多命令的数据同时出现的风险.

      解决方案:

        方案一、按照发送数据的长度接受数据,由于缓冲区数据容量有限,我们采用循环接受数据的方法接受大数据。

        方案二、把数据的长度信息与数据信息合并一次性发给接受端,接收端先提出数据长度,再按照数据长度接受数据。

    方案一源代码:

     1 import subprocess
     2 import socket
     3 import time
     4 server=socket.socket()#创建socket对象
     5 
     6 server.bind(('127.0.0.1',8001))#绑定ip_port
     7 server.listen()#监听
     8 conn,addr=server.accept()#等待连接,获取连接通道和地址
     9 
    10 while 1:
    11     time.sleep(0.1)#减少内存占用
    12     cmd_msg=conn.recv(1024).decode("utf-8")
    13     #判断是否结束doc指令读取
    14     if cmd_msg=="exit" or  cmd_msg=="exit()":
    15         break
    16     else:
    17         #创建读取cmd指令对象
    18         obj_sub=subprocess.Popen(
    19             cmd_msg,
    20             shell=True,
    21             stdout=subprocess.PIPE,#标准化输出
    22             stderr=subprocess.PIPE #标准化错误输出
    23         )
    24         out_info=obj_sub.stdout.read()
    25 
    26         err_info=obj_sub.stderr.read()
    27 
    28 
    29         if err_info:
    30             #不存在该命令则输出错误信息,并打印字节数
    31             data_len = len(err_info)
    32             all_send_datalen=0
    33             print(f"{cmd_msg} 命令执行结果长度", data_len)
    34             #把数据长度发送给接收端
    35             conn.send(str(data_len).encode("utf-8"))
    36             while all_send_datalen<data_len:#当发送的数据小于数据总长就不断的发送
    37                 #递增式改变截取位置
    38                 every_send_data=err_info[all_send_datalen:all_send_datalen+1024]
    39                 conn.send(err_info)
    40                 all_send_datalen+=len(every_send_data)
    41 
    42         else:
    43             #存在输入的命令则发送指令执行的结果,并打印字节数
    44             data_len = len(out_info)
    45 
    46             all_send_datalen = 0
    47             print(f"{cmd_msg} 命令执行结果长度:", data_len)
    48 
    49             # 把数据长度发送给接收端
    50             conn.send(str(data_len).encode("utf-8"))
    51             while all_send_datalen < data_len:  # 当发送的数据小于数据总长就不断的放送
    52                 # 递增式改变截取位置
    53                 #每1024个字节发一次
    54                 every_send_data = out_info[all_send_datalen:all_send_datalen + 1024]
    55                 conn.send(every_send_data)
    56 
    57                 all_send_datalen += len(every_send_data)
    58 
    59 conn.close()
    60 server.close()
    大数据黏包服务器
     1 import socket
     2 client=socket.socket()#创建客户端对象
     3 client.connect(('127.0.0.1', 8001))#连接服务器的ip_port
     4 
     5 while 1:
     6 
     7     cmd=input("cmd>>>")
     8     client.send(cmd.encode("utf-8"))
     9     #判断是否为退出指令
    10     if cmd=="exit" or cmd=="exit()":
    11         break
    12     else:
    13         # 客客户端接受返回信息
    14         data_len=client.recv(1024).decode("utf-8")
    15         int_data_len=int(data_len)
    16         print(int_data_len)
    17         #接受的字节个数,计数比较
    18         all_recv_datalen=0
    19         # 用于接收到的字节拼接
    20         all_data=b''
    21         #循环接受数据
    22         while all_recv_datalen<int_data_len:
    23             # 每1024个字节接收一次
    24             every_recv_data=client.recv(1024)
    25             all_recv_datalen+=len(every_recv_data)
    26             all_data += every_recv_data
    27         # 输出打印
    28         print(all_data.decode("gbk"))
    29 
    30 client.close()
    大数据黏包客户端

    方案二struct打包:

      struct操作简介

    import struct
    #打包pack
    #struct.pack(格式,数据)
    # a=231546789
    # b=struct.pack("i",a)
    # print(b)#b'xa5x1fxcd
    '
    
    #解包unpack,结果是元祖
    #struct.unpack(格式,数据)
    c=struct.unpack("i",b'xa5x1fxcd
    ') 
    print(c) #(231546789,)
    a=c[0]
    print(a)#231546789

    源码:

    import socket
    import subprocess
    import struct
    server = socket.socket()
    ip_port = ('127.0.0.1',8001)
    data_full_len = 0 #统计发送数据的长度
    server.bind(ip_port)
    server.listen()
    conn,addr = server.accept()
    while 1:
        from_client_cmd = conn.recv(1024).decode('utf-8')
    
        sub_obj = subprocess.Popen(
            from_client_cmd,
            shell=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
        )
        #subprocess对象.read 得到的结果是bytes类型的
        cmd_res = sub_obj.stdout.read()
        data_len = len(cmd_res)  #总数据长度
        data_len_str = str(data_len)
        print('结果长度>>>',data_len)
    
        #将真实数据长度打包成4个字节的数据
        struct_data_len = struct.pack('i',data_len)
    
        conn.send(struct_data_len + cmd_res)
    大数据黏包struct服务器
    import json
    import socket
    import struct
    client = socket.socket()
    ip_port = ('127.0.0.1',8001)
    client.connect(ip_port)
    all_recv_len = 0
    all_data_byte = b''
    
    while 1:
        client_cmd = input('请输入系统指令>>>')
        client.send(client_cmd.encode('utf-8'))
        #先接收4个字节,这4个字节是真实数据长度加工成的
        recv_data_len = client.recv(4)
        #将4个字节长度的数据,解包成后面真实数据的长度
        real_data_len = struct.unpack('i',recv_data_len)[0]
    
        print(real_data_len)
    
        server_result = client.recv(real_data_len)
    
        print(server_result.decode('gbk'))
    大数据黏包struct客户端

     加强版:

     1 import struct
     2 import socket
     3 import subprocess
     4 import time
     5 
     6 server=socket.socket()
     7 server.bind(("127.0.0.1",8001))
     8 server.listen()
     9 conn,addr=server.accept()
    10 while 1:
    11     time.sleep(0.1)
    12     cmd_msg=conn.recv(1024).decode("utf-8")
    13     obj_sub=subprocess.Popen(
    14         cmd_msg,
    15         shell=True,
    16         stdout=subprocess.PIPE,
    17         stderr=subprocess.PIPE
    18     )
    19     cmd_out=obj_sub.stdout.read()
    20     cmd_erro=obj_sub.stderr.read()
    21     if obj_sub.stdout:
    22         out_len=len(cmd_out)
    23         print(cmd_msg+"	"+str(out_len))
    24         b_out_len=struct.pack("i",out_len)
    25         conn.send(b_out_len+cmd_out)
    26 
    27     else:
    28         err_len = len(cmd_erro)
    29         int(cmd_msg + "	" + str(err_len))
    30         b_err_len = struct.pack("i", err_len)
    31         conn.send(b_err_len+cmd_erro)
    32 conn.close()
    33 server.close()
    完整版服务器
     1 import struct
     2 import socket
     3 import subprocess
     4 import time
     5 
     6 client=socket.socket()
     7 client.connect(("127.0.0.1", 8001))
     8 while 1:
     9     time.sleep(0.1)
    10     cmd=input("cmd>>>").encode("utf-8")
    11     client.send(cmd)
    12     #接受数据
    13     data_len_pack=client.recv(4)
    14     data_len=struct.unpack("i",data_len_pack)[0]
    15     print(data_len)
    16     data=client.recv(data_len).decode("gbk")
    17     print(data)
    18 client.close()
    完整版客服端

  • 相关阅读:
    HTML5结构
    HTML5新增的非主体元素header元素、footer元素、hgroup元素、adress元素
    CF GYM 100703G Game of numbers
    CF GYM 100703I Endeavor for perfection
    CF GYM 100703K Word order
    CF GYM 100703L Many questions
    CF GYM 100703M It's complicate
    HDU 5313 Bipartite Graph
    CF 560e Gerald and Giant Chess
    POJ 2479 Maximum sum
  • 原文地址:https://www.cnblogs.com/angle6-liu/p/10020605.html
Copyright © 2011-2022 走看看