QT中TCP协议让服务器和客户端之间通信的具体流程:
-----服务器流程:
- 创建QTcpServer对象tcpServer;
- 启动监听,调用listen函数;
listen(QHostAddress::Any, 端口号);
- 当有客户端连接时会发送newConnection信号,出发槽函数接受连接(得到一个与客户端通信的套接字QTcoSocket);
connect(&tcpServer, SIGNAL(newConnection(),this,SLOT(acceptConnection()));
在acceptConnection()槽函数中,QTcpSocket * tcpSocket = tcpServer.nextPendingConnection();
获得套接字;也可以得到IP地址和端口号;QString ip = tcpSocket->peerAddress().toString(); quint16 port = tcpSocket->peerPort();
- QTcpSocket发送数据成员方法write;
- 读数据当客户端有数据来,QTcpSocket对象就会发送readyRead信号,关联槽函数读取数据。
-----客户端流程:
- 创建QTcpSocket套接字tcpSocket;
- 连接服务器:
tcpSocket->connectToHost(ip,port);
- tcpSocket发送数据成员方法write;
- 读数据当对方有数据来时,tcpSocket对象就会发送readyRead信号,关联槽函数读取数据。
下面是一个示例:
实现的功能:客户端给服务器发送文件,
Server代码:
//server.h文件
#ifndef SERVER_H
#define SERVER_H
#include <QDialog>
#include <QTcpServer>
#include <QAbstractSocket>
class QTcpSocket;
class QFile;
namespace Ui {
class Server;
}
class Server : public QDialog
{
Q_OBJECT
public:
explicit Server(QWidget *parent = 0);
~Server();
private slots:
void start();//
void acceptConnection();//接受连接
void updateServerProgress();
void displayError(QAbstractSocket::SocketError socketError);
void on_pushButton_clicked();//点击监听按钮,
private:
Ui::Server *ui;
QTcpSocket *m_tcpServerConnection;
QTcpServer m_server;
qint64 m_totalBytes;//总大小
qint64 m_bytesReceive;//已经接收的大小
qint64 m_fileNameSize;//文件名的大小信息
QString m_filePath;
QFile *m_file;
QByteArray m_inBlock;//数据缓冲区
};
#endif // SERVER_H
//server.cpp
#include "server.h"
#include "ui_server.h"
#include <QtNetwork>
#pragma execution_character_set("utf-8")
#define LOG (qDebug()<<__FILE__<<__LINE__<<__FUNCTION__)
Server::Server(QWidget *parent) :
QDialog(parent),
ui(new Ui::Server)
{
ui->setupUi(this);
connect(&m_server,SIGNAL(newConnection()),this,SLOT(acceptConnection()));
}
Server::~Server()
{
delete ui;
}
void Server::start()
{
//监听
if(!m_server.listen(QHostAddress::LocalHost,9999))
{
qDebug()<<m_server.errorString();
close();
return;
}
//监听成功
ui->pushButton->setEnabled(false);
m_totalBytes = 0;//设置总的大小为0
m_bytesReceive = 0;//已经接收的大小为0
m_fileNameSize = 0;//文件名的大小0
ui->label->setText("监听");
ui->progressBar->reset();
qDebug()<<"m_totalBytes"<<m_totalBytes;
qDebug()<<"m_bytesReceive:"<<m_bytesReceive;
qDebug()<<"m_fileNameSize:"<<m_fileNameSize;
}
void Server::acceptConnection()
{
//接收了请求,并且获取了套接字
m_tcpServerConnection = m_server.nextPendingConnection();
QString ip;
quint16 port;
ip = (m_tcpServerConnection->peerAddress()).toString();
port = m_tcpServerConnection->peerPort();
QString str = QString("[%1:%2]成功连接!").arg(ip).arg(port);
ui->textEdit->setText(str);
//有数据时自动触发readRead()信号
connect(m_tcpServerConnection,SIGNAL(readyRead()),this,SLOT(updateServerProgress()));
connect(m_tcpServerConnection,SIGNAL(error(QAbstractSocket::SocketError)),
this,SLOT(displayError(QAbstractSocket::SocketError)));
ui->label->setText("接受连接");
//由于是一对一连接,一个连接成功之后,就不再监听
m_server.close();
}
void Server::updateServerProgress()
{
QDataStream in(m_tcpServerConnection);
in.setVersion(QDataStream::Qt_5_7);
qDebug()<<"In updateServerProgress: m_bytesReceive="<<m_bytesReceive;
//如果接收的数据小于16个字节,保存到来的文件头结构
if(m_bytesReceive <= sizeof(qint64)*2)
{
qDebug()<<"sizeof(quint64):"<<sizeof(qint64);
if((m_tcpServerConnection->bytesAvailable() >= sizeof(qint64)*2)
&&(m_fileNameSize == 0))
{
in>>m_totalBytes>>m_fileNameSize;
qDebug()<<"m_totalBytes(总共大小):"<<m_totalBytes<<"m_fileName:(文件名的大小)"<<m_fileNameSize;
m_bytesReceive += sizeof(qint64)*2;
}
if(m_tcpServerConnection->bytesAvailable() >= m_fileNameSize
&& m_fileNameSize != 0)
{
qDebug()<<"接收文件名,建立文件";
//接收文件名,建立文件
in>>m_filePath;
qDebug()<<"m_filePath:"<<m_filePath;
ui->label->setText(tr("接收文件 %1 ...").arg(m_filePath));
//更新m_bytesReceive大小
m_bytesReceive += m_fileNameSize;
//创建文件
m_file = new QFile(m_filePath);
qDebug()<<"---m_bytesReceive---(已接受):"<<m_bytesReceive;
if(!m_file->open(QFile::WriteOnly))
{
qDebug()<<"server: open file failed";
LOG;
return;
}
}
else
{
return;
}
}
//如果接收的数据小于总数据,就写入文件
if(m_bytesReceive < m_totalBytes)
{
m_bytesReceive += m_tcpServerConnection->bytesAvailable();
m_inBlock = m_tcpServerConnection->readAll();
qDebug()<<"m_inBlock="<<m_inBlock.size();//(最大值为65536)
m_file->write(m_inBlock);
m_inBlock.resize(0);
}
//更新进度条
ui->progressBar->setMaximum(m_totalBytes);
ui->progressBar->setValue(m_bytesReceive);
//数据接收完成时:
if(m_bytesReceive == m_totalBytes)
{
m_tcpServerConnection->close();
m_file->close();
ui->pushButton->setEnabled(true);
ui->label->setText(tr("接收文件%1成功!").arg(m_filePath));
qDebug()<<"接收成功!";
}
}
void Server::displayError(QAbstractSocket::SocketError socketError)
{
qDebug()<<m_tcpServerConnection->errorString();
LOG;
m_tcpServerConnection->close();
ui->progressBar->reset();
ui->label->setText("服务端就绪:");
ui->pushButton->setEnabled(true);
}
void Server::on_pushButton_clicked()
{
qDebug()<<"开始监听:";
start();
}
client客户端代码:
//client.h文件
#ifndef CLIENT_H
#define CLIENT_H
#include <QDialog>
#include <QAbstractSocket>
//class QTcpServer;
class QTcpSocket;
class QFile;
namespace Ui {
class Client;
}
class Client : public QDialog
{
Q_OBJECT
public:
explicit Client(QWidget *parent = 0);
~Client();
private slots:
void openFile();
void send();
void startTransfer();
void updateClientProgress(qint64);
void displayError(QAbstractSocket::SocketError);
void on_openButton_clicked();
void on_sendButton_clicked();
private:
Ui::Client *ui;
QTcpSocket *m_tcpClient;
QFile *m_localFile;//要发送的文件
qint64 m_totalBytes;//发送数据快总大小
qint64 m_bytesWritten;//已经发送的数据大小
qint64 m_bytesToWrite;//剩余数据大小
qint64 m_payloadSize;//每次发送的大小
QString m_filePath;//保存文件的路径
QByteArray m_outBlock;//数据缓冲区,存放每次要发送的数据快
};
#endif // CLIENT_H
//client.cpp文件
#include "client.h"
#include "ui_client.h"
#include <QtNetwork>
#include <QFileDialog>
#pragma execution_character_set("utf-8")
#define LOG (qDebug()<<__FILE__<<__LINE__<<__FUNCTION__)
Client::Client(QWidget *parent) :
QDialog(parent),
ui(new Ui::Client)
{
ui->setupUi(this);
m_payloadSize = 64*1024;//每次发送64kb
m_totalBytes = 0;
m_bytesToWrite = 0;
m_bytesWritten = 0;
m_tcpClient = new QTcpSocket(this);
//在调用connectToHost()并成功建立连接之后发connected()信号
connect(m_tcpClient,SIGNAL(connected()),this,SLOT(startTransfer()));
connect(m_tcpClient,SIGNAL(bytesWritten(qint64)),
this,SLOT(updateClientProgress(qint64)));
connect(m_tcpClient,SIGNAL(error(QAbstractSocket::SocketError)),
this,SLOT(displayError(QAbstractSocket::SocketError)));
ui->sendButton->setEnabled(false);
}
Client::~Client()
{
delete ui;
}
void Client::openFile()
{
m_filePath = QFileDialog::getOpenFileName(this);
if(!m_filePath.isEmpty())
{
ui->sendButton->setEnabled(true);
ui->label_3->setText(tr("open file %1 succes!").arg(m_filePath));
}
}
void Client::send()
{
ui->sendButton->setEnabled(false);
//初始化
m_bytesWritten = 0;
ui->label_3->setText(tr("连接中..."));
m_tcpClient->connectToHost(ui->hostLineEdit->text(),
ui->portLineEdit->text().toInt());
}
void Client::startTransfer()
{
m_localFile = new QFile(m_filePath);//类似一个文件指针
if( !m_localFile->open(QFile::ReadOnly))
{
qDebug()<<"open file error!";
return;
}
//打开成功
qDebug()<<"打开文件成功!";
m_totalBytes = m_localFile->size();//得到文件总大小
qDebug()<<"文件大小:"<<m_localFile->size();
qDebug()<<"m_totalBytes(总大小):="<<m_totalBytes;
qDebug()<<"m_bytesWritten(已发送):="<<m_bytesWritten;
qDebug()<<"m_bytesToWrite(还剩):="<<m_bytesToWrite;
//注意Tcp面向数据流
QDataStream sendOut(&m_outBlock,QIODevice::WriteOnly);
sendOut.setVersion(QDataStream::Qt_5_7);
//找出文件名;
QString currentFileName = m_filePath.right(m_filePath.size()
-m_filePath.lastIndexOf('/')-1);
//保留总大小信息空间,文件名大小信息空间,然后输入文件名
sendOut<<quint64(0)<<quint64(0)<<currentFileName;
m_totalBytes += m_outBlock.size();
sendOut.device()->seek(0);
sendOut<<m_totalBytes<<(quint64)(m_outBlock.size()-2*sizeof(quint64));
//发送完文件头结构后剩余数据的大小
m_bytesToWrite = m_totalBytes-m_tcpClient->write(m_outBlock);
qDebug()<<"m_totalBytes(总大小):="<<m_totalBytes;
qDebug()<<"m_bytesWritten(已发送):="<<m_bytesWritten;
qDebug()<<"m_bytesToWrite(还剩):="<<m_bytesToWrite;
ui->label_3->setText("已连接");
m_outBlock.resize(0);
}
void Client::updateClientProgress(qint64 numBytes)
{
qDebug()<<"m_totalBytes:="<<m_totalBytes;
//已经发送的数据的大小
m_bytesWritten += (int)numBytes;
//如果 已经发送了数据
if(m_bytesToWrite > 0)
{
//每次发送m_payloadSize的数据(64kB),如果剩下的不足64KB,就发送剩余大小
m_outBlock = m_localFile->read(qMin(m_payloadSize,m_bytesToWrite));
//发送完一次还剩下的数据的大小
m_bytesToWrite -=(int)m_tcpClient->write(m_outBlock);
qDebug()<<"---m_byteToWrite---(剩余)="<<m_bytesToWrite;
//每次要清空m_outBloc缓冲区
m_outBlock.resize(0);
}
else
m_localFile->close();
//更新进度条
ui->progressBar->setMaximum(m_totalBytes);
ui->progressBar->setValue(m_bytesWritten);
//如果发送完毕
if(m_totalBytes == m_bytesWritten)
{
ui->label_3->setText(tr("传送文件%1成功!").arg(m_filePath));
m_localFile->close();
m_tcpClient->close();
qDebug()<<"发送完成!";
qDebug()<<"m_totalBytes(总大小):="<<m_totalBytes;
qDebug()<<"m_bytesWritten(已发送):="<<m_bytesWritten;
qDebug()<<"m_bytesToWrite(还剩):="<<m_bytesToWrite;
}
}
void Client::displayError(QAbstractSocket::SocketError)
{
qDebug()<<m_tcpClient->errorString();
LOG;
m_tcpClient->close();
ui->progressBar->reset();
ui->label_3->setText("客户端准备就绪");
ui->sendButton->setEnabled(true);
}
void Client::on_openButton_clicked()
{
ui->progressBar->reset();
ui->label_3->setText("状态:等待打开文件!");
openFile();
}
void Client::on_sendButton_clicked()
{
send();
}