如何解决TCP传输的“粘包“问题

发布于:2025-06-25 ⋅ 阅读:(15) ⋅ 点赞:(0)

关于TCP传输过程中的"粘包"问题,目前有几种常见的解决方案:
1.传输定长数据内容,接收端每次解析固定长度的数据
2.使用特殊字符作为数据包的边界,接收端解析到特殊字符则认为是一个数据包
3.使用 固定字节头部 + 不定长消息体;例如固定4字节的头部,存储的是消息体的长度,接收端解析时,先解析4个字节的头部信息,获取到消息体的长度len,再继续读取len个字节的数据,即为一个"包"


本文使用第三种方案,演示在网络编程中对发送数据进行封包 以及 接收数据时的解包 操作

QTcpSpcket 收发数据

1.发送

QTcpSocket *client = new QTcpSocket();

connect(ui->btnSend,&QPushButton::clicked,[this]
{
    //判断是可操作,isValid表示准备好读写
    if(!client->isValid())
        return;
    // 模拟不同长度的数据包
    std::vector<std::string> vec{"1111",
                                 "22222222",
                                 "3333333333333333",
                                 "4444444444444444444444",
                                 "5555555555555555555555555555",
                                 "6666666666666666666666",
                                 "77777777777777777777",
                                 "8888888888888",
                                 "99999999999999999",
                                 "00000000000000000000000"};
    
    for (auto & item : vec)
    {
        sockets::TcpPacket packet(item);        // 使用TcpPacket结构包装, 添加头部长度信息
        client->write(packet.getData(), packet.getLenght());
    }
});

2.接收数据

sockets::Buffer *m_buffer = new sockets::Buffer();
 //收到数据,触发readyRead
    connect(client,&QTcpSocket::readyRead,[this]
    {
        if (client->bytesAvailable() > 0)
        {
            QByteArray recv = client->readAll();
            m_buffer->append(recv.data(), recv.length());

            while (m_buffer->readableBytes() >= sockets::TcpPacket::HEADER_LENGTH)
            {
                const void* data = m_buffer->peek();
                int32_t be32 = *static_cast<const int32_t*>(data); // SIGBUS
                const int32_t len = sockets::util::networkToHost32(be32);
                if (len > 65536 || len < 0)
                {
                    break;	// 长度错误
                }
                else if (m_buffer->readableBytes() >= len + sockets::TcpPacket::HEADER_LENGTH)
                {
                    m_buffer->retrieve(sockets::TcpPacket::HEADER_LENGTH);
                    std::string msg = m_buffer->retrieveAsString(len);
                    qDebug() << "receive data:" << msg.c_str();
                }
                else
                {
                    break;	// 不够一个完整包,等待下次接收
                }
            }
        }
    });

muduo 收发数据

    void onMessage(const TcpConnectionPtr &conn, Buffer *buf, Timestamp time)
    {
        while (buf->readableBytes() >= sockets::TcpPacket::HEADER_LENGTH) // kHeaderLen == 4
        {
            // FIXME: use Buffer::peekInt32()
            const void* data = buf->peek();
            int32_t be32 = *static_cast<const int32_t*>(data); // SIGBUS
            const int32_t len = networkToHost32(be32);
            if (len > 65536 || len < 0)
            {
                conn->shutdown();  // FIXME: disable reading
                break;
            }
            else if (buf->readableBytes() >= len + sockets::TcpPacket::HEADER_LENGTH)
            {
            	// 解包
                buf->retrieve(sockets::TcpPacket::HEADER_LENGTH);
                std::string msg = buf->retrieveAsString(len);
                std::cout << "receive data:" << msg << std::endl;

				// 使用Tcppacket 封包 + 发送
                std::string sendMsg = sockets::TcpPacket(msg).getDataAsString();
                conn->send(sendMsg);
            }
            else
            {
                break;          // 不够一个完整包,等待下次接收
            }
        }
   }