【Python】网络编程--粘包问题--终极版

粘包问题开始先来一个知识点回顾:

执行命令的话,肯定是用我们学过的subprocess模块啦,但注意注意注意:

res = subprocess.Popen(cmd.decode('utf-8'),shell=True,stderr=subprocess.PIPE,stdout=subprocess.PIPE)

命令结果的编码是以当前所在的系统为准的,如果是windows,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码,且只能从管道里读一次结果

由代码引申出下面的问题:

服务端–简单版:

import socket

# 1:买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
print(phone)

# 2:绑定手机卡
phone.bind(('127.0.0.1',8081))#0-65535:0-1024给操作系统使用


# 3:开机,5:可以预览的网页数
phone.listen(5)

# 4:等电话连接
print('starting...')
conn,client_addr=phone.accept()


# 5:收,发消息
data=conn.recv(1024)#1:单位:bytes 2:1024代表最大接收1024个bytes
print('客户端的数据',data)

conn.send(data.upper())

# 6:挂电话
conn.close()

# 7:关机
phone.close()

客户端–简单版:

import socket

# 1:买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
print(phone)

# 2:拨号
phone.connect((' 127.0.0.1',8081))

# 3:发,收消息
phone.send('hello'.encode('UTF-8'))
data =phone.recv(1024)
print(data)

# 4:关闭
phone.close()

粘包:

什么是粘包?

在执行命令时,你并不知道用户输入的信息有多大,而你的系统一开始是固定大小的,你输入的数据较小可能你就已经拿到了你想要得到的数据了,此时心情愉悦呀,想着我已经掌握网络编程基础了真不错,但是别高兴的太早:

此时执行一个结果比较长的命令,比如top -bn 1, 你发现依然可以拿到结果,但如果再执行一条df -h的话,就发现,你拿到并不是df命令的结果,而是上一条top命令的部分结果

因为,top命令的结果比较长,但客户端只recv(1024), 可结果比1024长呀,那怎么办,只好在服务器端的IO缓冲区里把客户端还没收走的暂时存下来,等客户端下次再来收,所以当客户端第2次调用recv(1024)就会首先把上次没收完的数据先收下来,再收df命令的结果。

那遇这类问题,有人就想着,有些同学说,直接把recv(1024)改大不就好了,改成5000\10000或whatever. 可我的亲,这么干的话,并不能解决实际问题,因为你不可能提前知道对方返回的结果数据大下,无论你改成多大,对方的结果都有可能比你设置的大,另外这个recv并不是真的可以随便改特别大的,有关部门建议的不要超过8192,再大反而会出现影响收发速度和不稳定的情况

这个现象叫做粘包,就是指两次结果粘到一起了。它的发生主要是因为socket缓冲区导致的

粘包问题只存在于TCP中,Not UDP

发送端可以是一K一K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据,也就是说,应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。怎样定义消息呢?可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。

例如基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束

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

粘包问题–终极版

代码实现:
客户端,client

import socket
import struct
import json

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

phone.connect(('127.0.0.1',9909))

while True:
    #1、发命令
    cmd=input('>>: ').strip() #ls /etc
    if not cmd:continue
    phone.send(cmd.encode('utf-8'))

    #2、拿命令的结果,并打印

    #第一步:先收报头的长度
    obj=phone.recv(4)
    header_size=struct.unpack('i',obj)[0]

    #第二步:再收报头
    header_bytes=phone.recv(header_size)

    #第三步:从报头中解析出对真实数据的描述信息
    header_json=header_bytes.decode('utf-8')
    header_dic=json.loads(header_json)
    print(header_dic)
    total_size=header_dic['total_size']

    #第四步:接收真实的数据
    recv_size=0
    recv_data=b''
    while recv_size < total_size:
        res=phone.recv(1024) #1024是一个坑
        recv_data+=res
        recv_size+=len(res)

    print(recv_data.decode('utf-8'))

phone.close()



注意:如果是Windows系统需要把客户端倒数第二行的编码格式换为GBK,因为Windows系统不兼容,否则会错,如果是Mac系统其他系统的就不需要改

服务端:

import socket
import subprocess
import struct
import json


phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.bind(('127.0.0.1',9908))

phone.listen(5)

print('starting...')
while True:
    conn,client_addr=phone.accept()
    print(client_addr)

    while True:#通信循环
        try:
            # 1:收命令
            cmd=conn.recv(8096)
            if not cmd:break#适用于Linux操作系统

            # 2:执行命令,拿到结果
            obj=subprocess.Popen(cmd.decode('utf-8'), shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)

            stdout=obj.stdout.read()
            stderr=obj.stderr.read()


            # 3:把命令的结果返回给客户端
            # 第一步:制作固定长度的报头
            header_dic={
                'filename':'a.txt',
                'md5':'xxxdxxxx',
                'total_size':len(stdout)+len(stderr)
            }

            header_json=json.dumps(header_dic)
            header_bytes=header_json.encode('utf-8')

            # 第二步:先发送报头的长度
            conn.send(struct.pack('i',len(header_bytes)))

            # 第三步:再发报文
            conn.send(header_bytes)

            # 第四步:再发送真实的数据
            conn.send(stdout)
            conn.send(stderr)


        except  ConnectionResetError:
            break
    conn.close()


phone.close()

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Recently 祝祝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值