python基础-tcp粘包、解决方案、subprocess执行shell命令

来源:互联网 发布:专业家具建模软件 编辑:程序博客网 时间:2024/06/05 11:27

      • subprocess
      • 引入socket粘包问题
      • 粘包现象
        • 客户端粘包
        • 服务端粘包
        • 粘包解决方案1
          • structpack
        • 粘包解决方案2

subprocess

平时我们在dos命令窗口中
这里写图片描述

那么我们在python中也有一个方法subprocess实现同样的效果

import subprocessobj=subprocess.Popen('dir',shell=True,                 stdout=subprocess.PIPE,                 stderr=subprocess.PIPE,                 )print(obj.stdout.read().decode('gbk'))print(obj.stdout.read().decode('gbk'))print(obj.stderr.read().decode("gbk"))

输出如下:

E:\python\python_sdk\python.exe "E:/python/py_pro/1 作业(粘包问题)/subprocess模块.py" 驱动器 E 中的卷没有标签。 卷的序列号是 A449-5D34 E:\python\py_pro\1 作业(粘包问题) 的目录2017/11/28  15:46    <DIR>          .2017/11/28  15:46    <DIR>          ..2017/11/28  15:46               289 subprocess模块.py2017/11/28  13:39                72 tcp的nagle算法2017/11/28  13:39               282 客户端.py2017/11/28  13:39               946 服务端.py               4 个文件          1,589 字节               2 个目录 119,030,386,688 可用字节Process finished with exit code 0

我们看到stdout只打印一次,因为第一次已经从管道取完,第二次就不会取出了

我们改下上面的代码改查如下:

obj=subprocess.Popen('dirr',shell=True,                 stdout=subprocess.PIPE,                 stderr=subprocess.PIPE,                 )

然后输出如下:

E:\python\python_sdk\python.exe "E:/python/py_pro/1 作业(粘包问题)/subprocess模块.py"'dirr' 不是内部或外部命令,也不是可运行的程序或批处理文件。Process finished with exit code 0

引入socket粘包问题

我们接下来写一个例子,需求是在客户端输入dos命令,然后在服务端接受结果,然后返回给客户端
我们先来看下服务端代码

import subprocessfrom socket import *server=socket(AF_INET,SOCK_STREAM)server.bind(('127.0.0.1',8081))server.listen(5)while True:    conn, addr=server.accept()    print(conn)    while True:        try:            cmd=conn.recv(8096)            if not cmd:break #针对linux            #执行命令            cmd=cmd.decode('utf-8')            #调用模块,执行命令,并且收集命令的执行结果,而不是打印            obj = subprocess.Popen(cmd, shell=True,                                   stdout=subprocess.PIPE,                                   stderr=subprocess.PIPE,                                   )            stdout=obj.stdout.read()            stderr=obj.stderr.read()            print(stdout.decode('gbk'))            #发送命令的结果            conn.send(stdout+stderr)        except ConnectionResetError:            break    conn.close()server.close()

接下来看客户端代码:

from socket import *client=socket(AF_INET,SOCK_STREAM)client.connect(('127.0.0.1',8081))while True:    cmd=input('>>: ').strip()    if not cmd:continue    client.send(cmd.encode('utf-8'))    data=client.recv(1024)    print(data.decode('gbk'))client.close()

接下来我们进行测试启动服务端,在启动客户端
在客户端输入dir指令,然后就在服务端接收到如下信息
这里写图片描述

服务端在 conn.send(stdout+stderr)发送指令到客户端,如下是输出信息
这里写图片描述

以上代码是有问题的?如果我们在客户端重新输入一个dos指令tasklist,我们再来看看有什么不同的输出信息呢?
服务端输出了tasklist的全部信息
这里写图片描述

然而客户端输出了tasklist的部分信息
这里写图片描述

原因是什么呢?
我们在服务端

cmd=conn.recv(8096)

然而我们在客户端

data=client.recv(1024)

当我们输入dir,在服务端能一次性的接收,服务端在发给列表时候,客户端也能一次性的取出来
但是我们输入tasklist,服务端能一次性取,然而服务端在发给客户端时候,客户端由于只能接受1024,所以就不会从缓存中一下取完大于1024的那部分数据

其实客户端,服务端recv(大小)的方式都是不可取的,这个例子只是引出一个概念就是粘包

粘包现象

此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。

客户端粘包

发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)

服务端:

import timefrom socket import *server=socket(AF_INET,SOCK_STREAM)server.bind(('127.0.0.1',8082))server.listen(5)conn,addr=server.accept()res1=conn.recv(1024)print(res1)

客户端:

import timefrom socket import *client=socket(AF_INET,SOCK_STREAM)client.connect(('127.0.0.1',8082))client.send(b'hello')client.send(b'world')

启动服务端、启动客户端
然后在服务端输出如下信息:

b'helloworld'

以上的例子就是一个粘包现象,将客户端的hello和world打包发送给服务端

服务端粘包

接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
服务端:

import timefrom socket import *server=socket(AF_INET,SOCK_STREAM)server.bind(('127.0.0.1',8082))server.listen(5)conn,addr=server.accept()res1=conn.recv(2)print(res1)res1=conn.recv(1024)print(res1)res1=conn.recv(1024)print(res1)

客户端:

import timefrom socket import *client=socket(AF_INET,SOCK_STREAM)client.connect(('127.0.0.1',8082))client.send(b'hello')time.sleep(1)client.send(b'world')

服务端输出如下:

E:\python\python_sdk\python.exe "E:/python/py_pro/2 粘包现象/服务端.py"b'he'b'llo'b'world'

粘包解决方案1

我们知道python只定义了6种数据类型,字符串,整数,浮点数,列表,元组,字典。但是C语言中有些字节型的变量,在python中该如何实现呢?这点颇为重要,特别是要在网络上进行数据传输的话。
struct.pack用于将Python的值根据格式符,转换为字符串(因为Python中没有字节(Byte)类型,可以把这里的字符串理解为字节流,或字节数组)。其函数原型为:struct.pack(fmt, v1, v2, …),参数fmt是格式字符串,关于格式字符串的相关信息下面有所介绍。v1, v2, …表示要转换的python值。下面的例子将两个整数转换为字符串(字节流):

struct.pack
import struct#帮我们把数字转成固定长度的bytes类型res=struct.pack('i',68999)print(res,len(res))res1=struct.unpack('i',res)print(res1[0])re2 = struct.pack("ii",2,3)print(len(re2))

输出如下:

b'\x87\r\x01\x00' 4689998

接下来看下解决粘包的第一种方案

服务端

import subprocessimport structfrom socket import *server=socket(AF_INET,SOCK_STREAM)server.bind(('127.0.0.1',8085))server.listen(5)while True:    conn,addr=server.accept()    print(addr)    while True:        try:            cmd=conn.recv(8096)            if not cmd:break #针对linux            #执行命令            cmd=cmd.decode('utf-8')            print(cmd,"==========")            #调用模块,执行命令,并且收集命令的执行结果,而不是打印            obj = subprocess.Popen(cmd, shell=True,                                   stdout=subprocess.PIPE,                                   stderr=subprocess.PIPE,                                   )            stdout=obj.stdout.read()            stderr=obj.stderr.read()            print("stdout-----------",len(stdout),len(stderr))            #第一步:制作报头:            total_size=len(stdout)+len(stderr)            header=struct.pack('i',total_size)            #第二步:先发报头(固定长度)            conn.send(header)            #第三步:发送命令的结果            conn.send(stdout)            conn.send(stderr)        except ConnectionResetError:            break    conn.close()server.close()

客户端:

import structfrom socket import *client=socket(AF_INET,SOCK_STREAM)client.connect(('127.0.0.1',8085))while True:    cmd=input('>>: ').strip()    if not cmd:continue    client.send(cmd.encode('utf-8'))    #第一步:收到报头    header=client.recv(4)    total_size=struct.unpack('i',header)[0]    #第二步:收完整真实的数据    recv_size=0    res=b''    while recv_size < total_size:        recv_data=client.recv(1024)        res+=recv_data        #控制条件        recv_size+=len(recv_data)    print(res.decode('gbk'))client.close()

粘包解决方案2

接下来看下解决粘包的第二种方案
服务端:

import subprocessimport structimport jsonfrom socket import *server=socket(AF_INET,SOCK_STREAM)server.bind(('127.0.0.1',8089))server.listen(5)while True:    conn,addr=server.accept()    print(addr)    while True:        try:            cmd=conn.recv(8096)            if not cmd:break #针对linux            #执行命令            cmd=cmd.decode('utf-8')            #调用模块,执行命令,并且收集命令的执行结果,而不是打印            obj = subprocess.Popen(cmd, shell=True,                                   stdout=subprocess.PIPE,                                   stderr=subprocess.PIPE,                                   )            stdout=obj.stdout.read()            stderr=obj.stderr.read()            # 1:先制作报头,报头里放:数据大小, md5, 文件            header_dic = {                'total_size':len(stdout)+len(stderr),                'md5': 'xxxxxxxxxxxxxxxxxxx',                'filename': 'xxxxx',                'xxxxx':'123123'            }            header_json = json.dumps(header_dic)            header_bytes = header_json.encode('utf-8')            header_size = struct.pack('i', len(header_bytes))            print(len(header_bytes),header_size)            # 2: 先发报头的长度            conn.send(header_size)            # 3:先发报头            conn.send(header_bytes)            # 4:再发送真实数据            conn.send(stdout)            conn.send(stderr)        except ConnectionResetError:            break    conn.close()server.close()

客户端:

import structimport jsonfrom socket import *client=socket(AF_INET,SOCK_STREAM)client.connect(('127.0.0.1',8089))while True:    cmd=input('>>: ').strip()    if not cmd:continue    client.send(cmd.encode('utf-8'))    # 1:先收报头长度    obj = client.recv(4)    header_size = struct.unpack('i', obj)[0]    # 2:先收报头,解出报头内容    header_bytes = client.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']    # 3:循环收完整数据    recv_size=0    res=b''    while recv_size < total_size:        recv_data=client.recv(1024)        res+=recv_data        recv_size+=len(recv_data)    print(res.decode('gbk'))client.close()
原创粉丝点击