学习笔记(12):Python网络编程&并发编程-解决粘包问题-简单版本

立即学习:https://edu.csdn.net/course/play/24458/296243?utm_source=blogtoedu

粘包现象的解决:简单版

 

1.思路:

      在服务器端计算出执行命令后结果的字节长度,然后再将字节数长度send即通知给客户端,客户端根据这个字节数的长度一次性即可将相应的命令执行结果给接收,进而解决了粘包问题。

 

2.知识点

 

1)互联网协议:报头+数据

 

2)报头是固定长度字节的,一般是4字节数,包含了一段数据的相关信息,如数据的字节总数以及相关描述等;

 

3)struct模块,是python内置模块,用于报头的相关函数,如res = struct.pack('i',信息)是用于定制固定长度的函数,得到的是一个对象,而struct.unpack('i',res)则是解析报头的函数,得到的是一个元组,第一个元素为字节数长度

 

3.关键代码

'''
服务端
'''

......

#1接收客户端发送过来的命令
            cmd = conn.recv(1024)
            #2处理命令,执行命令并且获得命令得到的结果
            obj = subprocess.Popen(cmd.decode('utf-8'),shell=True,
                             stdout=subprocess.PIPE,#将正确运行命令得到的结果传给管道stdout中
                             stderr=subprocess.PIPE)#将没有正确运行命令得到的返回信息存放在stderr管道中

            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            total_size = len(stderr + stdout)
            #1)定制固定长度的报头,报头包含命令执行结果的字节数长度
            header = struct.pack('i',total_size)

            #2)将报头发送给客户端
            conn.send(header)

            #3)将真实的命令执行结果信息发送给客户端
            data = stdout + stderr
            conn.send(data)

......


'''
客户端
'''

.......

#4、接收服务器返回来的数据recv()

    #1)先接收由服务器返回来的报头,报头是固定长度的,因此取前面4字节的数据即为报头
    header = phone.recv(4)#返回的是一个对象

    #2)解析返回的报头,获得字节数总长信息
    obj_truple = struct.unpack('i',header)#返回的是一个元组
    total_size = obj_truple[0]#取元组第一个元素即为总字节数

    #3)接收真实的命令执行结果信息
    recv_size = 0
    data = b''
    while recv_size < total_size:
        recv_data = phone.recv(1024)#接收小于1024bytes的数据
        recv_size += len(recv_data)
        data += recv_data
    print('服务器返回来的数据:',data.decode('gbk'))

.......

结果:由结果可以得到,输入相应的命令可以得到正确的命令执行结果

#第一个命令

请输入:dir
服务器返回来的数据:  驱动器 C 中的卷是 本地磁盘
 卷的序列号是 B476-3C7C

 C:\Users\jinlin\Desktop\python_further_study\socket编程\粘包现象解决(简单版) 的目录

2020/03/09  14:46    <DIR>          .
2020/03/09  14:46    <DIR>          ..
2020/03/09  14:46             1,503 客户端(粘包).py
2020/03/09  14:45             1,434 服务器端(粘包).py
               2 个文件          2,937 字节
               2 个目录 122,025,189,376 可用字节

 

#第二个命令

请输入:tasklist
服务器返回来的数据:
映像名称                       PID 会话名              会话#       内存使用
========================= ======== ================ =========== ============
System Idle Process              0 Services                   0          4 K
System                           4 Services                   0        568 K
smss.exe                       324 Services                   0        784 K
csrss.exe                      524 Services                   0      8,760 K
csrss.exe                      620 Console                    1     37,232 K
wininit.exe                    628 Services                   0      3,940 K
winlogon.exe                   656 Console                    1      6,564 K

..............

cmd.exe                      11188 Console                    1      2,304 K
tasklist.exe                  9056 Console                    1      6,176 K

 

#第三个命令

请输入:dir
服务器返回来的数据:  驱动器 C 中的卷是 本地磁盘
 卷的序列号是 B476-3C7C

 C:\Users\jinlin\Desktop\python_further_study\socket编程\粘包现象解决(简单版) 的目录

2020/03/09  14:46    <DIR>          .
2020/03/09  14:46    <DIR>          ..
2020/03/09  14:46             1,503 客户端(粘包).py
2020/03/09  14:45             1,434 服务器端(粘包).py
               2 个文件          2,937 字节
               2 个目录 122,024,615,936 可用字节

 

4.完整代码

'''
服务端
'''
import socket
import subprocess
import struct
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(('127.0.0.1',8080))
phone.listen(5)
while True:
    #接收客户端发送过来连接服务器请求
    res = phone.accept()
    conn,client_addr = res
    while True:
        try:
            #1接收客户端发送过来的命令
            cmd = conn.recv(1024)
            #2处理命令,执行命令并且获得命令得到的结果
            obj = subprocess.Popen(cmd.decode('utf-8'),shell=True,
                             stdout=subprocess.PIPE,#将正确运行命令得到的结果传给管道stdout中
                             stderr=subprocess.PIPE)#将没有正确运行命令得到的返回信息存放在stderr管道中

            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            total_size = len(stderr + stdout)
            #1)定制固定长度的报头,报头包含命令执行结果的字节数长度
            header = struct.pack('i',total_size)

            #2)将报头发送给客户端
            conn.send(header)

            #3)将真实的命令执行结果信息发送给客户端
            data = stdout + stderr
            conn.send(data)

        except ConnectionResetError:
            break
    conn.close()
phone.close()
phone.close()
'''
客户端
'''
#导入模块
import socket
import struct

#1、设置phone套接字
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

#2、连接服务器(打电话),本地地址:127.0.0.1
phone.connect(('127.0.0.1',8080))

#3、向服务器发送请求send(),发送的数据不能直接发送字符串,因为要传送到物理层底层,因此需要转换成二进制的bytes类型进行发送,只需:发送的数据.encode('utf-8')即可
while True:
    cmd = input("请输入:")
    #修复客户端发送空字符串而服务器卡在接收信息处的bug,continue表示跳出本次循环,重新开始下一次的循环
    if not cmd:continue
    phone.send(cmd.encode('utf-8'))

    #4、接收服务器返回来的数据recv()

    #1)先接收由服务器返回来的报头,报头是固定长度的,因此取前面4字节的数据即为报头
    header = phone.recv(4)#返回的是一个对象

    #2)解析返回的报头,获得字节数总长信息
    obj_truple = struct.unpack('i',header)#返回的是一个元组
    total_size = obj_truple[0]#取元组第一个元素即为总字节数

    #3)接收真实的命令执行结果信息
    recv_size = 0
    data = b''
    while recv_size < total_size:
        recv_data = phone.recv(1024)#接收小于1024bytes的数据
        recv_size += len(recv_data)
        data += recv_data
    print('服务器返回来的数据:',data.decode('gbk'))

#5、关闭套接字phone
phone.close()

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

有情怀的机械男

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

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

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

打赏作者

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

抵扣说明:

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

余额充值