文章目录
0 效果
完整代码地址
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);
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();
}
}
布局:
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 不足
- 没有心跳机制来判断离线,进而自动重连服务器,没有设置超时时间;
- 没有使用线程进行图片传输
- 没有实现多个客户端和一个服务端通信