文章目录

0 效果

Qt TCP网络编程——传输图片(附TCP连接逻辑以及完整代码)_数据
Qt TCP网络编程——传输图片(附TCP连接逻辑以及完整代码)_数据_02
Qt TCP网络编程——传输图片(附TCP连接逻辑以及完整代码)_服务器_03
​​​完整代码地址​

1 知识点

1.1 图片编码和解码

png编码为base64数据 :(用于客服端传输)

QByteArray Client::getImageData(const QImage &image)
{
QByteArray imageData;
QBuffer buffer(&imageData);
image.save(&buffer, "png");
imageData = imageData.toBase64();

return imageData;
}

base64数据解码为png :(用于客服端传输)

QImage Server::getImage(const QString &data)
{
QByteArray imageData = QByteArray::fromBase64(data.toLatin1());
QImage image;
image.loadFromData(imageData);
return image;
}

1.2 图片显示(合理缩放图像以填充label)

QImage imageData = getImage(imageContent);
QPixmap resImage = QPixmap::fromImage(imageData);
QPixmap* imgPointer = &resImage;
imgPointer->scaled(ui->imageLabel->size(), Qt::IgnoreAspectRatio);//重新调整图像大小以适应窗口
//法二:替换前面的表达式
// imgPointer->scaled(ui->imageLabel->size(), Qt::KeepAspectRatio);//设置pixmap缩放的尺寸

ui->imageLabel->setScaledContents(true);//设置label的属性,能够缩放pixmap充满整个可用的空间。
ui->imageLabel->setPixmap(*imgPointer);

Qt TCP网络编程——传输图片(附TCP连接逻辑以及完整代码)_客户端_04

1.3 TCP传输与接收

  • 信号:
  • 客户端连接上服务器时,发送​​SIGNAL(connected())​​信号;
  • 客户端的连接被断开时,发送​​SIGNAL(disconnected())​​信号;
  • 客户端中的​​tcpClient->write(outBlock);​​​,发送​​readyRead()​​信号【readyRead()当网络套接字上有新的网络数据有效负载时】;
  • 两个端出现错误时,发出​​SIGNAL(error(QAbstractSocket::SocketError))​​信号;
  • 每当有客户端连接服务器后,会发送​​SIGNAL(newConnection())​​信号;
  • 连接逻辑
  • 1 首先服务器通过​​(QTcpServer 对象).listen(ip地址,端口)​​开始监听端口;
  • 2 客户端通过​​(QTcpSocket* 对象)->connectToHost(ip地址,端口)​​​连接服务器,当连接上服务器时,对于客户端会发出​​SIGNAL(connected())​​​信号,对于服务器端会发送​​SIGNAL(newConnection())​​信号;
  • 3 服务器端收到​​SIGNAL(newConnection())​​​信号后,使用​​(QTcpServer对象).nextPendingConnection()​​ 获得连接套接字;
  • 4 客户端通过​​(QTcpSocket *对象)->write(QByteArray对象)​​​发出​​readyRead()​​信号(把数据写入TCP包中);
  • 5 服务器端收到​​readyRead()​​信号后,开始处理信息,显示图片。
  • 6 服务器端断开连接时​​(QTcpSocket*对象)->disconnectFromHost();​​​,会发出​​SIGNAL(disconnected())​​信号,然后客户端更新状态

1.4 TCP知识

如果只是发送空的图片,就是只有TCP报头的数据报,大小为20字节。

一般以太网帧的大小为[64,1518],除去帧头14字节(6字节目的MAC地址,6字节目的MAC地址,2字节Tye域值)、4字节帧尾(CRC校验),剩下大小为[64,1500]字节,不足64字节会被当作无效帧(因为争用期的存在,即确保在发送数据的同时检测到可能存在的冲突【CSMA/CD协议】)。

一个TCP报文大小最大为1500-20(IP头)-20(TCP头)=1460字节;
一个UPD报文最大为1500-20(IP头)-8(UPD头)=1482字节。

TCP全靠IP曾来分帧(流协议),TCP协议本身会进行拥赛和流利控制。

2 客户端

类的声明

private:
Ui::Client *ui;

QTcpSocket *tcpClient;
QFile *localFile; // 要发送的文件
qint64 totalBytes; // 发送数据的总大小
// qint64 bytesWritten; // 已经发送数据大小
// qint64 bytesToWrite; // 剩余数据大小
qint64 payloadSize; // 每次发送数据的大小(64k) 未用到
QString fileName; // 保存文件路径
QByteArray outBlock; // 数据缓冲区,即存放每次要发送的数据块

QImage image;//图片
QString currentImageName;//图片名

volatile bool isOk;

private slots:
void openFile();//打开文件
void send();//发送
void connectServer();//连接服务器
void startTransfer();//发送图片数据
void displayError(QAbstractSocket::SocketError);//处理错误函数
void tcpConnected();//更新isOk的值,按钮以及label的显示
void tcpDisconnected();//断开连接处理的事件

//图片转base64字符串
QByteArray getImageData(const QImage&);

void on_openButton_clicked();//打开图片
void on_sendButton_clicked();//发送图片
void on_connectButton_clicked();//连接或断开服务器
signals:
void buildConnected();//连接上服务器后,发出的信号

类的构造函数:

Client::Client(QWidget *parent) :
QDialog(parent),
ui(new Ui::Client)
{
ui->setupUi(this);

//地址和端口自动补全以及默认提示
QStringList hostWordList, portWordList;
hostWordList <<tr("127.0.0.1");
portWordList << tr("6666");
QCompleter* completerHost = new QCompleter(hostWordList, this);
QCompleter* completerPort = new QCompleter(portWordList, this);

ui->hostLineEdit->setCompleter(completerHost);
ui->portLineEdit->setCompleter(completerPort);
ui->hostLineEdit->setPlaceholderText(tr("127.0.0.1"));
ui->portLineEdit->setPlaceholderText(tr("6666"));

payloadSize = 64 * 1024; // 64KB
totalBytes = 0;
// bytesWritten = 0;
// bytesToWrite = 0;
isOk = false;

ui->sendButton->setEnabled(false);

tcpClient = new QTcpSocket(this);

// 当连接服务器成功时,发出connected()信号,isOK置为true
connect(tcpClient, SIGNAL(connected()), this, SLOT(tcpConnected()));
//当点击发送按钮后(且isOK为true),发出buildConnected()信号,开始传送数据
connect(this, SIGNAL(buildConnected()), this, SLOT(startTransfer()));
//当断开连接时,发出disconnected(),isOK置为false
connect(tcpClient, SIGNAL(disconnected()), this, SLOT(tcpDisconnected()));
//显示错误
connect(tcpClient, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(displayError(QAbstractSocket::SocketError)));
}

其余成员函数以及槽函数的定义:

void Client::openFile()
{
fileName = QFileDialog::getOpenFileName(this);

if (!fileName.isEmpty()) {

//获得实际文件名
currentImageName = fileName.right(fileName.size()
- fileName.lastIndexOf('/')-1);

ui->clientStatusLabel->setText(tr("打开 %1 文件成功!").arg(currentImageName));

if(isOk == true){
ui->sendButton->setEnabled(true);
}
}
}

void Client::send()
{
if(!isOk){
ui->clientStatusLabel->setText(tr("请先连接服务器"));
return;
}else{
//发射信号
emit buildConnected();
qDebug() << "emit buildConnected()" << endl;
}
}

void Client::connectServer()
{
// 初始化已发送字节为0
// bytesWritten = 0;
ui->clientStatusLabel->setText(tr("连接中…"));

// //连接到服务器
tcpClient->connectToHost(ui->hostLineEdit->text(),
ui->portLineEdit->text().toInt());

isOk = true;
qDebug() << "connectServer: isOk is ok" << endl;
}


void Client::startTransfer()
{
QDataStream sendOut(&outBlock, QIODevice::WriteOnly);
sendOut.setVersion(QDataStream::Qt_5_6);

//获得图片数据
QImage image(fileName);
QString imageData = getImageData(image);

qDebug() << "fileName: " <<fileName << endl;
// qDebug() << "imageData" << imageData << endl;

// 保留总大小信息空间、图像大小信息空间,然后输入图像信息
sendOut << qint64(0) << qint64(0) << imageData;

// 这里的总大小是总大小信息、图像大小信息和实际图像信息的总和
totalBytes += outBlock.size();
sendOut.device()->seek(0);

// 返回outBolock的开始,用实际的大小信息代替两个qint64(0)空间
sendOut << totalBytes << qint64((outBlock.size() - sizeof(qint64)*2));

//发出readyRead()信号
tcpClient->write(outBlock);

qDebug() << "图片的内容大小: " << qint64((outBlock.size() - sizeof(qint64)*2)) << endl;
qDebug() << "整个包的大小: " << totalBytes << endl;
// qDebug() << "发送完文件头结构后剩余数据的大小(bytesToWrite): " << bytesToWrite <

outBlock.resize(0);

ui->clientStatusLabel->setText(tr("传送文件 %1 成功").arg(currentImageName));
totalBytes = 0;
// bytesToWrite = 0;
}

void Client::displayError(QAbstractSocket::SocketError)
{
qDebug() << tcpClient->errorString();
tcpClient->close();

ui->clientStatusLabel->setText(tr("客户端就绪"));
ui->sendButton->setEnabled(true);
}

void Client::tcpConnected()
{
isOk = true;
ui->connectButton->setText(tr("断开"));

ui->clientStatusLabel->setText(tr("已连接"));
}

void Client::tcpDisconnected()
{
isOk = false;
tcpClient->abort();
ui->connectButton->setText(tr("连接"));

ui->clientStatusLabel->setText(tr("连接已断开"));
}

QByteArray Client::getImageData(const QImage &image)
{
QByteArray imageData;
QBuffer buffer(&imageData);
image.save(&buffer, "png");
imageData = imageData.toBase64();

return imageData;
}

// 打开按钮
void Client::on_openButton_clicked()
{
ui->clientStatusLabel->setText(tr("状态:等待打开文件!"));
openFile();

}

// 发送按钮
void Client::on_sendButton_clicked()
{
send();
}

void Client::on_connectButton_clicked()
{
if (ui->connectButton->text() == tr("连接")) {
tcpClient->abort();
connectServer();
} else {
tcpClient->abort();
}
}

布局:
Qt TCP网络编程——传输图片(附TCP连接逻辑以及完整代码)_客户端_05

3 服务器

类声明:

private:
Ui::Server *ui;

QTcpServer tcpServer;
QTcpSocket *tcpServerConnection;
qint64 totalBytes; // 存放总大小信息
qint64 bytesReceived; // 已收到数据的大小
qint64 fileNameSize; // 文件名的大小信息
qint64 imageSize; //图片大小

QString fileName; // 存放文件名
QFile *localFile; // 本地文件
QByteArray inBlock; // 数据缓冲区
QString imageContent;

QImage image;//图片


private slots:
void start();//监听的事件
void acceptConnection();//被客户端连接上后,创建套接字、接收数据、处理异常、关闭服务器
void updateServerProgress();//接收并处理显示图片
void displayError(QAbstractSocket::SocketError socketError);//错误处理

//base64字符串转图片
QImage getImage(const QString &);

void on_startButton_clicked();//监听或断开监听

构造函数:

ui->setupUi(this);

//端口自动补全以及默认提示
ui->portLineEdit->setPlaceholderText(tr("6666"));//设置默认提示
QStringList portWordList;
portWordList << tr("6666");
QCompleter* portCompleter = new QCompleter(portWordList, this);
ui->portLineEdit->setCompleter(portCompleter);

connect(&tcpServer, SIGNAL(newConnection()),
this, SLOT(acceptConnection()));


ui->imageLabel->show();

其余函数的定义:

void Server::start()
{


if (!tcpServer.listen(QHostAddress::LocalHost, ui->portLineEdit->text().toInt())) {
qDebug() << tcpServer.errorString();
close();
return;
}

totalBytes = 0;
bytesReceived = 0;
imageSize = 0;
ui->serverStatusLabel->setText(tr("正在监听"));

}

void Server::acceptConnection()
{
//获得链接套接字
tcpServerConnection = tcpServer.nextPendingConnection();

//接收数据
//readyRead()当网络套接字上有新的网络数据有效负载时
connect(tcpServerConnection, SIGNAL(readyRead()),
this, SLOT(updateServerProgress()));
//处理异常
connect(tcpServerConnection, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(displayError(QAbstractSocket::SocketError)));

ui->serverStatusLabel->setText(tr("接受连接"));
// 关闭服务器,不再进行监听
// tcpServer.close();
}


void Server::updateServerProgress()
{
QDataStream in(tcpServerConnection);
in.setVersion(QDataStream::Qt_5_6);

// 如果接收到的数据小于16个字节,保存到来的文件头结构
if (bytesReceived <= sizeof(qint64)*2) {
if((tcpServerConnection->bytesAvailable() >= sizeof(qint64)*2)
&& (imageSize == 0)) {
// 接收数据总大小信息和文件名大小信息
in >> totalBytes >> imageSize;
bytesReceived += sizeof(qint64) * 2;

if(imageSize == 0){
ui->serverStatusLabel->setText(tr("显示的图片为空!"));
}
qDebug() <<"定位点0" << endl;
}
if((tcpServerConnection->bytesAvailable() >= imageSize)
&& (imageSize != 0)) {

// 接收文件名,并建立文件
in >> imageContent;

// qDebug() << imageContent << endl;

ui->serverStatusLabel->setText(tr("接收文件 …"));

QImage imageData = getImage(imageContent);

QPixmap resImage = QPixmap::fromImage(imageData);
QPixmap* imgPointer = &resImage;
imgPointer->scaled(ui->imageLabel->size(), Qt::IgnoreAspectRatio);//重新调整图像大小以适应窗口
// imgPointer->scaled(ui->imageLabel->size(), Qt::KeepAspectRatio);//设置pixmap缩放的尺寸

ui->imageLabel->setScaledContents(true);//设置label的属性,能够缩放pixmap充满整个可用的空间。
ui->imageLabel->setPixmap(*imgPointer);

bytesReceived += imageSize;

qDebug() << "定位1 bytesReceived: " << bytesReceived << endl;

if(bytesReceived == totalBytes){
ui->serverStatusLabel->setText(tr("接收文件成功"));
totalBytes = 0;
bytesReceived = 0;
imageSize = 0;
}

}
}
}

void Server::displayError(QAbstractSocket::SocketError socketError)
{
qDebug() <<"errorString()" <<tcpServerConnection->errorString();
tcpServerConnection->close();

ui->serverStatusLabel->setText(tr("服务端就绪"));

}

QImage Server::getImage(const QString &data)
{
QByteArray imageData = QByteArray::fromBase64(data.toLatin1());
QImage image;
image.loadFromData(imageData);
return image;
}

// 开始监听按钮

void Server::on_startButton_clicked()
{
if(ui->startButton->text() == tr("监听")){
ui->startButton->setText(tr("断开"));
start();
}else{
ui->startButton->setText(tr("监听"));
tcpServer.close();

tcpServerConnection->disconnectFromHost();
}
}

4 代码下载

5 不足

  • 没有心跳机制来判断离线,进而自动重连服务器,没有设置超时时间;
  • 没有使用线程进行图片传输
  • 没有实现多个客户端和一个服务端通信