(python)解决TCP下的粘包问题

只有TCP有粘包问题,而UDP永远不会粘包。我们先掌握一个socket收发消息的原理:


    服务端可以1kb,1kb地发向客户端送数据,客户端的应用程序可以在缓存当中2kb,2kb地取走数据,当然也可以更多,或都更少。也就是说,应用程序 看到的数据是来个整体。或者说是一个流。一条消息有多少字节对应用程序是不可见的,TCP协议是面向流的协议,这就是它容易粘包的问题原因。

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

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

粘包情况一:发送端要等缓冲区满才发送出去(即发送的数据量少且时间间隔短,会合到一起),造成粘包。

1、服务端

from socket import *

server=socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5)

conn,client_addr=server.accept()

res1=conn.recv(1024)
print('第一次:',res1)
res2=conn.recv(1024)
print('第二次:',res2)

conn.close()
server.close()

2、客户端

from socket import *

client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))

client.send(b'hello')
client.send(b'world')
client.close()

先启动服务端,后再启动客户端,服务端得到的结果为:


粘包情况二:客户端发关了一段数据,服务端只收了一小部分,服务端下次再收的时候不是从缓冲区拿上次遗留的数据,产生粘包。

情况一的,客户端不变,服务端略作修改,如下:

from socket import *

server=socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5)

conn,client_addr=server.accept()

res1=conn.recv(2)
print('第一次:',res1)
res2=conn.recv(3)
print('第二次:',res2)

conn.close()
server.close()
先启动服务端,后再启动客户端,服务端得到的结果为:


现在我们来想一下如何处理粘包的方法。粘包的问题根源是接收端不知发送端将要传送的字节流,所以我们要让发送端在发送数据前,把要发送的字节流总大小让接收端知晓,然后接收端来个循环将其全部接收。这种方法比较低级,因为程序的运行速度运快于网络传输速度,所以在发送一段字节前,先发送该字节流的长度,这种方式会放大网络延迟带来的性能损耗。

目前比较合理的处理方法是:为字节流加上一个报头,将这个报告做成字典,字典里包含将要发送的真实数据详细信息。然后将这个字典JSON序列化,然后用struck将序列化后的数据长度打包成4个字节(4个字节完全够用)

发送时:先发报头长度,再编码报头内容然后发送,最后发真实数据。

接收时:用struct取出报头长度,然后根据长度取出报头,解码,反序列化。最后从反序列化的结果中取出待取数据的详细信息,然后取真实的数据内容。

来看实现的代码:

1、服务端

from socket import *

import subprocess
import struct
import json

server=socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5)
while True:
    conn,client_addr=server.accept()
    print(conn,client_addr)#(连接对象,客户端的ip和端口)
    while True:
        try:
            cmd=conn.recv(1024)
            obj=subprocess.Popen(
                cmd.decode('utf-8'),
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
                                 )
            stdout=obj.stdout.read()
            stderr=obj.stderr.read()
            #1、制作报头
            header_dic={
                'total_size':len(stdout)+len(stderr),
                'md5':'dgdsfsdfdsdfsfewrewge',
                'file_name':'a.txt'
            }
            header_json=json.dumps(header_dic)
            header_bytes=header_json.encode('utf-8')
            #2、先发送报头的长度
            header_size=len(header_bytes)
            conn.send(struct.pack('i',header_size))
            #3、发送报头
            conn.send(header_bytes)
            #4、发送真实的数据
            conn.send(stdout)
            conn.send(stderr)

        except ConnectionResetError:
            break
    conn.close()
server.close()

2、客户端

from socket import *
import json
import struct

client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))

while True:
    cmd=input(">>:").strip()
    if not cmd:continue
    client.send(cmd.encode('utf-8'))

    #1、接收报文头的长度
    header_size=struct.unpack('i',client.recv(4))[0]
    #2、接收报文
    header_bytes=client.recv(header_size)

    #3、解析报文
    header_json=header_bytes.decode('utf-8')
    header_dic=json.loads(header_json)
    print(header_dic)

    #4、获取真实数据的长度
    totol_size=header_dic['total_size']

    #5、获取数据
    recv_size=0
    res=b''
    while recv_size<totol_size:
        recv_date=client.recv(1024)
        res+=recv_date
        recv_size+=len(recv_date)

    print(res.decode('gbk'))
client.close()



猜你喜欢

转载自blog.csdn.net/miaoqinian/article/details/80020291
今日推荐