QTcpSocket readAll无法接收大数据(65536个字节)

2,369 阅读5分钟

QTcpSocket readAll无法接收大数据(65536个字节)

1.引入

当我使用Qt的network模块进行图片的传输时. 我发现图片大小较小的能够成功被传输, 而图片大小较大的却无法成功被传输.

2.复现

1.建立Server端

为了更好的演示,我编写一个GUI程序作为服务器程序

建立Server端项目的时候, 我使用了MainWindow为窗口父类并且设置编译套件为Mingw.

项目结构:

image.png

下面是UI,text是最好的描述:

image.png 不多演示细节,我确保我一定能够选择到一张图片,所以我并不考虑文件选择器的对话框意外退出.

在窗口类内部定义以下成员

std::unique_ptr<QPixmap> image_;
std::unique_ptr<QTcpServer> server_;
std::unique_ptr<QTcpSocket> client_;

client_是我们客户端的连接,我们假设有且只有这一个客户端.

下面是选择文件的一些操作,并没有限制后缀名因为演示的需要.

QString path { QFileDialog::getOpenFileName() };
image_.reset(new QPixmap(path));
ui->pathLbl->setText(path);

那这样,我们就可以获取图片,现在就是发送的问题了。

发送之前,我们需要将图片转换为数据,也就是QPixmap转换为QByteArray

QByteArray MainWindow::getData(const QPixmap &image)
{
    QByteArray result;
    QBuffer buffer(&result);
    image.save(&buffer, "PNG");
    return result;
}

那我们现在编写send按钮的click信号对应的槽函数。

当然,这是这个文章讨论的主要问题

void MainWindow::on_sendBtn_clicked()
{
    client_->write(getData(*image_.get()));
}

在对客户端进行传输之前我们要让服务器保持监听

在MainWindow类的构造函数添加了以下的初始化列表

server_(new QTcpServer(this))

在MainWindow类的构造函数内添加了以下代码

server_->listen(QHostAddress::Any, 8888);
connect(server_.get(), &QTcpServer::newConnection, [this]{
    client_.reset(server_->nextPendingConnection());
});

这样,服务器就完成了,现在我们来搭建客户端接收图片

2.搭建Client端

项目结构和搭建服务端是一样的

image.png

接下来,我们添加一个QLabel控件放置在主窗口用于展示服务器发送过来的图片。

image.png

当然,网络传输的是字节数据,我们将它转换为图片

QPixmap MainWindow::getImage(const QByteArray &data)
{
    QPixmap image;
    image.loadFromData(data, "PNG");
    return image;
}

image.png 进行设置

void MainWindow::onNewMsg()
{
    ui->imageLbl->setPixmap(
      getImage(socket_->readAll())
    );
}

上面是一张图片,用于测试,它的大小是2688 字节

image.png

可以看到,上面的图片是成功发送的。

客户端的输出为2720(数据包的大小)

显然,这个图片比较小,我们换一张较大的图片

image.png 上面是我当前桌面的截图,我们尝试发送这个图片

image.png 什么都没有,貌似数据是有错误的,我们现在看看客户端的输出

65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 65536 63698.

总之,veryveryverylong。

3.解决问题

既然输出这么多的size,那么必定是槽函数进行了多次的响应,在这些数据中,我们不难发现,65536是最大的,这表明readAll最多读取65536字节的数据,那么我们应该如何接收这么大的图片呢?

给数据设置固定头部

这个原理就是给要发送的数据前面加上一个固定的头部,对于一个固定的头部,我没有像其他博主一样添加一个固定的head,而是添加了一个count,count存储该数据需要被读取的次数,我定义这个头部如下

count+size

注意,"+"需要省略,例如我们要传输100次,头部就是下面这个样子

count100

可以看到,这个字符串的大小是sizeof("count") /sizeof(char)+ sizeof("100")/sizeof(char) 为8,当然,count是写死的,它的长度始终为5,那么我们只需要考虑value。

对于value,我们定义它为4位数,那么如何计算value呢?

我们将count+size作为一个size为9的字符串,因为value最大是4位数,那么数据大小dataSize = 原始数据大小+9,则value为dataSize / 65536 + 1, 可以设想如果dataSize < 65536的情况,这样dataSize/65536是0,所以加一,这样就可以得到我们的count.

这样,我们需要重写服务端发送按钮的槽函数

void MainWindow::on_sendBtn_clicked()
{
    QByteArray data;
    QByteArray imageData = getData(*image_.get());
    data.push_back("count");
    data.push_back(QByteArray::fromStdString(std::to_string(imageData.size() / 65536 + 1)));
    while(data.size() < 9){
        data.push_back(' ');
    }
    data.push_back(imageData);
    qDebug() << data.toStdString().substr(0, 9).c_str();
    qDebug() << imageData.size();
    client_->write(data);
}

在客户端,我们接收到数据时,我们先判断是否有count,如果有count就接收count次数据,接收期间再也不判断是否有count。

void MainWindow::onNewMsg()
{
    static QPixmap image;
    static QByteArray data;
    static int count = 0;
    QByteArray networkData = socket_->readAll();
    qDebug() << networkData.size();
    if(count == 0 && networkData.contains("count")){
        // 读取count
        qDebug() << networkData.toStdString().substr(0, 9).c_str();
        count = std::stoi(networkData.toStdString().substr(5, 4));
        qDebug() << "count is" << count;
        // 移除count+size头
        networkData.remove(0, 9);
        qDebug() << networkData.toStdString().substr(0, 9).c_str();
        data.clear(); // 清除原有数据
    }
    data.push_back(networkData);
    // 如果count为0,读取完成
    if(--count == 0){
        qDebug() << "ok";
        image = getImage(data);
        qDebug() << image.size();
        ui->imageLbl->setPixmap(image);
    }
}

接下来我把Label和窗口大小都设置为图片大小

// 如果count为0,读取完成
if(--count == 0){
    qDebug() << "ok";
    image = getImage(data);
    qDebug() << image.size();
    ui->imageLbl->move(0,0);
    resize(image.size());
    ui->imageLbl->resize(image.size());
    ui->imageLbl->setPixmap(image);
}

image.png

图片太大了不好展示出来,但是是成功展示的。

一张更小的图片

image.png