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()
    完整版客服端

  • 相关阅读:
    vue修改项目名
    CAS5.3.0安装部署
    nginx 10054报错问题解决方案
    react安装 项目构建
    oracle ORA-00119, ORA-00132问题解决
    SQL Server
    centos7设置httpd
    centos7 firewalld 开放端口
    java日期间相隔年月日计算
    PLSQL僵死
  • 原文地址:https://www.cnblogs.com/angle6-liu/p/10020605.html
Copyright © 2011-2022 走看看