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 首先服務器通過
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 <<endl;
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 代碼下載
https://download.csdn.net/download/qq_33375598/12340133
5 不足
- 沒有心跳機制來判斷離線,進而自動重連服務器,沒有設置超時時間;
- 沒有使用線程進行圖片傳輸
- 沒有實現多個客戶端和一個服務端通信