黏包现象和解决黏包的方法

一, 缓冲区:  将程序和网络解耦

  输入缓冲区

  输出缓冲区

二,  sunbprocess模块

import subprocess
sub_obj = subprocess.Popen(
    'ipconfig',  #系统指令
    shell=True,  #固定
    stdout=subprocess.PIPE, #标准输出  PIPE 管道,保存着指令的执行结果
    stderr=subprocess.PIPE )#标准错误输出

a = sub_obj.stdout.read()  #执行"ipconfig"命令的结果,且a为字节类型
b = sub_obj.stderr.read()  #错误输出,但"ipconfig"命令可执行,所以b为空

print('正确输出',a.decode('gbk'))  #因为操作系统的编码为"gbk",所以a字节应该用"gbk"来解码
print('错误输出',b.decode('gbk'))  #如果命令不可以被执行,b.decode('gbk')会打印 不是内部或外部命令...

  

 

三, 两种黏包现象:

  1 连续的小包可能会被优化算法给组合到一起进行发送

  2 第一次如果发送的数据大小2000B接收端一次性接受大小为1024,这就导致剩下的内容会被下一次recv接收到,导致结果错乱(看代码)

  黏包的服务端

import socket
import subprocess
server = socket.socket()
ip_port = ("127.0.0.1",8001)
server.bind(ip_port)
server.listen()
conn,add = server.accept()
while 1 :
    from_client = conn.recv(1024).decode("utf-8")
    print(from_client)
    sub_obj = subprocess.Popen(
        from_client,
        shell = True,
        stdout = subprocess.PIPE,
        stderr = subprocess.PIPE

    )
    std_msg = sub_obj.stdout.read()
    print("执行指令的长度:",len(std_msg))
    conn.send(std_msg)

  

  黏包的客户端

import socket

client = socket.socket()
client.connect(('127.0.0.1',8001))

while 1:
    cmd = input('请输入指令:')
    client.send(cmd.encode('utf-8'))
    server_cmd_result = client.recv(1024)
    print(server_cmd_result.decode('gbk'))

  当客户端输入"ipconfig/all"之后,服务端发送给客户端的内容最多为1024字节,不能完全发过来.当客户端再输入一个"dir"命令后,之前没发完的消息会继续传给客户端,这就是黏包

 

     解决黏包现象有两种方案

  方案一:由于双方不知道对方发送数据的长度,导致接收的时候,可能接收不全,或者多接收另外一次发送的信息内容,所以在发送真实数据之前,要先发送数据的长度,接收端根据长度来接收后面的真实数据,但是双方有一个交互确认的过程

 

  方案二: (重点)

    Struct模块,

      打包:struct.pack(‘i’,长度)

      解包:struct.unpack(‘i’,字节) ,解包出来是元组,需要拿到元组的第0项索引的数据,就是之前的长度

  解决黏包现象的服务端

import socket
import subprocess
import struct
server = socket.socket()
ip_port = ("127.0.0.1",8003)
server.bind(ip_port)
server.listen()
conn,add = server.accept()
while 1 :
    # 收到客户端发来的消息,并进行解码
    from_client = conn.recv(1024).decode("utf-8")
    print(from_client)
    # 把收到的消息,放在"subprocess"模块中执行"Popen"方法(执行这条消息的指令)
    sub_obj = subprocess.Popen(
        from_client,
        shell = True,
        stdout = subprocess.PIPE,
        stderr = subprocess.PIPE
    )
    # 得到执行命令的结果,该结果为字节
    std_msg = sub_obj.stdout.read()
    #打印这个结果(字节)的长度
    print("执行指令的长度:",len(std_msg))
    # 把这个长度在"struct"模块中执行"pack"方法,"i"参数是把数字编译成4位字节(打包)
    dabao = struct.pack("i",len(std_msg))
    # 把执行指令的结果,前面加上这个结果的长度(4位字节),(把打包"dabao"嵌在真实结果的包头)发送到客户端
    conn.send(dabao+std_msg)

  

  解决黏包现象的客户端

import socket
import struct
client = socket.socket()
client.connect(('127.0.0.1',8003))
while 1:
    cmd = input('请输入指令:')
    # 把输入的指令进行编码,传到服务端端
    client.send(cmd.encode('utf-8'))
    # 收到服务端发过来的消息(4个字节),其实就是服务端打包的执行命令的真实内容的长度.
    server_len_a = client.recv(4)
    # 在"struct"模块中执行"unpack"方法,把收到的4个字节解包
    # 取出该解包之后的元组中的索引为0的元素,该元素就是执行命令的(字节的)长度
    #  重点:解包出来的是元组,且只有一个元素
    server_len = struct.unpack('i',server_len_a)[0]
    # 再次收到服务端发过来的消息,"recv"后面的参数为执行命令的(字节的)长度
    server_msg = client.recv(server_len)
    # 因为操作系统编码为"gbk",所以执行命令的(字节类型)解码为"gbk"
    print(server_msg.decode('gbk'))

  

posted on 2019-01-04 21:13  哎呀!土豆  阅读(244)  评论(0编辑  收藏  举报

导航