• 缓冲区 subprocess 黏包


    一、缓冲区
     
    每个socket被创建以后,都会分配两个缓冲区,输入缓冲区和输出缓冲区,默认大小都为8k,可以通过getsocket()获取,暂时存放传输数据,防止程序在发送数据的时候卡组,提高代码运行效率
     
    首先看python的系统交互subprocess
     
    import subprocess
    
    sub_obj = subprocess.Popen(
        'dir',                  # 系统指令:'dir','ipconfig'.等
        shell=True,             # 使用shell,就相当于使用cmd窗口
        stdout=subprocess.PIPE,  # 标准输出PIPE管道,保存着指令的执行结果
        stderr=subprocess.PIPE   # 标准错误输出
    )
    print('正确输出',sub_obj.stdout.read().decode('gbk'))
    print('错误输出',sub_obj.stderr.read().decode('gbk'))
    

      

    结果编码是以当前所在系统为准的,若为windows,则用GBK解码,且只能从管道里读一次结果
     
    二、黏包
     
      1、tcp两种黏包现象:
       ①、发送端需要等缓冲区满才发送出去,造成黏包(发送时间的时间间隔很短,数据也很小,会被优化算法合到一起,产生黏包)
     
    server 端的代码示例如下
    from socket import *
    ip_port=('127.0.0.1',8080)
    
    tcp_socket_server=socket(AF_INET,SOCK_STREAM)
    tcp_socket_server.bind(ip_port)
    tcp_socket_server.listen(5)
    conn, addr = tcp_socket_server.accept()
    # 服务端连续接受两个信息
    data1 = conn.recv(10)
    data2 = conn.recv(10)
    
    print('----->',data1.decode('utf-8'))
    print('----->',data2.decode('utf-8'))
    
    conn.close
    nian_server01.py
    client端的实例如下:
     
    1 import socket
    2 BUFSIZE=1024
    3 ip_port=('127.0.0.1',8080)
    4 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    5 res=s.connect(ip_port)
    6 
    7 s.send('hello'.encode('utf-8'))
    8 s.send('sigui'.encode('utf-8'))
    nian_client01.py

    结果:

    -----> hellosigui
    ----->
    

      

     ②、
    接受方没有及时接受缓冲区的包,导致多个包接收,(客户端发送了一段数据,服务端只收了一小部分,服务区下次接收的时候还是从缓冲区拿上次遗留的数据,产生黏包)
    第一次如果发送的数据大小2000B,接收端一次性接受大小为1024,这样就导致剩下的内容会被下一次recv接收到,导致结果错乱。
     
    server代码如下
     1 import socket
     2 import subprocess
     3 server = socket.socket()
     4 ip_port = ('127.0.0.1',8010)
     5 server.bind(ip_port)
     6 server.listen()
     7 conn,addr = server.accept()
     8 
     9 while 1:
    10     from_client_cmd = conn.recv(1024)
    11     print(from_client_cmd.decode('utf-8'))
    12     sub_obj = subprocess.Popen(
    13         from_client_cmd.decode('utf-8'),
    14         shell=True,
    15         stdout=subprocess.PIPE,
    16         stderr=subprocess.PIPE
    17     )
    18     std_msg = sub_obj.stdout.read()
    19     print('指令执行的长度>>>',len(std_msg))
    20     conn.send(std_msg)
    nian_server02.py
    client代码如下
     1 import socket
     2 
     3 client = socket.socket()
     4 client.connect(('127.0.0.1',8010))
     5 
     6 while 1:
     7     cmd = input('请输入指令:')
     8     client.send(cmd.encode('utf-8'))
     9     server_cmd_result = client.recv(1025)
    10     print(server_cmd_result.decode('gbk'))
    nian_client02.py
    2、解决tcp黏包的方案
     
      1、方案一:由于接受方不知道发送端将要传送的字节流的长苏,导致接收的时候,可能接收不全,或者多接收另外一次发送的内容,所以让发送端在发送数据之前,把自己将要发送的字节流总大小让接收方知晓,然后接收方发一个确认消息给发送端,然后发送端再发送过来后面的真实数据,接收方再来接收完所有数据。
     
    server端代码:
     1 import socket
     2 import subprocess
     3 # 创建socket对象
     4 server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
     5 server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 地址重用
     6 # 设置IP地址和端口号
     7 ip_port = ('127.0.0.1',8080)
     8 # 绑定IP地址和端口号
     9 server.bind(ip_port)
    10 # 监听IP地址和端口号
    11 server.listen()
    12 # 等待建立连接,conn是连接通道,addr是客户端地址
    13 conn,addr = server.accept()
    14 
    15 while 1:
    16     from_client_cmd = conn.recv(1024)
    17     print(from_client_cmd.decode('utf-8'))
    18     # 接收到客户端发送的指令,服务端通过subprocess模块到服务器自己的系统执行这条指令
    19     sub_obj = subprocess.Popen(
    20         from_client_cmd.decode('utf-8'),
    21         shell=True,
    22         stdout=subprocess.PIPE,
    23         stderr=subprocess.PIPE
    24     )
    25     # 从管道中拿出结果,通过subprocess.Popen的实例化对象.stdout.read()方法来获取管道中的结果
    26     std_msg = sub_obj.stdout.read()
    27     # 为了解决黏包的现象,我们统计了一下消息的长度,先将消息长度发给客户端,客户端通过这个长度来接收后面服务端要发送的真实数据
    28     std_msg_len = len(std_msg)
    29     # 首先将数据长度的数据类型转化为bytes类型
    30     std_bytes_len = str(len(std_msg)).encode('utf-8')
    31     print('指令的执行结果长度>>>',len(std_msg))
    32     conn.send(std_bytes_len)
    33 
    34     status = conn.recv(1024)
    35     if status.decode('utf-8') == 'ok':
    36         conn.send(std_msg)
    37     else:
    38         pass
    solve_server01.py
    client端代码:
     1 import socket
     2 
     3 client = socket.socket()
     4 client.connect(('127.0.0.1',8080))
     5 
     6 while 1:
     7     cmd = input('请输入指令:')
     8     client.send(cmd.encode('utf-8'))
     9     server_res_len = client.recv(1024).decode('utf-8')
    10     print('来自服务端的消息长度:',server_res_len)
    11     client.send(b'ok')
    12     server_cmd_result = client.recv(int(server_res_len))
    13     print(server_cmd_result.decode('gbk'))
    solve_client01.py
     
    2、方案二:通过struck模块将需要发送的内容长度进行打包,打包成一个4字节长度的数据发送到对端,对端只要取出前4个字节,然后对前4个字节的弧据进行解包,拿到发送内容的长度,然后通过这个长度来继续接收我们要发送的内容。
    先说struct包
    例:
     
    import struct
    
    num = 100
    
    # num太大的话会报错,
    # struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围
    # 打包,将int类型的数据打包成4个长度的bytes类型的数据
    byt = struct.pack('i',num)
    print(byt)
    # 解包,将bytes类型的数据,转换为对应的那个int类型的数据
    # 注:unpack返回的是truple
    int_num = struct.unpack('i',byt)[0]
    print(int_num)
    

      

    server端代码02:
     1 import  socket
     2 import subprocess
     3 import struct
     4 
     5 server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
     6 server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)  # 地址重用
     7 ip_port = ('127.0.0.1',8090)
     8 
     9 server.bind(ip_port)
    10 server.listen()
    11 conn,addr = server.accept()
    12 
    13 while 1:
    14     from_client_cmd = conn.recv(1024)
    15 
    16     print(from_client_cmd.decode('utf-8'))
    17     # 接收到客户端发送来的系统指令,服务端通过subprocess模块到服务端自己的系统里只能怪这条指令
    18     sub_obj = subprocess.Popen(
    19         from_client_cmd.decode('utf-8'),
    20         shell=True,
    21         stdout=subprocess.PIPE,  # 正确的结果存放位置
    22         stderr=subprocess.PIPE   # 错误结果的存放位置
    23     )
    24     # 从管道中拿出结果,通过subproess.Popen的实例化对象.stdout.read()方法来获取管道中的结果
    25     std_msg = sub_obj.stdout.read()
    26     # 为了解决黏包的现象,首先统计一下消息的长度,先将消息的长度发给客户端,客户端铜鼓这个长度来接收后面服务端发送的真实数据
    27     std_msg_len = len(std_msg)
    28     print('指令的执行长度>>>',len(std_msg))
    29     msg_lenint_struct = struct.pack('i',std_msg_len)
    30     conn.send(msg_lenint_struct+std_msg)
    solve_server01.py
    client端代码02:
     1 import socket
     2 import struct
     3 client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
     4 client.connect(('127.0.0.1',8090))
     5 
     6 while 1:
     7     cmd = input('请输入指令:')
     8     # 发送指令
     9     client.send(cmd.encode('utf-8'))
    10     # 接收数据长度,首先接收4个字节长度的数据,因为这四个字节是后面数据的长度
    11     server_res_len = client.recv(4)
    12     msg_len = struct.unpack('i',server_res_len)[0]
    13 
    14     print('来自服务端的消息长度',msg_len)
    15     # 通过解包出来的长度,来接收后面的真实数据
    16     server_cmd_result = client.recv(msg_len)
    17     print(server_cmd_result.decode('gbk'))
    solve_client02.py
     
    3、udp是面向包的,所以udp是不存在黏包的。
     
    在udp代码中,我们在server端接受返回消息的时候,我们设置的recvfrom(1024),那么当我们输入的执行指令为'dir'的时候,若dir在当前文件夹下输出的内容大于1024,然后就报错了,
    解释原因:因为udp是面向报文的,每个消息是一个包,接收端设置接受大小的时候,必须要比你发的这个 包要大,不然一次接受不了就会报错,而tcp是不会报错的,这也是为什么udp会丢包的原因
     
     
     
     
  • 相关阅读:
    .NET 2.0泛型集合类与.NET 1.1集合类的区别(二)
    关于插件的好文章
    MemberInfo.GetCustomAttributes和MemberDescriptor.Attributes获取特性的不同
    新一代编程语言
    .NET 2.0泛型集合类与.NET 1.1集合类的区别(一)
    发现一篇关于.NET 2.0中关于事务命名空间的好文章
    C# WinForm控件美化扩展系列之给TextBox加水印
    log4.net
    C# GDI+ 双缓冲
    C# WinForm控件美化扩展系列之ListBox
  • 原文地址:https://www.cnblogs.com/1915823-huxt/p/10224319.html
Copyright © 2020-2023  润新知