一. 粘包现象

1. 粘包现象的由来
(1)TCP属于长连接,当服务端与一个客户端进行了连接以后,其他客户端需要(排队)等待.若服务端想要连接另一个客户端,必须首先断开与第一个客户端的连接.
(2)缓冲区(参考资料):
a. 缓冲区(buffer),它是内存空间的一部分.也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区,显然缓冲区是具有一定大小的.
b. 每个socket(套接字)被创建后,都会分配两个缓冲区: 输入缓冲区和输出缓冲区.
c. write()/send()并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器. 一旦将数据写入到缓冲区,函数就已经完成任务可以成功返回了,而不用去考虑数据何时被发送到网络,也不用去考虑数据是否已经到达目标机器,因为这些后续操作都是TCP协议负责的事情.
d. TCP协议独立于 write()/send() 函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况,当前线程是否空闲等诸多因素,不由程序员控制.
e. read()/recv() 函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取.
f. 这些I/O缓冲区特性可整理如下:
1). I/O缓冲区在每个TCP套接字中单独存在
2). I/O缓冲区在创建套接字时自动生成
3). 即使关闭套接字也会继续传送输出缓冲区中遗留的数据
4). 关闭套接字将丢失输入缓冲区中的数据
5). 输入/输出缓冲区的默认大小一般是8K(了解:可以通过getsockopt()函数获取)
g. 如果一次性最多输入/输出的数据量超出了缓冲区的大小,系统就会报错.例如,在UDP协议下,一个数据包的大小超过了一次recv()方法能接受数据量大小,就会报错.

2. subprocess模块 的简单用法
import subprocess
cmd = imput("请输入指令>>>")
result = subprocess.Popen(
cmd, # 字符串指令: "dir","ipconfig",等等
shell=True, # 使用shell,就相当于使用cmd窗口
stderr=subprocess.PIPE, # 标准错误输出,凡是输入错误指令,错误指令输出的报错信息就会被它拿到
stdout=subprocess.PIPE, # 标准输出,正确指令的输出结果会被它拿到
)
print(result.stdout.read().decode("gbk"))
print(result.stderr.read().decode("gbk"))
注意: 如果是在windows操作系统下,那么result.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码且只能从管道里读一次结果,PIPE称为管道.

3. 粘包现象模拟(tcp协议下)
(1)发送方连续发送较小的数据,并且每次发送之间的时间间隔很短,此时,两个消息在输出缓冲区黏在一起了.原因是TCP为了传输效率,做了一个优化算法(Nagle),减少连续的小包发送(因为每个消息被包裹以后,都会有两个过程:组包和拆包,这两个过程是极其消耗时间的,优化算法Magle的目的就是为了减少传输时间) 服务端(接收方):
import socket
server = socket.socket()
ip_port = ("192.168.15.28", 8001)
server.bind(ip_port)
server.listen()
conn, addr = server.accept() # 连续接收两次消息
from_client_msg1 = conn.recv(1024).decode("utf-8")
print("第一次接收到的消息>>>", from_client_msg1)
from_client_msg2 = conn.recv(1024).decode("utf-8")
print("第二次接收到的消息>>>", from_client_msg2) conn.close()
server.close()
客户端(发送方):
import socket
client = socket.socket()
server_ip_port = ("192.168.15.28", 8001)
client.connect(server_ip_port) # 连续发送两次消息
client.send('Hello'.encode('utf-8'))
client.send('World'.encode('utf-8')) client.close()
先运行服务端,再运行客户端,最终在服务端看到的执行结果是:
第一次接收到的消息>>> HelloWorld
第二次接收到的消息>>>
客户端连续发送了两次消息,第一次发送"Hello",第二次发送"World".双方执行程序后却发现,服务端本应接收到两个消息,然而实际情况是:服务端在第一次就收到了所有消息,并且两个消息紧紧靠在一起,出现了"粘包现象"!
(2)发送方第一次发送的数据比接收方设置的"一次接收消息的大小"要大,于是会出现一次接收不完的情况.因此,接收方第二次再接收的时候,就会将第一次剩余的消息接收到,从而与后续消息粘结住,产生粘包现象.

二. 粘包的解决方案
解决方案(一):粘包问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕"如何让发送端在发送数据前,把自己将要发送的字节流总长度让接收端知晓".
解决步骤:
a. 发送端把"数据长度"传输给接收端
b. 接收端把"确认信息"传输给发送端
c. 发送端把"全部数据"传输给接收端
d. 接收端使用一个死循环接收完所有数据. 服务端:
代码流程: (服务端是发送端,客户端是接收端)
a. 服务端接收客户端的cmd指令
b. 服务端通过subprocess模块,从电脑系统中,拿到cmd指令返回值
c. 服务端拿到cmd指令返回值的"字节流长度", 并将其传输给客户端
d. 服务端接收来自客户端的"确认信息"
e. 服务端把"cmd指令返回值"传输给客户端
import socket
import subprocess server = socket.socket()
ip_port = ('192.168.15.28',8001)
server.bind(ip_port)
server.listen()
conn,addr = server.accept() while 1:
from_client_cmd = conn.recv(1024).decode('utf-8') # a.接收来自客户端的cmd指令
sub_obj = subprocess.Popen(
from_client_cmd, # 客户端的指令
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
) server_cmd_msg = sub_obj.stdout.read() # b.拿到cmd指令返回值 --> stdout接受到的返回值是bytes类型的,并且windows系统的默认编码为gbk
cmd_msg_len = str(len(server_cmd_msg)) # c.拿到返回值的长度
print("cmd返回的正确信息的长度>>>",cmd_msg_len)
conn.send(cmd_msg_len.encode('gbk')) # c.把"长度"传输给客户端
from_client_ack = conn.recv(1024).decode('utf-8') # d.拿到"确认信息" if from_client_ack == "确认":
conn.send(server_cmd_msg) # e.把"cmd指令返回值"传输给客户端
else:
continue
客户端:
代码流程: (服务端是发送端,客户端是接收端)
a. 用户输入cmd指令
b. 客户端把"cmd指令"传输给服务端
c. 客户端接收cmd指令返回值的"字节流长度"
d. 客户端把"确认信息"传输给服务端
e. 客户端通过"字节流长度"设置最大可接收数据量,同时接收"cmd指令返回值"
import socket
client = socket.socket()
server_ip_port = ('192.168.15.28',8001)
client.connect(server_ip_port) while 1:
cmd = input('请输入要执行的指令>>>') # a.用户输入cmd指令
client.send(cmd.encode('utf-8')) # b.把"cmd指令"传输给服务端
from_server_msglen = int(client.recv(1024).decode('gbk')) # c.接收cmd指令返回值的"字节流长度"
print('接收到的信息长度是>>>', from_server_msglen)
client.send('确认'.encode('utf-8')) # d.把"确认信息"传输给服务端
from_server_stdout = client.recv(from_server_msglen).decode('gbk') # e.设置最大可接收数据量,同时接收"cmd指令返回值"
print('接收到的指令返回值是>>>', from_server_stdout)
2. 解决方案(二):通过struct模块将数据实体(要传输的数据)的长度打包成一个"4bytes字符串",并将其传输给接收端.接收端取出这个"4bytes字符串",对其进行解包,解包后的内容就是"数据实体的长度",接收端再通过这个长度来继续接收数据实体.

(1)首先简单认识一下struct模块:
struct模块中最重要的两个函数是:
pack() -- 具有"打包"功能
unpack() -- 具有"解包"功能 1)bytes = struct.pack(format, values)
描述: pack() --> 把"int类型的数据"打包成一个"4bytes字符串"
参数: format --> 数据格式(类型),在这里我们指定该格式为"i"(即int数据类型).
values --> 整数
返回值: 4bytes字符串 2)(values,) = struct.unpack(format, bytes)
描述: unpack() --> 把"4bytes字符串"解包成"int类型的数据"
参数: format --> 数据格式(类型),在这里我们指定该格式为"i"(即int数据类型).
bytes --> "4bytes字符串"
返回值: unpack()函数以元组的形式返回int类型的数据 举例说明:
import struct
num = 100 # 把"int类型的数据"打包成"4bytes字符串"
num_stru = struct.pack("i", num) # "i"代表int数据类型
print(len(num_stru)) # 打印结果: 4
print(num_stru) # 打印结果: b'd\x00\x00\x00' # 把"4bytes字符串"解包成"int类型的数据"
num2 = struct.unpack("i", num_stru) # "i"代表int数据类型
print(num2) # 打印结果: (100,) --> 返回结果是元组!! (2)结合struct模块解决粘包现象 服务端(发送端):
代码流程:
a.拿到数据实体的长度
b.将长度打包成"4bytes字符串"
c.将"4bytes字符串"发送给客户端
d.发送数据实体
import socket
import subprocess
import struct server = socket.socket()
ip_port = ("127.0.0.1", 8001)
server.bind(ip_port)
server.listen()
conn, addr = server.accept() while 1:
from_client_cmd = conn.recv(1024).decode("utf-8")
print("来自客户端的指令是>>>") # 通过subprocess模块拿到指令的返回值
sub_obj = subprocess.Popen(
from_client_cmd, # 客户端的指令
shell=True,
stdout=subprocess.PIPE, # 标准输出:接收正确指令的执行结果
stderr=subprocess.PIPE, # 标准错误输出:接收错误指令的执行结果
) # 通过stdout拿到正确指令的执行结果,即需要发送的"数据实体"
server_cmd_msg = sub_obj.stdout.read()
# a.拿到数据实体的长度
cmd_msg_len = len(server_cmd_msg)
# b.将长度打包成"4bytes字符串"
msg_len_stru = struct.pack('i',cmd_msg_len)
# c.将"4bytes字符串"发送给客户端
conn.send(msg_len_stru)
# d.发送数据实体 --> sendall() 循环发送数据,直到数据全部发送成功
conn.sendall(server_cmd_msg)
客户端(接收端):
代码流程:
a.接收打包后的"4bytes字符串"
b.解包,拿到"数据实体的长度"
c.循环接收数据实体,通过"数据实体的长度"来确定跳出循环的条件
import socket
import struct client = socket.socket()
server_ip_port = ("127.0.0.1", 8001)
client.connect(server_ip_port) while 1:
cmd = input("请输入要执行的指令>>>")
client.send(cmd.encode("utf-8"))
# a.接收打包后的"4bytes字符串"
from_server_msglen = client.recv(4)
# b.解包,拿到"数据实体的长度",即unpack_msglen
unpack_msglen = struct.unpack('i', from_server_msglen)[0] # c.循环接收数据实体,通过"数据实体的长度"来确定跳出循环的条件
recv_msg_len = 0 # 统计"数据长度"
all_msg = b'' # 统计"数据实体"
while recv_msg_len < unpack_msglen:
every_recv_data = client.recv(1024)
# 将每次接收到的"数据实体"进行拼接
all_msg += every_recv_data
# 将每次接收到的"数据实体的长度"进行累加
recv_msg_len += len(every_recv_data)
print(all_msg.decode("gbk"))

python 粘包现象的更多相关文章

  1. python socket的应用 以及tcp中的粘包现象

    1,socket套接字 一个接口模块,在tcp/udp协议之间的传输接口,将其影藏在socket之后,用户看到的是socket让其看到的. 在tcp中当做server和client的主要模块运用 #s ...

  2. Python网络编程04 /recv工作原理、展示收发问题、粘包现象

    Python网络编程04 /recv工作原理.展示收发问题.粘包现象 目录 Python网络编程04 /recv工作原理.展示收发问题.粘包现象 1. recv工作原理 2. 展示收发问题示例 发多次 ...

  3. Python网络编程(2)-粘包现象及socketserver模块实现TCP并发

    1. 基于Tcp的远程调用命令实现 很多人应该都使用过Xshell工具,这是一个远程连接工具,通过上面的知识,就可以模拟出Xshell远程连接服务器并调用命令的功能. Tcp服务端代码如下: impo ...

  4. Python之路(第三十一篇) 网络编程:简单的tcp套接字通信、粘包现象

    一.简单的tcp套接字通信 套接字通信的一般流程 服务端 server = socket() #创建服务器套接字 server.bind() #把地址绑定到套接字,网络地址加端口 server.lis ...

  5. python之路--subprocess,粘包现象与解决办法,缓冲区

    一. subprocess 的简单用法 import subprocess sub_obj = subprocess.Popen( 'dir', #系统指令 shell=True, #固定方法 std ...

  6. python 全栈开发,Day35(TCP协议 粘包现象 和解决方案)

    一.TCP协议 粘包现象 和解决方案 黏包现象让我们基于tcp先制作一个远程执行命令的程序(命令ls -l ; lllllll ; pwd)执行远程命令的模块 需要用到模块subprocess sub ...

  7. python网络编程基础之socket粘包现象

    粘包现象两种 登陆 #服务端import json import socket server=socket.socket()#创建socket对象 ip_port=('127.0.0.1',8001) ...

  8. python笔记8 socket(TCP) subprocess模块 粘包现象 struct模块 基于UDP的套接字协议

    socket 基于tcp协议socket 服务端 import socket phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 买 ...

  9. Python之网路编程之粘包现象

    一.什么是粘包 须知:只有TCP有粘包现象,UDP永远不会粘包 粘包不一定会发生 如果发生了:1.可能是在客户端已经粘了 2.客户端没有粘,可能是在服务端粘了 首先需要掌握一个socket收发消息的原 ...

随机推荐

  1. jwt、token

    什么是JWT jwt是一段密文;然而密码是如何产生的? 密码是由三个部分生成: 1.JWT头:JWT头部分是一个描述JWT元数据的JSON对象:{"alg":"hash2 ...

  2. python+Appium自动化:读取Yaml配置文件

    Yaml简介 Yaml:"Yet Another Markup Language"(是一种标记语言),但为了强调这种语言以数据做为中心,而不是以标记语言为重点,而用反向缩略语重命名 ...

  3. IIS配置web.config 将带www域名转为不带www域名

    在configuration节点中添加 <configuration> <system.webServer> <rewrite> <rules> < ...

  4. zabbix内存溢出解决方法

    1406:20180802:183248.783 __mem_malloc: skipped 0 asked 48 skip_min 4294967295 skip_max 0 1406:201808 ...

  5. python pip 出现locations that require TLS/SSL异常处理方法

    python pip 出现locations that require TLS/SSL异常处理方法 转载 郑才华 发布于2018-03-24 21:41:16 阅读数 51844 收藏 展开 最近在r ...

  6. Gym - 101981B Tournament (WQS二分+单调性优化dp)

    题意:x轴上有n个人,让你放置m个集合点,使得每个人往离他最近的集合点走,所有人走的距离和最短. 把距离视为花费,设$dp[i][k]$表示前i个人分成k段的最小花费,则有递推式$dp[i][k]=m ...

  7. C# 继承(3)持续更新

    类继承 和 接口继承 类继承        一个类型派生于一个基类行,它拥有该基类型的所有成员字段和函数. 接口继承     一个类型继承函数的签名,不需要实现代码. 多重继承 一个类派生自多个类.多 ...

  8. 关于iar intrinsics.h is already included previously!报错的问题及解决办法

    用最新的cubemx生成f103的代码(带freertos系统),如果用iar编译,可能会出现intrinsics.h is already included previously!的错误,如果没有待 ...

  9. 2018-2019 ACM-ICPC Nordic Collegiate Programming Contest (NCPC 2018) A. Altruistic Amphibians (DP)

    题目链接:https://codeforc.es/gym/101933/problem/A 题意:有 n 只青蛙在一个坑里面,要求可以跳出坑的青蛙的最大数量.每个青蛙有 3 种属性:l 为青蛙一次可以 ...

  10. 01_初识redis

    1.redis和mysql mysql是一个软件,帮助开发者对一台机器的硬盘进行操作. redis是一个软件,帮助开发者对一台机器的内存进行操作 汽车之家,如果硬盘挂掉了,页面还能访问1个月 关键字: ...