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

2024-08-31 22:38

本文主要是介绍python基础-tcp粘包、解决方案、subprocess执行shell命令,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

      • 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-5D34E:\python\py_pro\1 作业(粘包问题) 的目录2017/11/28  15:46    <DIR>          .
2017/11/28  15:46    <DIR>          ..
2017/11/28  15:46               289 subprocess模块.py
2017/11/28  13:39                72 tcp的nagle算法
2017/11/28  13:39               282 客户端.py
2017/11/28  13:39               946 服务端.py4 个文件          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 subprocess
from 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:breakconn.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:continueclient.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(大小)的方式都是不可取的,这个例子只是引出一个概念就是粘包

粘包现象

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

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

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

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

TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头,实验略

udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠

tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

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

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

客户端粘包

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

服务端:

import time
from 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 time
from 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 time
from 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 time
from 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' 4
68999
8

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

服务端

import subprocess
import struct
from 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:breakconn.close()server.close()

客户端:

import struct
from socket import *client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8085))while True:cmd=input('>>: ').strip()if not cmd:continueclient.send(cmd.encode('utf-8'))#第一步:收到报头header=client.recv(4)total_size=struct.unpack('i',header)[0]#第二步:收完整真实的数据recv_size=0res=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 subprocess
import struct
import json
from 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:breakconn.close()server.close()

客户端:

import struct
import json
from socket import *client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8089))while True:cmd=input('>>: ').strip()if not cmd:continueclient.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=0res=b''while recv_size < total_size:recv_data=client.recv(1024)res+=recv_datarecv_size+=len(recv_data)print(res.decode('gbk'))client.close()

这篇关于python基础-tcp粘包、解决方案、subprocess执行shell命令的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1125190

相关文章

jupyter代码块没有运行图标的解决方案

《jupyter代码块没有运行图标的解决方案》:本文主要介绍jupyter代码块没有运行图标的解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录jupyter代码块没有运行图标的解决1.找到Jupyter notebook的系统配置文件2.这时候一般会搜索到

利用Python快速搭建Markdown笔记发布系统

《利用Python快速搭建Markdown笔记发布系统》这篇文章主要为大家详细介绍了使用Python生态的成熟工具,在30分钟内搭建一个支持Markdown渲染、分类标签、全文搜索的私有化知识发布系统... 目录引言:为什么要自建知识博客一、技术选型:极简主义开发栈二、系统架构设计三、核心代码实现(分步解析

基于Python实现高效PPT转图片工具

《基于Python实现高效PPT转图片工具》在日常工作中,PPT是我们常用的演示工具,但有时候我们需要将PPT的内容提取为图片格式以便于展示或保存,所以本文将用Python实现PPT转PNG工具,希望... 目录1. 概述2. 功能使用2.1 安装依赖2.2 使用步骤2.3 代码实现2.4 GUI界面3.效

Python获取C++中返回的char*字段的两种思路

《Python获取C++中返回的char*字段的两种思路》有时候需要获取C++函数中返回来的不定长的char*字符串,本文小编为大家找到了两种解决问题的思路,感兴趣的小伙伴可以跟随小编一起学习一下... 有时候需要获取C++函数中返回来的不定长的char*字符串,目前我找到两种解决问题的思路,具体实现如下:

python连接本地SQL server详细图文教程

《python连接本地SQLserver详细图文教程》在数据分析领域,经常需要从数据库中获取数据进行分析和处理,下面:本文主要介绍python连接本地SQLserver的相关资料,文中通过代码... 目录一.设置本地账号1.新建用户2.开启双重验证3,开启TCP/IP本地服务二js.python连接实例1.

基于Python和MoviePy实现照片管理和视频合成工具

《基于Python和MoviePy实现照片管理和视频合成工具》在这篇博客中,我们将详细剖析一个基于Python的图形界面应用程序,该程序使用wxPython构建用户界面,并结合MoviePy、Pill... 目录引言项目概述代码结构分析1. 导入和依赖2. 主类:PhotoManager初始化方法:__in

Python从零打造高安全密码管理器

《Python从零打造高安全密码管理器》在数字化时代,每人平均需要管理近百个账号密码,本文将带大家深入剖析一个基于Python的高安全性密码管理器实现方案,感兴趣的小伙伴可以参考一下... 目录一、前言:为什么我们需要专属密码管理器二、系统架构设计2.1 安全加密体系2.2 密码强度策略三、核心功能实现详解

Python Faker库基本用法详解

《PythonFaker库基本用法详解》Faker是一个非常强大的库,适用于生成各种类型的伪随机数据,可以帮助开发者在测试、数据生成、或其他需要随机数据的场景中提高效率,本文给大家介绍PythonF... 目录安装基本用法主要功能示例代码语言和地区生成多条假数据自定义字段小结Faker 是一个 python

Python实现AVIF图片与其他图片格式间的批量转换

《Python实现AVIF图片与其他图片格式间的批量转换》这篇文章主要为大家详细介绍了如何使用Pillow库实现AVIF与其他格式的相互转换,即将AVIF转换为常见的格式,比如JPG或PNG,需要的小... 目录环境配置1.将单个 AVIF 图片转换为 JPG 和 PNG2.批量转换目录下所有 AVIF 图

Python通过模块化开发优化代码的技巧分享

《Python通过模块化开发优化代码的技巧分享》模块化开发就是把代码拆成一个个“零件”,该封装封装,该拆分拆分,下面小编就来和大家简单聊聊python如何用模块化开发进行代码优化吧... 目录什么是模块化开发如何拆分代码改进版:拆分成模块让模块更强大:使用 __init__.py你一定会遇到的问题模www.