上一节里我们使用TCP服务器和客户端相互发送字符串信息,本节间学习图片的传输及显示。
一、服务器端
程序开始进行监听,一旦发现有连接请求就发出newConnection()信号,然后我们便接受连接,可以接收数据。
步骤1.新建QtGui应用
名称为tcpServer,基类选择QWidget,类名为Widget,完成后打开tcpReceiver.pro添加一行代码:QT += network 。
步骤2.我们更改widget.ui文件,设计界面如下。
其中“服务器端”Label的objectName为statuslab;进度条ProgressBar的objectName为progressBar,设置其value属性为0;发送按钮的objectName为sendButton;图片显示textlabel的objectName为recLab。
效果如下。
步骤3.更改widget.h文件的内容。
- 添加头文件包含:#include <QtNetwork>
(2)添加私有变量:
QTcpServer *tcpServer;
QTcpSocket *currentClient;
qint64 totalBytes; //存放总大小信息
qint64 bytesReceived; //已收到数据的大小
qint64 fileNameSize; //文件名的大小信息
QString fileName; //存放文件名
QFile *localFile; //本地文件
QByteArray inBlock; //接收数据缓冲区
qint64 bytesWritten; //已经发送数据大小
qint64 bytesToWrite; //剩余数据大小
qint64 loadSize; //每次发送数据的大小
QByteArray outBlock; //数据缓冲区
(3)添加私有槽函数:
private slots:
void NewConnection();
void recMessage();
void sendMessage();
void disconnect();
void continueSend(qint64);
void on_sendButton_clicked();
步骤4.更改widget.cpp文件。
(1)在构造函数中添加代码:
totalBytes = 0;
bytesReceived = 0;
fileNameSize = 0;
totalBytes = 0;
bytesReceived = 0;
fileNameSize = 0;
tcpServer = new QTcpServer(this);
if(!tcpServer->listen(QHostAddress::Any,6666))
{ //**本地主机的6666端口,如果出错就输出错误信息,并关闭
qDebug() << tcpServer->errorString();
close();
}
//连接信号和相应槽函数,有新的连接进入是需处理
connect(tcpServer,SIGNAL(newConnection()),this,SLOT(NewConnection()));
ui->sendButton->setEnabled(false);
(2)实现接受连接函数。
void Widget::NewConnection()
{
//初始化为0;
//blockSize=0;
// inBlock.resize(0);
//新连接进入的显示处理
currentClient = tcpServer->nextPendingConnection();
ui->statuslab->setText(tr("%1:%2").arg(currentClient->peerAddress().toString().split("::ffff:")[1])\
.arg(currentClient->peerPort()));
connect(currentClient, SIGNAL(readyRead()), this, SLOT(recMessage()));
connect(currentClient, SIGNAL(disconnected()), this, SLOT(disconnect()));
//当有数据发送成功时,继续发送
connect(currentClient,SIGNAL(bytesWritten(qint64)),this, SLOT(continueSend(qint64)));
ui->sendButton->setEnabled(true);
}
我们要先发送文件的总大小,然后文件名长度,然后是文件名,这三部分合称为文件头结构,最后再发送文件数据。所以在发送函数里就要进行相应的处理,当然,在服务器的接收函数里也要进行相应的处理。对于文件大小,这次使用了qint64,它是64位的,可以表示一个很大的文件。
(3)开始发送数据(文件名,文件大小等信息)
void Widget::sendMessage()
{
bytesWritten = 0;
fileName = QFileDialog::getOpenFileName(this);
if(!fileName.isEmpty())
{
localFile = new QFile(fileName);
if(!localFile->open(QFile::ReadOnly))
{
qDebug() << "open file error!";
return;
}
//文件总大小
totalBytes = localFile->size();
QDataStream sendOut(&outBlock,QIODevice::WriteOnly);
sendOut.setVersion(QDataStream::Qt_5_8);
QString currentFileName = fileName.right(fileName.size()
- fileName.lastIndexOf('/')-1);
//依次写入总大小信息空间,文件名大小信息空间,文件名
sendOut << qint64(0) << qint64(0) << currentFileName;
//这里的总大小是文件名大小等信息和实际文件大小的总和
totalBytes += outBlock.size();
sendOut.device()->seek(0);
//返回outBolock的开始,用实际的大小信息代替两个qint64(0)空间
sendOut<<totalBytes<<qint64((outBlock.size() - sizeof(qint64)*2));
//发送完头数据后剩余数据的大小
bytesToWrite = totalBytes - currentClient->write(outBlock);
ui->statuslab->setText(tr("开始发送"));
outBlock.resize(0);
}
}
(4)分块发送,更新进度条
void Widget::continueSend(qint64 numBytes)
{
//已经发送数据的大小
bytesWritten += (int)numBytes;
if(bytesToWrite > 0) //如果已经发送了数据
{
//每次发送loadSize大小的数据,这里设置为4KB,如果剩余的数据不足4KB,
//就发送剩余数据的大小
outBlock = localFile->read(qMin(bytesToWrite,loadSize));
//发送完一次数据后还剩余数据的大小
bytesToWrite -= (int)currentClient->write(outBlock);
//清空发送缓冲区
outBlock.resize(0);
} else {
localFile->close(); //如果没有发送任何数据,则关闭文件
}
//更新进度条
ui->progressBar->setMaximum(totalBytes);
ui->progressBar->setValue(bytesWritten);
if(bytesWritten == totalBytes) //发送完毕
{
ui->statuslab->setText(tr("传送文件 %1 成功").arg(fileName));
localFile->close();
}
}
(5)发送按钮
void Widget::on_sendButton_clicked()
{
//发送数据
sendMessage();
}
(6)接收及显示
void Widget::recMessage()
{
QDataStream in(currentClient);
in.setVersion(QDataStream::Qt_5_8);
if(bytesReceived <= sizeof(qint64)*2)
{ //如果接收到的数据小于16个字节,那么是刚开始接收数据,我们保存到//来的头文件信息
if((currentClient->bytesAvailable() >= sizeof(qint64)*2)
&& (fileNameSize == 0))
{ //接收数据总大小信息和文件名大小信息
in >> totalBytes >> fileNameSize;
bytesReceived += sizeof(qint64) * 2;
}
if((currentClient->bytesAvailable() >= fileNameSize)
&& (fileNameSize != 0))
{ //接收文件名,并建立文件
in >> fileName;
ui->statuslab->setText(tr("接收文件 %1 ...").arg(fileName));
bytesReceived += fileNameSize;
ui->statuslab->setText(fileName);
localFile= new QFile(fileName);
if(!localFile->open(QFile::WriteOnly))
{
qDebug() << "open file error!";
return;
}
}
else return;
}
if(bytesReceived < totalBytes)
{ //如果接收的数据小于总数据,那么写入文件
bytesReceived += currentClient->bytesAvailable();
inBlock+= currentClient->readAll();
}
//更新进度条
ui->progressBar->setMaximum(totalBytes);
ui->progressBar->setValue(bytesReceived);
if(bytesReceived == totalBytes)
{ //接收数据完成时
//接收显示
QBuffer buffer(&inBlock);
buffer.open(QIODevice::ReadOnly);
QImageReader reader(&buffer,"jpg");
QImage image = reader.read();
if(!image.isNull())
{
image=image.scaled(ui->recLab->size());
ui->recLab->setPixmap(QPixmap::fromImage(image));
}
localFile->write(inBlock);
localFile->close();
inBlock.resize(0);
//重新置0 准备下次接收
totalBytes = 0;
bytesReceived = 0;
fileNameSize = 0;
ui->statuslab->setText(tr("接收文件 %1 成功!").arg(fileName));
}
}
二、客户端
客户端里需要与服务器进行连接,一旦连接成功,才可以进行文件传输。
步骤1.新建QtGui项目
名称为tcpSender,基类选择QWidget,类名为Widget,完成后打开tcpSender.pro添加一行代码:QT += network 。
步骤2.我们在widget.ui文件中将界面设计如下。
这里“主机”后的Line Edit的objectName为ipEdit;“端口”后的Line Edit的objectName为portEdit;下面的Progress Bar的objectName为progressBar,其value属性设为24;“状态”Label的objetName为statusLab;“打开”按钮的objectName为conBun;“发送”按钮的objectName为sendBun。
步骤3.在widget.h文件中进行更改。
(1)添加头文件包含#include <QtNetwork>
(2)添加private变量:
QTcpSocket *tcpSocket;
QFile *localFile; //要发送的文件
qint64 totalBytes; //数据总大小
qint64 bytesWritten; //已经发送数据大小
qint64 bytesToWrite; //剩余数据大小
qint64 loadSize; //每次发送数据的大小
QString fileName; //保存文件路径
QByteArray outBlock; //数据缓冲区,即存放每次要发送的数据
QByteArray inBlock; //数据缓冲区,接收
qint64 bytesReceived; //已收到数据的大小
qint64 fileNameSize; //文件名的大小信息
(3)添加私有槽函数:
private slots:
void newConnect(); //连接服务器
void readData(); //接收数据
void sendData();//发送数据
void continueSend(qint64 numBytes); //继续并更新发送进度条
void on_conBtn_clicked();
void on_sendbtn_clicked();
步骤4.在widget.cpp文件中进行更改
添加头文件:#include <QFileDialog>
(1)在构造函数中添加代码:
tcpSocket = new QTcpSocket(this);
loadSize = 4*1024;
totalBytes = 0;
bytesWritten = 0;
bytesToWrite = 0;
//接收
bytesReceived = 0;
fileNameSize = 0;
//当有数据发送成功时,继续发送
connect(tcpSocket,SIGNAL(bytesWritten(qint64)),this,
SLOT(continueSend(qint64)));
//接收数据
connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(readData()));
ui->sendbtn->setEnabled(false);
该函数将在下面的“打开”按钮单击事件槽函数中调用。
(2)实现连接主机函数。
void Widget::newConnect()
{
// blockSize = 0; //初始化其为0
tcpSocket->abort(); //取消已有的连接
//连接到主机,这里从界面获取主机地址和端口号
tcpSocket->connectToHost(ui->hostEdit->text(), ui->portEdit->text().toInt());
ui->sendbtn->setEnabled(true);
}该函数将在“发送”按钮的单击事件槽函数中调用。
void Widget::on_conBtn_clicked()
{
newConnect();
}
(4)实现文件头结构的发送。
void Widget::sendData()
{
bytesWritten = 0;
fileName = QFileDialog::getOpenFileName(this);
if(!fileName.isEmpty())
{
localFile = new QFile(fileName);
if(!localFile->open(QFile::ReadOnly))
{
qDebug() << "open file error!";
return;
}
//文件总大小
totalBytes = localFile->size();
QDataStream sendOut(&outBlock,QIODevice::WriteOnly);
sendOut.setVersion(QDataStream::Qt_5_8);
QString currentFileName = fileName.right(fileName.size()
- fileName.lastIndexOf('/')-1);
//依次写入总大小信息空间,文件名大小信息空间,文件名
sendOut << qint64(0) << qint64(0) << currentFileName;
//这里的总大小是文件名大小等信息和实际文件大小的总和
totalBytes += outBlock.size();
sendOut.device()->seek(0);
//返回outBolock的开始,用实际的大小信息代替两个qint64(0)空间
sendOut<<totalBytes<<qint64((outBlock.size() - sizeof(qint64)*2));
//发送完头数据后剩余数据的大小
bytesToWrite = totalBytes - tcpSocket->write(outBlock);
ui->statuslab->setText(tr("开始发送"));
outBlock.resize(0);
}
}
(5)发送文件数据。
void Widget::continueSend(qint64 numBytes)
{
//已经发送数据的大小
bytesWritten += (int)numBytes;
if(bytesToWrite > 0) //如果已经发送了数据
{
//每次发送loadSize大小的数据,这里设置为4KB,如果剩余的数据不足4KB,
//就发送剩余数据的大小
outBlock = localFile->read(qMin(bytesToWrite,loadSize));
//发送完一次数据后还剩余数据的大小
bytesToWrite -= (int)tcpSocket->write(outBlock);
//清空发送缓冲区
outBlock.resize(0);
} else {
localFile->close(); //如果没有发送任何数据,则关闭文件
}
//更新进度条
ui->progressBar->setMaximum(totalBytes);
ui->progressBar->setValue(bytesWritten);
if(bytesWritten == totalBytes) //发送完毕
{
ui->statuslab->setText(tr("传送文件 %1 成功").arg(fileName));
localFile->close();
}
}
(6)接收数据并显示。
void Widget::readData()
{
QDataStream in(tcpSocket);
in.setVersion(QDataStream::Qt_5_8);
if(bytesReceived <= sizeof(qint64)*2)
{ //如果接收到的数据小于16个字节,那么是刚开始接收数据,我们保存到//来的头文件信息
if((tcpSocket->bytesAvailable() >= sizeof(qint64)*2)
&& (fileNameSize == 0))
{ //接收数据总大小信息和文件名大小信息
in >> totalBytes >> fileNameSize;
bytesReceived += sizeof(qint64) * 2;
}
if((tcpSocket->bytesAvailable() >= fileNameSize)
&& (fileNameSize != 0))
{ //接收文件名,并建立文件
in >> fileName;
ui->statuslab->setText(tr("接收文件 %1 ...").arg(fileName));
bytesReceived += fileNameSize;
localFile= new QFile(fileName);
if(!localFile->open(QFile::WriteOnly))
{
qDebug() << "open file error!";
return;
}
}
else return;
}
if(bytesReceived < totalBytes)
{ //如果接收的数据小于总数据,那么写入文件
bytesReceived += tcpSocket->bytesAvailable();
inBlock+= tcpSocket->readAll();
}
//更新进度条
ui->progressBar->setMaximum(totalBytes);
ui->progressBar->setValue(bytesReceived);
if(bytesReceived == totalBytes)
{ //接收数据完成时
//接收显示
QBuffer buffer(&inBlock);
buffer.open(QIODevice::ReadOnly);
QImageReader reader(&buffer,"jpg");
QImage image = reader.read();
if(!image.isNull())
{
image=image.scaled(ui->recLab->size());
ui->recLab->setPixmap(QPixmap::fromImage(image));
}
localFile->write(inBlock);
localFile->close();
inBlock.resize(0);
//重新置0 准备下次接收
totalBytes = 0;
bytesReceived = 0;
fileNameSize = 0;
ui->statuslab->setText(tr("接收文件 %1 成功!").arg(fileName));
}
}
源码下载: