原理接上篇:Qt网络-TCP,实现服务器、客户端传递文本消息和文件(一)
程序效果
1. 主要功能:TCP方式建立连接,实现服务器和客户端互发消息,文件传输只实现了单向传输,客户端向服务器传送文件,但是从服务器向客户端传文件原理也是一样的,代码都有,实现起来也简单,我这里就不实现了,这篇博客主要是了解传输原理和流程,了解如何传输数据,还可以增加是否接收文件等等很多功能,可以根据需要自己尝试实现,了解了传输过程,什么功能都可以做。
2. 程序里文本消息和文件传输分别用了两个socket,这样在传输大文件的时候依然可以发送文本消息,互不影响。
1. 服务器
(1)服务器头文件:
私有变量:
private:
QLabel *m_labListen; // 状态栏标签
QLabel *m_labSocketState; // 状态栏标签
QTcpServer *m_tcpServer; // TCP 服务器
QTcpSocket *m_tcpTextSocket; // 文本Socket
QTcpSocket *m_tcpFileSocket; // 文件socket,使用两个socket,传输时互不影响
// 传输文件时使用的变量
qint64 m_totalBytes; // 存放总大小信息
qint64 m_bytesReceived; // 已收到数据的大小
qint64 m_fileNameSize; // 文件名的大小信息
QString m_fileName; // 存放文件名
QFile *m_localFile; // 本地文件, 接收的文件存在程序运行路径
QByteArray m_inBlock;
私有槽:
private slots:
void onNewConnection(); // QTcpServer的newConnection()信号
void onSocketStateChange(QAbstractSocket::SocketState socketState);
void onClientConnected(); // 文本消息Socket connected
void onClientDisconnected();// 文本Socket disconnected
void onSocketReadyRead();// 读取文本socket传入的数据
void onFileClientConnected(); // 文件socket连接时触发此槽函数
void onFileClientDisconnected(); // 文件socket断开连接时出发此槽函数
void updateServerProgress(); //更新进度条,接收数据
void displayError(QAbstractSocket::SocketError socketError); //显示错误
void on_actStart_triggered(); // 开启监听
void on_actStop_triggered(); // 停止监听
void on_actClear_triggered(); // 清空文本框
void on_btnSend_clicked(); // 发送消息
服务器源文件cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QtNetwork>
#include <QMessageBox>
QString MainWindow::getLocalIP()
{//获取本机IPv4地址
QString hostName = QHostInfo::localHostName();//本地主机名
QHostInfo hostInfo = QHostInfo::fromName(hostName);
QString localIP = "";
QList<QHostAddress> addList = hostInfo.addresses();//
if (!addList.isEmpty())
{
for (int i=0;i<addList.count();i++)
{
QHostAddress aHost = addList.at(i);
if (QAbstractSocket::IPv4Protocol == aHost.protocol())
{
localIP = aHost.toString();
break;
}
}
}
return localIP;
}
void MainWindow::closeEvent(QCloseEvent *event)
{//关闭窗口时停止监听
if (m_tcpServer->isListening())
m_tcpServer->close(); //停止网络监听
event->accept();
}
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_labListen=new QLabel("监听状态:");
m_labListen->setMinimumWidth(150);
ui->statusBar->addWidget(m_labListen);
m_labSocketState=new QLabel("Socket状态:");//
m_labSocketState->setMinimumWidth(200);
ui->statusBar->addWidget(m_labSocketState);
QString localIP=getLocalIP();//本机IP
this->setWindowTitle(this->windowTitle()+"----本机IP:"+localIP);
ui->comboIP->addItem(localIP);
m_totalBytes = 0;
m_bytesReceived = 0;
m_fileNameSize = 0;
m_tcpServer = new QTcpServer(this);
connect(m_tcpServer, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
m_tcpTextSocket = new QTcpSocket(this);
m_tcpFileSocket = new QTcpSocket(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::onNewConnection()
{
if (m_tcpTextSocket->state() == QAbstractSocket::UnconnectedState)
{
m_tcpTextSocket = m_tcpServer->nextPendingConnection(); // 创建socket
connect(m_tcpTextSocket, SIGNAL(connected()),
this, SLOT(onClientConnected()));
onClientConnected();//
connect(m_tcpTextSocket, SIGNAL(disconnected()),
this, SLOT(onClientDisconnected()));
connect(m_tcpTextSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),
this,SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
onSocketStateChange(m_tcpTextSocket->state());
connect(m_tcpTextSocket,SIGNAL(readyRead()),
this,SLOT(onSocketReadyRead()));
return;
}
if (m_tcpFileSocket->state() == QAbstractSocket::UnconnectedState)
{
m_tcpFileSocket = m_tcpServer->nextPendingConnection(); // 创建socket
connect(m_tcpFileSocket, SIGNAL(connected()),
this, SLOT(onFileClientConnected()));
onFileClientConnected();//m_tcpFileSocket->deleteLater();
connect(m_tcpFileSocket, SIGNAL(disconnected()),
this, SLOT(onFileClientDisconnected()));
connect(m_tcpFileSocket, SIGNAL(readyRead()), this, SLOT(updateServerProgress()));
connect(m_tcpFileSocket, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(displayError(QAbstractSocket::SocketError)));
}
}
void MainWindow::onSocketStateChange(QAbstractSocket::SocketState socketState)
{//socket状态变化时
switch(socketState)
{
case QAbstractSocket::UnconnectedState:
m_labSocketState->setText("scoket状态:UnconnectedState");
break;
case QAbstractSocket::HostLookupState:
m_labSocketState->setText("scoket状态:HostLookupState");
break;
case QAbstractSocket::ConnectingState:
m_labSocketState->setText("scoket状态:ConnectingState");
break;
case QAbstractSocket::ConnectedState:
m_labSocketState->setText("scoket状态:ConnectedState");
break;
case QAbstractSocket::BoundState:
m_labSocketState->setText("scoket状态:BoundState");
break;
case QAbstractSocket::ClosingState:
m_labSocketState->setText("scoket状态:ClosingState");
break;
case QAbstractSocket::ListeningState:
m_labSocketState->setText("scoket状态:ListeningState");
}
}
void MainWindow::onClientConnected()
{//客户端接入时
ui->plainTextEdit->appendPlainText("**client socket connected");
ui->plainTextEdit->appendPlainText("**peer address:"+
m_tcpTextSocket->peerAddress().toString());
ui->plainTextEdit->appendPlainText("**peer port:"+
QString::number(m_tcpTextSocket->peerPort()));
}
void MainWindow::onFileClientConnected()
{
ui->plainTextEdit_file->appendPlainText("**client socket connected");
ui->plainTextEdit_file->appendPlainText("**peer address:"+
m_tcpFileSocket->peerAddress().toString());
ui->plainTextEdit_file->appendPlainText("**peer port:"+
QString::number(m_tcpFileSocket->peerPort()));
}
void MainWindow::onClientDisconnected()
{//客户端断开连接时
ui->plainTextEdit->appendPlainText("**client socket disconnected");
m_tcpTextSocket->close();
}
void MainWindow::onFileClientDisconnected()
{
ui->plainTextEdit_file->appendPlainText("**client socket disconnected");
m_tcpFileSocket->close();
}
void MainWindow::onSocketReadyRead()
{//读取缓冲区行文本
// QStringList lines;
while(m_tcpTextSocket->canReadLine())
ui->plainTextEdit->appendPlainText("[in] "+m_tcpTextSocket->readLine());
}
void MainWindow::on_actStart_triggered()
{//开始监听
QString IP = ui->comboIP->currentText();//IP地址
quint16 port = ui->spinPort->value();//端口
QHostAddress addr(IP);
// text
m_tcpServer->listen(addr, port);//
// m_tcpServer->listen(QHostAddress::LocalHost,port);// Equivalent to QHostAddress("127.0.0.1").
ui->plainTextEdit->appendPlainText("**开始监听...");
ui->plainTextEdit->appendPlainText("**服务器地址:"
+m_tcpServer->serverAddress().toString());
ui->plainTextEdit->appendPlainText("**服务器端口:"
+QString::number(m_tcpServer->serverPort()));
// file
ui->plainTextEdit_file->appendPlainText("**开始监听...");
ui->plainTextEdit_file->appendPlainText("**服务器地址:"
+ m_tcpServer->serverAddress().toString());
ui->plainTextEdit_file->appendPlainText("**服务器端口:"
+ QString::number(m_tcpServer->serverPort()));
ui->actStart->setEnabled(false);
ui->actStop->setEnabled(true);
m_labListen->setText("监听状态:正在监听");
}
void MainWindow::on_actStop_triggered()
{//停止监听
if (m_tcpServer->isListening()) // m_tcpServer正在监听
{
m_tcpServer->close(); // 停止监听
ui->actStart->setEnabled(true);
ui->actStop->setEnabled(false);
m_labListen->setText("监听状态:已停止监听");
}
}
void MainWindow::on_actClear_triggered()
{
ui->plainTextEdit->clear();
ui->plainTextEdit_file->clear();
}
void MainWindow::on_btnSend_clicked()
{//发送一行字符串,以换行符结束
QString msg=ui->editMsg->text();
if (msg.isEmpty())
{
QMessageBox::information(this, "提示", "发送的消息不能为空!");
return;
}
ui->plainTextEdit->appendPlainText("[out] "+msg);
ui->editMsg->clear();
ui->editMsg->setFocus();
QByteArray str=msg.toUtf8();
str.append('\n');//添加一个换行符
m_tcpTextSocket->write(str);
}
void MainWindow::updateServerProgress() //更新进度条,接收数据
{
QDataStream in(m_tcpFileSocket);
in.setVersion(QDataStream::Qt_5_9);
if (m_bytesReceived <= sizeof(qint64)*2)
{ //如果接收到的数据小于16个字节,那么是刚开始接收数据,我们保存接收到的的头文件信息
if((m_tcpFileSocket->bytesAvailable() >= sizeof(qint64)*2) && (m_fileNameSize == 0))
{ //接收数据总大小信息和文件名大小信息
in >> m_totalBytes >> m_fileNameSize;
m_bytesReceived += sizeof(qint64) * 2;
}
if((m_tcpFileSocket->bytesAvailable() >= m_fileNameSize) && (m_fileNameSize != 0))
{ //接收文件名,并建立文件
in >> m_fileName;
ui->plainTextEdit_file->appendPlainText(tr("接收文件 %1 ...").arg(m_fileName));
m_bytesReceived += m_fileNameSize;
m_localFile= new QFile(QApplication::applicationDirPath() + "\\" + m_fileName);
if(!m_localFile->open(QFile::WriteOnly))
{
qDebug() << "open file error!";
return;
}
}
else
return;
}
if(m_bytesReceived < m_totalBytes)
{ // 如果接收的数据小于总数据,那么写入文件
m_bytesReceived += m_tcpFileSocket->bytesAvailable();
m_inBlock= m_tcpFileSocket->readAll();
m_localFile->write(m_inBlock);
m_inBlock.resize(0);
}
//更新进度条
ui->progressBar->setMaximum(m_totalBytes);
ui->progressBar->setValue(m_bytesReceived);
if(m_bytesReceived == m_totalBytes)
{ //接收数据完成时
m_totalBytes = 0;
m_bytesReceived = 0;
m_fileNameSize = 0;
m_localFile->close();
ui->plainTextEdit_file->appendPlainText(QString("接收文件 %1 成功!").arg(m_fileName));
}
}
void MainWindow::displayError(QAbstractSocket::SocketError) //错误处理
{
ui->plainTextEdit_file->appendPlainText(m_tcpFileSocket->errorString());
m_tcpFileSocket->close();
ui->progressBar->reset();
}
2. 客户端
头文件
private:
QTcpSocket *m_tcpTextClient; //文本消息socket
QLabel *m_labSocketState; //状态栏显示标签
QFile *m_localFile; //要发送的文件
qint64 m_totalBytes; //数据总大小
qint64 m_bytesWritten; //已经发送数据大小
qint64 m_bytesToWrite; //剩余数据大小
qint64 m_loadSize; //每次发送数据的大小
QString m_fileName; //保存文件路径
QByteArray m_outBlock; //数据缓冲区,即存放每次要发送的数据
QTcpSocket *m_tcpFileClient; //文件消息socket
QString getLocalIP();//获取本机IP地址
protected:
void closeEvent(QCloseEvent *event);
private slots:
void onConnected();
void onDisconnected();
void onSocketStateChange(QAbstractSocket::SocketState socketState);
void onSocketReadyRead();//读取socket传入的数据
void onFileConnected();
void onFileDisconnected();
void startTransfer(); //发送文件大小等信息
void updateClientProgress(qint64); //发送数据,更新进度条
void displayError(QAbstractSocket::SocketError); //显示错误信息
void on_actConnect_triggered();
void on_actDisconnect_triggered();
void on_actClear_triggered();
void on_btnSend_clicked();
void on_btnSelectFile_clicked();
void on_btnSendFile_clicked();
客户端源文件
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QHostAddress>
#include <QHostInfo>
#include <QMessageBox>
#include <QFileDialog>
QString MainWindow::getLocalIP()
{
QString hostName=QHostInfo::localHostName();//本地主机名
QHostInfo hostInfo=QHostInfo::fromName(hostName);
QString localIP="";
QList<QHostAddress> addList=hostInfo.addresses();//
if (!addList.isEmpty())
for (int i=0;i<addList.count();i++)
{
QHostAddress aHost=addList.at(i);
if (QAbstractSocket::IPv4Protocol==aHost.protocol())
{
localIP=aHost.toString();
break;
}
}
return localIP;
}
void MainWindow::closeEvent(QCloseEvent *event)
{
if (m_tcpTextClient->state()==QAbstractSocket::ConnectedState)
m_tcpTextClient->disconnectFromHost();
if (m_tcpFileClient->state()==QAbstractSocket::ConnectedState)
m_tcpFileClient->disconnectFromHost();
event->accept();
}
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_loadSize = 4*1024; // 每次发送的文件数据大小
m_totalBytes = 0;
m_bytesWritten = 0;
m_bytesToWrite = 0;
m_tcpTextClient = new QTcpSocket(this); //创建socket变量
m_tcpFileClient = new QTcpSocket(this);
m_labSocketState = new QLabel("Socket状态:");//状态栏标签
m_labSocketState->setMinimumWidth(250);
ui->statusBar->addWidget(m_labSocketState);
QString localIP = getLocalIP();//本机IP
this->setWindowTitle(this->windowTitle()+"----本机IP:"+localIP);
ui->comboServer->addItem(localIP);
connect(m_tcpTextClient,SIGNAL(connected()),this,SLOT(onConnected()));
connect(m_tcpTextClient,SIGNAL(disconnected()),this,SLOT(onDisconnected()));
connect(m_tcpTextClient,SIGNAL(stateChanged(QAbstractSocket::SocketState)),
this,SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
connect(m_tcpTextClient,SIGNAL(readyRead()),
this,SLOT(onSocketReadyRead()));
connect(m_tcpFileClient,SIGNAL(connected()),this,SLOT(onFileConnected()));
connect(m_tcpFileClient,SIGNAL(disconnected()),this,SLOT(onFileDisconnected()));
//当有数据发送成功时,更新进度条
connect(m_tcpFileClient,SIGNAL(bytesWritten(qint64)),this,
SLOT(updateClientProgress(qint64)));
connect(m_tcpFileClient,SIGNAL(error(QAbstractSocket::SocketError)),this,
SLOT(displayError(QAbstractSocket::SocketError)));
ui->btnSendFile->setEnabled(false);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::onConnected()
{ //connected()信号槽函数
ui->plainTextEdit->appendPlainText("**已连接到服务器");
ui->plainTextEdit->appendPlainText("**peer address:"+
m_tcpTextClient->peerAddress().toString());
ui->plainTextEdit->appendPlainText("**peer port:"+
QString::number(m_tcpTextClient->peerPort()));
ui->actConnect->setEnabled(false);
ui->actDisconnect->setEnabled(true);
}
void MainWindow::onDisconnected()
{//disConnected()信号槽函数
ui->plainTextEdit->appendPlainText("**已断开与服务器的连接");
ui->actConnect->setEnabled(true);
ui->actDisconnect->setEnabled(false);
}
void MainWindow::onFileConnected()
{ //connected()信号槽函数
ui->plainTextEdit_file->appendPlainText("**已连接到服务器");
ui->plainTextEdit_file->appendPlainText("**peer address:"+
m_tcpFileClient->peerAddress().toString());
ui->plainTextEdit_file->appendPlainText("**peer port:"+
QString::number(m_tcpFileClient->peerPort()));
}
void MainWindow::onFileDisconnected()
{//disConnected()信号槽函数
ui->plainTextEdit_file->appendPlainText("**已断开与服务器的连接");
}
void MainWindow::onSocketReadyRead()
{//readyRead()信号槽函数
while(m_tcpTextClient->canReadLine())
ui->plainTextEdit->appendPlainText("[in] "+m_tcpTextClient->readLine());
}
void MainWindow::onSocketStateChange(QAbstractSocket::SocketState socketState)
{//stateChange()信号槽函数
switch(socketState)
{
case QAbstractSocket::UnconnectedState:
m_labSocketState->setText("scoket状态:UnconnectedState");
break;
case QAbstractSocket::HostLookupState:
m_labSocketState->setText("scoket状态:HostLookupState");
break;
case QAbstractSocket::ConnectingState:
m_labSocketState->setText("scoket状态:ConnectingState");
break;
case QAbstractSocket::ConnectedState:
m_labSocketState->setText("scoket状态:ConnectedState");
break;
case QAbstractSocket::BoundState:
m_labSocketState->setText("scoket状态:BoundState");
break;
case QAbstractSocket::ClosingState:
m_labSocketState->setText("scoket状态:ClosingState");
break;
case QAbstractSocket::ListeningState:
m_labSocketState->setText("scoket状态:ListeningState");
}
}
void MainWindow::on_actConnect_triggered()
{//连接到服务器
QString addr = ui->comboServer->currentText();
quint16 port = ui->spinPort->value();
m_tcpTextClient->connectToHost(addr, port);
m_bytesWritten = 0; // 初始化已发送字节为0
m_tcpFileClient->connectToHost(addr, port);
}
void MainWindow::on_actDisconnect_triggered()
{//断开与服务器的连接
if (m_tcpTextClient->state()==QAbstractSocket::ConnectedState)
m_tcpTextClient->disconnectFromHost();
if (m_tcpFileClient->state()==QAbstractSocket::ConnectedState)
m_tcpFileClient->disconnectFromHost();
}
void MainWindow::on_actClear_triggered()
{
ui->plainTextEdit->clear();
ui->plainTextEdit_file->clear();
}
void MainWindow::on_btnSend_clicked()
{//发送数据
QString msg=ui->editMsg->text();
if (msg.isEmpty())
{
QMessageBox::information(this, "提示", "发送的消息不能为空!");
return;
}
ui->plainTextEdit->appendPlainText("[out] "+msg);
ui->editMsg->clear();
ui->editMsg->setFocus();
QByteArray str=msg.toUtf8();
str.append('\n');
m_tcpTextClient->write(str);
}
void MainWindow::on_btnSelectFile_clicked()
{
m_fileName = QFileDialog::getOpenFileName(this, "选择文件");
if (!m_fileName.isEmpty())
{
ui->plainTextEdit_file->appendPlainText(QString("打开文件 %1 成功!").arg(m_fileName));
ui->btnSendFile->setEnabled(true);
}
}
void MainWindow::startTransfer() //实现文件大小等信息的发送
{
m_localFile = new QFile(m_fileName);
if(!m_localFile->open(QFile::ReadOnly))
{
qDebug() << "open file error!";
return;
}
// 文件总大小
m_totalBytes = m_localFile->size();
QDataStream sendOut(&m_outBlock, QIODevice::WriteOnly);
sendOut.setVersion(QDataStream::Qt_5_9);
QString currentFileName = m_fileName.right(m_fileName.size() - m_fileName.lastIndexOf('/')-1);
// 依次写入文件总大小信息空间,文件名大小信息空间,文件名
sendOut << qint64(0) << qint64(0) << currentFileName;
// 总大小是文件名大小等信息和实际文件大小的总和
m_totalBytes += m_outBlock.size();
sendOut.device()->seek(0);
// 返回outBolock的开始,用实际的大小信息代替两个qint64(0)空间
sendOut<<m_totalBytes<<qint64((m_outBlock.size() - sizeof(qint64)*2));
// 发送完头数据后剩余数据的大小
m_bytesToWrite = m_totalBytes - m_tcpFileClient->write(m_outBlock);
m_outBlock.resize(0);
}
// 更新进度条,实现文件的传送
void MainWindow::updateClientProgress(qint64 numBytes)
{
// 已经发送数据的大小
m_bytesWritten += (int)numBytes;
if(m_bytesToWrite > 0) // 剩余数据大小
{
// 从文件中取出数据到发送缓冲区,每次发送loadSize大小的数据,这里设置为4KB,如果剩余的数据不足4KB,就发送剩余数据的大小
m_outBlock = m_localFile->read(qMin(m_bytesToWrite, m_loadSize));
// 从发送缓冲区发送数据,计算发送完一次数据后还剩余数据的大小
m_bytesToWrite -= (int)m_tcpFileClient->write(m_outBlock);
// 清空发送缓冲区
m_outBlock.resize(0);
} else
{
m_localFile->close(); // 没有数据待发送,则关闭文件
}
//更新进度条
ui->progressBar->setMaximum(m_totalBytes);
ui->progressBar->setValue(m_bytesWritten);
if(m_bytesWritten == m_totalBytes) //发送完毕
{
ui->plainTextEdit_file->appendPlainText(QString("传送文件 %1 成功").arg(m_fileName));
m_localFile->close();
}
}
void MainWindow::on_btnSendFile_clicked()
{
m_totalBytes = 0;
m_bytesWritten = 0;
m_bytesToWrite = 0;
startTransfer();
}
void MainWindow::displayError(QAbstractSocket::SocketError) //显示错误
{
ui->plainTextEdit_file->appendPlainText(m_tcpFileClient->errorString());
m_tcpFileClient->close();
ui->progressBar->reset();
}