码迷,mamicode.com
首页 > 编程语言 > 详细

python 粘包现象

时间:2018-10-18 00:48:50      阅读:214      评论:0      收藏:0      [点我收藏+]

标签:连接   产生   解码   bin   res   div   数据   out   默认   

一. 粘包现象

1. 粘包现象的由来
(1)TCP属于长连接,当服务端与一个客户端进行了连接以后,其他客户端需要(排队)等待.若服务端想要连接另一个客户端,必须首先断开与第一个客户端的连接.

(2)缓冲区:
a. 每个socket(套接字)被创建后,都会分配两个缓冲区: 输入缓冲区和输出缓冲区.
b. write()/send()并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器. 一旦将数据写入到缓冲区,函数就已经完成任务可以成功返回了,而不用去考虑数据何时被发送到网络,也不用去考虑数据是否已经到达目标机器,因为这些后续操作都是TCP协议负责的事情.
c. TCP协议独立于 write()/send() 函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况,当前线程是否空闲等诸多因素,不由程序员控制.
d. read()/recv() 函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取.
e. 这些I/O缓冲区特性可整理如下:
1). I/O缓冲区在每个TCP套接字中单独存在
2). I/O缓冲区在创建套接字时自动生成
3). 即使关闭套接字也会继续传送输出缓冲区中遗留的数据
4). 关闭套接字将丢失输入缓冲区中的数据
5). 输入/输出缓冲区的默认大小一般是8K(了解:可以通过getsockopt()函数获取)
f. 如果一次性最多输入/输出的数据量超出了缓冲区的大小,系统就会报错.例如,在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)

 

python 粘包现象

标签:连接   产生   解码   bin   res   div   数据   out   默认   

原文地址:https://www.cnblogs.com/haitaoli/p/9807287.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!