python学习笔记 day32 解决黏包问题

1. 出现黏包现象的本质

黏包现象是TCP协议传输时特有的,当连续send多个小的数据,发送端会连在一起发送 接收端就会一次性接收,就出现黏包现象,这是TCP协议内部的优化算法造成的;

# server.py
import socket
sk=socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
conn,addr=sk.accept()
ret1=conn.recv(12)
print(ret1)
ret2=conn.recv(12)
print(ret2)
ret3=conn.recv(12)
print(ret3)
conn.close()
sk.close()

 

# client.py
import socket
sk=socket.socket()
sk.connect(('127.0.0.1',8080))
sk.send(bytes("hello".encode("utf-8")))
sk.send(bytes("xuan".encode("utf-8")))
sk.close()

 

 

1.  当连续send多个小的数据 发送端会合并在一起发送,接收端只有一个recv时 就会一次性接收,出现黏包现象;

2. 当发送端一次性send一个大数据(发送端在发送时会拆包)接收端分多次recv (第一个recv的字节很小,接受不完,就会由别的recv来接收)也会出现黏包现象;

出现黏包现象的本质是 不知道发送端发送了多大的字节,接收端不知道该接收多大的数据;

 

2. 解决黏包现象方法:

首先,发送一下这个数据有多大,然后按照数据的长度接收数据;

# server.py
import socket
sk=socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(('127.0.0.1',8080))
sk.listen()
conn,addr=sk.accept()
while True:
    cmd=input(">>>")  # 用户输入命令
    conn.send(bytes(cmd.encode("utf-8")))  # 服务器下发命令,由客户端来接收并通过subprocess模块来执行
    num=conn.recv(1024).decode("utf-8")    # 客户端执行命令 会得到stdout 和 stderr 告诉服务端字节数,让服务端以多大的字节数接收,解决黏包现象
    conn.send(bytes("ok received~".encode("utf-8")))  # 服务端收到客户端发送的字节数,给客户端回一个ok 是为了防止客户端发送num字节数大小 的数据太小 跟接下来发送执行命令的结果 合并起来一起发送,还是会出现黏包现象
    ret=conn.recv(int(num)).decode("gbk")  # 客户端发送过来的执行完cmd的内容在windows上是gbk编码的 因为res.stdout.read().decode("gbk")
    print(ret)  # 这样服务端反复下发命令,客户端 执行完之后 给服务端返回结果,服务端一次就可以接受完这个命令执行的结果
                # (因为客户端在发送执行结果之前就告诉服务端 我需要发送多少字节的,让服务端以这些字节去接受,这样就不会出现黏包现象)
conn.close()
sk.close()

 

 

# client.py
import socket
import subprocess

sk=socket.socket()
sk.connect(('127.0.0.1',8080))
while True:
    cmd=sk.recv(1024).decode("utf-8")  # 客户端首先接收服务端发来的命令
    print(cmd)  # 客户端首先打印服务端发来的让客户端来执行的命令 (str类型)
    res=subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) # shell=True 表明是在执行操作系统的命令
    std_out=res.stdout.read()  # 得到的是stdout的字节数  read()的结果是bytes类型
    std_err=res.stderr.read()
    num=str(len(std_out)+len(std_err))  # 将stdout的字节数和stderr的字节数加起来 传给服务端 ,告诉服务端下达的命令我执行的结果需要这么多字节,你就按这个大小的字节数接受,就对应的命令的执行结果,不会出现黏包现象
    sk.send(bytes(num.encode("utf-8")))  # 上面len(std_out)的结果是int 需要先转化为str 然后send 传输时再转化为bytes类型(因为int是无法直接bytes转换的)
    sk.recv(1024).decode("utf-8")  # 服务端接收到客户端发送的命令执行结果的字节数 之后 会给客户端发送一个信息 表明收到了, 之所以客户端在这里接收一下(服务器客户端之间交互了一下)
                                   # 是因为客户端send(num)时 也是一个小数据包,这里如果不先接受一条信息,直接再继续发送执行命令的结果 ,由于tcp协议内部的优化算法,可能会把send(num)这个小数据包,连同要发送的执行命令结果的数据合并在一起发送,造成 stdout  stderr的信息不全
                                   # (因为服务器那边只接受那些字节,前面被num信息占了一部分)后面没发完的信息会再下一次服务器接受时 从缓存拿 ,也有可能会造成黏包现象
    sk.send(std_out)  # 这里std_out是res.stdout.read()得到的字节类型,其实是gbk编码的,服务器在接收执行命令的信息时 也需要按照gbk解码才能拿到具体信息
    sk.send(std_err)
sk.close()

 

运行结果:

 

 

 

 ipconfig 很大 后面在继续执行fr命令时 也没有返回上一条ipconfig命令的信息,而是fr命令的执行结果,所以并没有出现黏包现象!(数据没有发生混乱)

 nice~

 

上面解决黏包现象的方法:

好处:1.   要在文件中配置一个配置项,就是每一次recv的大小;buffer=1024 4096 等;

           2.  当我们需要发送大的数据时,要明确告诉接收方要发送多大的数据,以便接收方可以准确的接收到所有数据;

           3.  一般多用于文件传输;

不好的地方:

           1. 多了一次交互;

 

 

2018-10-07 补充:

为什么会出现黏包问题,怎么解决?

黏包现象只会在TCP协议中才会出现,TCP协议是面向流的协议,在数据传输过程中还有缓存机制,避免数据丢失,因此再连续发送小数据时以及接受大小不符合时,均会出现黏包现象;

本质还是因为在接收数据时不知道发送数据的长短,因此解决黏包现象的关键就是发送端在传输大数据之前需要先告诉接收端要发送的数据大小;

 

posted @ 2018-10-05 12:58  写的BUG代码少  阅读(241)  评论(0编辑  收藏  举报