TCP粘包以及解决方案

Posted msj513

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TCP粘包以及解决方案相关的知识,希望对你有一定的参考价值。

TCP协议粘包现象的说明:

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

粘包会发生什么?

这时候,接受不知道数据的限界,就没有办法正确的解析对方传输过来的限界。就才去了类似通信协议的解决方案,处理粘包问题。

简单解决问题的方法。根据当前需要发送的数据的大小传输数据的二进制长度先发送给客户端,在根据传输数据的长度来获取的真实的数据。

实现如下服务端

import socket,subprocess,struct
server = socket.socket()
server.bind(("127.0.0.1",54321))
server.listen(5)

while True:
    client, addr = server.accept()
    while True:
        try:

            cmd = client.recv(1024).decode("utf-8")#接受命令
            if not cmd:
                print("client closed")
                client.close()
                break
            p = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            res = p.stdout.read()+p.stderr.read()#获得返回的数据
            res_len =len(res)#整形形式的需要转换为固定长度的二进制形式。使用struct模块
            bytes_len = struct.pack("i",res_len)
            client.send(bytes_len)
            client.send(res)
        except ConnectionResetError:
            print("客户端错误退出")
            client.close()
            break
server.close()

客户端

import socket,struct

client =socket.socket()
client.connect(("127.0.0.1",54321))

while True:
    cmd = input(">>>:")
    if not cmd:continue
    client.send(cmd.encode("utf-8"))
    #获取数据的长度的二进制
    bytes_len = client.recv(1024)
    #解压成为整形
    res_len = struct.unpack("i",bytes_len)[0]
    #接受数据的方式
    final_data=b""
    #当前接受数据的长度计数器
    data_len = 0
    while data_len<res_len:
        res = client.recv(1024)
        final_data += res
        data_len += len(res)
    print(final_data.decode("gbk"))

client.close()

其实我们还以定制更加复杂的数据头

可以字典形式表现,实现传输更加复杂文件,甚至可以进行校验

服务端

import socket,struct,json

server = socket.socket()
server.bind(("127.0.0.1",3333))
server.listen(5)
while True:
    client,addr = server.accept()
    while True:
        try:
            ml = client.recv(1024).decode("utf-8")#模拟客户端请求报告
            if not ml:
                client.close()
                break
            if ml != "1":continue
            #开始资质表头
            filename = "总结"
            #计算文件bytes长度
            file_len = 0
            f = open(r"F:Python_exeday34上午总结","rb")
            for line in f:
                line_len = len(line)
                file_len += line_len
            f.close()
            file_dict = {"filename":filename,"length":file_len}
            #将字典装换为json格式
            json_dict = json.dumps(file_dict)
            #再json字符串转为bytes
            byte_dict = json_dict.encode("utf-8")
            #计算字典二进制的长度
            head_len = len(byte_dict)
            #将长度打包成一个数据头
            head = struct.pack("i", head_len)
            client.send(head)
            client.send(byte_dict)
            #标记内容完成
            #传输正式内容
            with open(r"F:Python_exeday34上午总结","rb") as f:
                for line in f:
                    client.send(line)


        except ConnectionResetError:
            print("连接错误连接")
            client.close()
            break

客户端

import socket,struct模块,json

client = socket.socket()
client.connect(("127.0.0.1",3333))

while True:
    ml = input(">>>:")
    if not ml:continue
    client.send(ml.encode("utf-8"))
    #获取字典的二进制
    bytes_len = client.recv(4)
    #还原
    head_len =struct模块.unpack(i, bytes_len)[0]
    #获取数据报头
    bytes_dict = client.recv(head_len)
    #将bytes类型转化为json格式
    json_dict = bytes_dict.decode("utf-8")
    #将字典json格式还原为成python的字典

    file_dict=json.loads(json_dict)
    #获取文件的名称
    filename = file_dict["filename"]
    #获取文件的传输长度
    file_len = file_dict["length"]
    #打开文件追加写
    f = open(filename,"ab")
    #当前接受长度
    data_len = 0

    while data_len<file_len:
        data = client.recv(1024)
        data_len += len(data)
        f.write(data)
    f.close()
    client.close()

client.close()
解决粘包的方案  自定义报头
1.先用报头传输数据的长度
对于我们远程CMD程序来说 只要先传输长度就能解决粘包的问题
但是如果做得是一个文件上传下载 除了数据的长度 还需要传输文件的名字 md5等等信息
又该如何?

2.自定义复杂报头 完成发送一些额外的信息 例如文件名
1.将要发送的额外数据打包成一个字典
2.将字典转为bytes类型
3.计算字典的bytes长度 并先发送
4.发送字典数据
5.发送真实数据











以上是关于TCP粘包以及解决方案的主要内容,如果未能解决你的问题,请参考以下文章

TCP粘包以及解决方案

tcp的粘包和拆包示例以及使用LengthFieldFrameDecoder来解决的方法

socket网络编程:粘包现象以及解决方法(代码完善)

Netty如何解决粘包以及拆包问题

TCP粘包,拆包及解决方法

TCP粘包和拆包