Qt5 基于TCP传输的发送/接收文件服务器(支持多客户端)
版权声明:本文为博主原创文章,未经博主允许不得转载。
一、实现功能
1、服务器端选择待发送的文件,可以是多个
2、开启服务器,支持多客户端接入,能够实时显示每个客户端接入状态
3、等待所有客户端都处于已连接状态时,依次发送文件集给每个客户端,显示每个客户端发送进度
4、发送完成后等待接收客户端发回的文件,显示接收进度
5、关闭服务器
二、实现要点
先讲一下实现上述功能的几个关键点,明白的这几个要点,功能的大框架就搭好了,细节在下一节再讲
1、新建服务器类testServer,继承自QTcpServer
功能:用于接收客户端TCP请求,存储所有客户端信息,向主窗口发送信息
在这个类中实例化QTcpServer的虚函数:
void incomingConnection(int socketDescriptor); //虚函数,有tcp请求时会触发
参数为描述socket ID的int变量
此函数在QTcpServer检测到外来TCP请求时,会自动调用。
注:若要接收局域网内的TCP请求,连接newConnection信号到自定义槽函数
connect(tcpServer,SIGNAL(newConnection()),this,SLOT(acceptConnection()));
但是这种方法无法处理多客户端
2、新建客户端类testClient,继承自QTcpSocket
功能:存储每个接入的客户端信息,控制发送/接收文件,向testServer发送信息
在testServer的incomingConnection函数中,通过设置socketDescriptor,初始化一个testClient实例
- testClient *socket = new testClient(this);
- if (!socket->setSocketDescriptor(socketDescriptor))
- {
- emit error(socket->error());
- return;
- }
testClient *socket = new testClient(this);
if (!socket->setSocketDescriptor(socketDescriptor))
{
emit error(socket->error());
return;
}
3、发送数据
实例化testClient类,通过void writeData(const char * data, qint64 size)函数将数据写入TCP流
testClient *a;
a->writeData("message",8);
一般来说,使用QByteArray和QDataStream进行格式化的数据写入,设置流化数据格式类型为QDataStream::Qt_5_0,与客户端一致
- testClient *a;
- QByteArray outBlock; //缓存一次发送的数据
- QDataStream sendOut(&outBlock, QIODevice::WriteOnly); //数据流
- sendOut.setVersion(QDataStream::Qt_5_0);
- sendOut << "message";
- a->writeData(outBlock,outBlock.size());
- outBlock.resize(0);
testClient *a;
QByteArray outBlock; //缓存一次发送的数据
QDataStream sendOut(&outBlock, QIODevice::WriteOnly); //数据流
sendOut.setVersion(QDataStream::Qt_5_0);
sendOut << "message";
a->writeData(outBlock,outBlock.size());
outBlock.resize(0);
注意outBlock如果为全局变量,最后需要resize,否则下一次写入时会出错
在testClient构造函数中连接数据成功发送后产生的bytesWritten()信号
connect(this, SIGNAL(bytesWritten(qint64)), this, SLOT(updateClientProgress(qint64)));
参数为成功写入TCP流的字节数
通过捕获这个信号,可以实现数据的连续发送。因为发送文件时,数据是一块一块发送的,比如设定一块的大小为20字节,那么发送完20字节,怎么继续发送呢?就靠的是这个函数,再用全局变量记录下总共发送的字节数,便可以控制发送的结束。
4、发送文件
先把文件读到QFile变量中,再通过QBytesArray发送
- testClient *a;
- QFile sendFile = new QFile(sendFilePath);<span style="WHITE-SPACE: pre"> </span>//读取发送文件路径
- if (!sendFile->open(QFile::ReadOnly )) //读取发送文件
- { return;}
- QByteArray outBlock;
- outBlock = sendFile->read(sendFile.size());
- a->writeData(outBlock,outBlock.size());
testClient *a;
QFile sendFile = new QFile(sendFilePath); //读取发送文件路径
if (!sendFile->open(QFile::ReadOnly )) //读取发送文件
{ return;}
QByteArray outBlock;
outBlock = sendFile->read(sendFile.size());
a->writeData(outBlock,outBlock.size());
5、接收数据
依旧是testClient类,连接有readyRead()信号,readyRead()信号为新连接中有可读数据时发出
connect(this, SIGNAL(readyRead()), this, SLOT(recvData()));
首先将testClient连接实例(如testClient *a)封装到QDataStream变量中,设置流化数据格式类型为QDataStream::Qt_5_0,与客户端一致。
在读入数据前,用a->bytesAvailable()检测数据流中可读入的字节数,然后就可以通过"<<"操作符读入数据了。
- QDataStream in(a); //本地数据流
- in.setVersion(QDataStream::Qt_5_0); //设置流版本,以防数据格式错误
- quint8 receiveClass;
- if(this->bytesAvailable() >= (sizeof(quint8)))
- {
- in >> receiveClass;
- }
QDataStream in(a); //本地数据流
in.setVersion(QDataStream::Qt_5_0); //设置流版本,以防数据格式错误
quint8 receiveClass;
if(this->bytesAvailable() >= (sizeof(quint8)))
{
in >> receiveClass;
}
6、接收文件
使用readAll读入TCP流中所有数据,再写入到QFile变量中
- QByteArray inBlock;
- inBlock = a->readAll(); //读入所有数据
- QFile receivedFile = new QFile(receiveFilePath); //打开接收文件
- if (!receivedFile->open(QFile::WriteOnly ))
- { return;}
- QFile receivedFile->write(inBlock); //写入文件
- inBlock.resize(0);
QByteArray inBlock;
inBlock = a->readAll(); //读入所有数据
QFile receivedFile = new QFile(receiveFilePath); //打开接收文件
if (!receivedFile->open(QFile::WriteOnly ))
{ return;}
QFile receivedFile->write(inBlock); //写入文件
inBlock.resize(0);
三、具体实现
1、全局定义
需要定义一些全局变量和常量,定义如下
- //存储文件路径和文件名的结构体
- struct openFileStruct
- {
- QString filePath;
- QString fileName;
- };
- struct clientInfo //客户端信息
- {
- QString ip; //ip
- int state; //状态
- QString id; //id
- };
- const quint8 sendtype_file = 0; //发送类型是文件
- const quint8 sendtype_start_test = 10; //发送类型是开始测试
- const quint8 sendtype_msg = 20; //发送类型是消息
- const quint16 filetype_list = 0; //文件类型为列表
- const quint16 filetype_wavfile = 1; //文件类型为wav文件
- const QString clientStatus[7] = //客户端状态
- {QObject::tr("Unconnected"),QObject::tr("HostLookup"),QObject::tr("Connecting"),
- QObject::tr("Connected"),QObject::tr("Bound"),QObject::tr("Listening"),QObject::tr("Closing")};
//存储文件路径和文件名的结构体
struct openFileStruct
{
QString filePath;
QString fileName;
};
struct clientInfo //客户端信息
{
QString ip; //ip
int state; //状态
QString id; //id
};
const quint8 sendtype_file = 0; //发送类型是文件
const quint8 sendtype_start_test = 10; //发送类型是开始测试
const quint8 sendtype_msg = 20; //发送类型是消息
const quint16 filetype_list = 0; //文件类型为列表
const quint16 filetype_wavfile = 1; //文件类型为wav文件
const QString clientStatus[7] = //客户端状态
{QObject::tr("Unconnected"),QObject::tr("HostLookup"),QObject::tr("Connecting"),
QObject::tr("Connected"),QObject::tr("Bound"),QObject::tr("Listening"),QObject::tr("Closing")};
openFileStruct 用于主窗口,存储发送文件的路径和文件名clientInfo 用于主窗口,存储每个客户端的信息
规定传输协议为先发送一个quint8的标志位,规定传输类型,即为sendtype定义的部分
sendtype为0时,发送文件。filetype为文件类型
发送文件协议为:发送类型 | 数据总大小 | 文件类型 | 文件名大小 | 文件名 | 文件内容
发送文件时,先发送一个文件列表,每行一个文件名,再逐个发送文件
clientStatus为客户端状态,与Qt内部的socket status定义相同
2、testClient类
一个testClient类实例为一个socket客户端描述
(1)类声明:
- class testClient : public QTcpSocket
- {
- Q_OBJECT
- public:
- testClient(QObject *parent = 0);
- int num; //客户端序号
- void prepareTransfer(std::vector<openFileStruct>& openFileList,int testtype_t); //准备传输文件
- signals:
- void newClient(QString, int, int); //客户端信息变更信号
- void outputlog(QString); //输出日志信息
- void updateProgressSignal(qint64,qint64,int); //更新发送进度信号
- void newClientInfo(QString,int,int); //更新客户端信息
- private slots:
- void recvData(); //接收数据
- void clientConnected(); //已连接
- void clientDisconnected(); //断开连接
- void newState(QAbstractSocket::SocketState); //新状态
- void startTransfer(const quint16); //开始传输文件
- void updateClientProgress(qint64 numBytes); //发送文件内容
- void getSendFileList(QString path); //在指定路径生成发送文件列表
- quint64 getSendTotalSize(); //获取sendFilePath对应文件加上头的大小
- private:
- //发送文件所需变量
- qint64 loadSize; //每次接收的数据块大小
- qint64 TotalSendBytes; //总共需发送的字节数
- qint64 bytesWritten; //已发送字节数
- qint64 bytesToWrite; //待发送字节数
- QString sendFileName; //待发送的文件的文件名
- QString sendFilePath; //待发送的文件的文件路径
- QFile *sendFile; //待发送的文件
- QByteArray outBlock; //缓存一次发送的数据
- int sendNum; //记录当前发送到第几个文件
- int sendFileNum; //记录发送文件个数
- int totalSendSize; //记录发送文件总大小
- quint64 proBarMax; //发送进度条最大值
- quint64 proBarValue; //进度条当前值
- std::vector<openFileStruct> openList; //发送文件列表
- //接收文件用变量
- quint8 receiveClass; //0:文件,1:开始测试,20:客户端接收到文件反馈
- quint16 fileClass; //待接收文件类型
- qint64 TotalRecvBytes; //总共需接收的字节数
- qint64 bytesReceived; //已接收字节数
- qint64 fileNameSize; //待接收文件名字节数
- QString receivedFileName; //待接收文件的文件名
- QFile *receivedFile; //待接收文件
- QByteArray inBlock; //接收临时存储块
- qint64 totalBytesReceived; //接收的总大小
- qint32 recvNum; //现在接收的是第几个
- quint64 recvFileNum; //接收文件个数
- quint64 totalRecvSize; //接收文件总大小
- bool isReceived[3]; //是否收到客户端的接收文件反馈
- };
class testClient : public QTcpSocket
{
Q_OBJECT
public:
testClient(QObject *parent = 0);
int num; //客户端序号
void prepareTransfer(std::vector<openFileStruct>& openFileList,int testtype_t); //准备传输文件
signals:
void newClient(QString, int, int); //客户端信息变更信号
void outputlog(QString); //输出日志信息
void updateProgressSignal(qint64,qint64,int); //更新发送进度信号
void newClientInfo(QString,int,int); //更新客户端信息
private slots:
void recvData(); //接收数据
void clientConnected(); //已连接
void clientDisconnected(); //断开连接
void newState(QAbstractSocket::SocketState); //新状态
void startTransfer(const quint16); //开始传输文件
void updateClientProgress(qint64 numBytes); //发送文件内容
void getSendFileList(QString path); //在指定路径生成发送文件列表
quint64 getSendTotalSize(); //获取sendFilePath对应文件加上头的大小
private:
//发送文件所需变量
qint64 loadSize; //每次接收的数据块大小
qint64 TotalSendBytes; //总共需发送的字节数
qint64 bytesWritten; //已发送字节数
qint64 bytesToWrite; //待发送字节数
QString sendFileName; //待发送的文件的文件名
QString sendFilePath; //待发送的文件的文件路径
QFile *sendFile; //待发送的文件
QByteArray outBlock; //缓存一次发送的数据
int sendNum; //记录当前发送到第几个文件
int sendFileNum; //记录发送文件个数
int totalSendSize; //记录发送文件总大小
quint64 proBarMax; //发送进度条最大值
quint64 proBarValue; //进度条当前值
std::vector<openFileStruct> openList; //发送文件列表
//接收文件用变量
quint8 receiveClass; //0:文件,1:开始测试,20:客户端接收到文件反馈
quint16 fileClass; //待接收文件类型
qint64 TotalRecvBytes; //总共需接收的字节数
qint64 bytesReceived; //已接收字节数
qint64 fileNameSize; //待接收文件名字节数
QString receivedFileName; //待接收文件的文件名
QFile *receivedFile; //待接收文件
QByteArray inBlock; //接收临时存储块
qint64 totalBytesReceived; //接收的总大小
qint32 recvNum; //现在接收的是第几个
quint64 recvFileNum; //接收文件个数
quint64 totalRecvSize; //接收文件总大小
bool isReceived[3]; //是否收到客户端的接收文件反馈
};
public为需要上层访问的公有变量和函数
signals为向上层发送的信号,保证主窗口实时显示信息
private slots包括底层实现发送、接收消息的函数,以及信号处理的槽
private为私有变量,包括发送文件和接收文件所需的变量
(2)类定义:
构造函数:
- testClient::testClient(QObject *parent) :
- QTcpSocket(parent)
- {
- //发送变量初始化
- num = -1;
- loadSize = 100*1024;
- openList.clear();
- //接收变量初始化
- TotalRecvBytes = 0;
- bytesReceived = 0;
- fileNameSize = 0;
- recvFileNum = 0;
- totalRecvSize = 0;
- totalBytesReceived = 0;
- recvNum = 0;
- receiveClass = 255;
- fileClass = 0;
- for(int i=0; i<3; i++)
- isReceived[i] = false;
- //连接信号和槽
- connect(this, SIGNAL(readyRead()), this, SLOT(recvData()));
- connect(this, SIGNAL(disconnected()), this, SLOT(clientDisconnected()));
- connect(this, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
- this, SLOT(newState(QAbstractSocket::SocketState)));
- connect(this, SIGNAL(bytesWritten(qint64)), this, SLOT(updateClientProgress(qint64)));
- }
testClient::testClient(QObject *parent) :
QTcpSocket(parent)
{
//发送变量初始化
num = -1;
loadSize = 100*1024;
openList.clear();
//接收变量初始化
TotalRecvBytes = 0;
bytesReceived = 0;
fileNameSize = 0;
recvFileNum = 0;
totalRecvSize = 0;
totalBytesReceived = 0;
recvNum = 0;
receiveClass = 255;
fileClass = 0;
for(int i=0; i<3; i++)
isReceived[i] = false;
//连接信号和槽
connect(this, SIGNAL(readyRead()), this, SLOT(recvData()));
connect(this, SIGNAL(disconnected()), this, SLOT(clientDisconnected()));
connect(this, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
this, SLOT(newState(QAbstractSocket::SocketState)));
connect(this, SIGNAL(bytesWritten(qint64)), this, SLOT(updateClientProgress(qint64)));
}
初始化变量
连接qt的tcp信号和自定义槽,包括:
readyRead() 接收消息信号
disconnected() 断开连接信号
stateChanged(QAbstractSocket::SocketState) 连接状态变更信号
bytesWritten(qint64) 已写入发送消息流信号
接收数据槽,设定服务器只接收一个文件:
- void testClient::recvData() //接收数据,服务器只接收客户端的一个结果文件
- {
- QDataStream in(this); //本地数据流
- in.setVersion(QDataStream::Qt_5_0); //设置流版本,以防数据格式错误
- QString unit;
- qint32 msg;
- if(bytesReceived <= (sizeof(quint8)))
- {
- if(this->bytesAvailable() >= (sizeof(quint8)))
- {
- in >> receiveClass;
- }
- switch(receiveClass)
- {
- case sendtype_file: //接收文件
- bytesReceived += sizeof(quint8);
- qDebug() << "bytesReceived: " << bytesReceived;
- break;
- case sendtype_msg:
- in >> msg;
- if(msg == 0) //接收文件列表
- {
- emit outputlog(tr("client %1 have received list file")
- .arg(this->peerAddress().toString()));
- }
- else if(msg == 1) //接收文件
- {
- emit outputlog(tr("client %1 have received file(s)")
- .arg(this->peerAddress().toString()));
- }
- return;
- default:
- return;
- }
- }
- if(bytesReceived >= (sizeof(quint8)) && bytesReceived <= (sizeof(quint8) + sizeof(qint64)*2 + sizeof(quint16))) //开始接收文件,先接受报头
- {
- //收3个int型数据,分别存储总长度、文件类型和文件名长度
- if( ( this->bytesAvailable() >= (sizeof(qint64)*2 + sizeof(quint16)) ) && (fileNameSize == 0) )
- {
- in >> TotalRecvBytes >> fileClass >> fileNameSize;
- bytesReceived += sizeof(qint64)*2; //收到多少字节
- bytesReceived += sizeof(quint16);
- if(fileClass == filetype_result)
- {
- recvNum = 1;
- recvFileNum = 1;
- totalRecvSize = TotalRecvBytes; //只有一个文件,文件总大小为该文件发送大小
- totalBytesReceived += sizeof(qint64)*2;
- totalBytesReceived += sizeof(quint16);
- totalBytesReceived += sizeof(quint8);
- emit newClientInfo(tr("receiving result"),num,4);
- }
- else
- {
- QMessageBox::warning(NULL,tr("WARNING"),tr("client %1 send wrong type of file to server")
- .arg(this->peerAddress().toString()));
- return;
- }
- }
- //接着收文件名并建立文件
- if((this->bytesAvailable() >= fileNameSize)&&(fileNameSize != 0))
- {
- in >> receivedFileName;
- bytesReceived += fileNameSize;
- totalBytesReceived += fileNameSize;
- QString receiveFilePath = receive_path + "/" + receivedFileName; //接收文件路径
- emit outputlog(tr("receive from client %1\nsave as:%2")
- .arg(this->peerAddress().toString())
- .arg(receiveFilePath));
- //建立文件
- receivedFile = new QFile(receiveFilePath);
- if (!receivedFile->open(QFile::WriteOnly ))
- {
- QMessageBox::warning(NULL, tr("WARNING"),
- tr("cannot open file %1:\n%2.").arg(receivedFileName).arg(receivedFile->errorString()));
- return;
- }
- }
- else
- {
- return;
- }
- }
- //一个文件没有接受完
- if (bytesReceived < TotalRecvBytes)
- {
- //可用内容比整个文件长度-已接收长度短,则全部接收并写入文件
- qint64 tmp_Abailable = this->bytesAvailable();
- if(tmp_Abailable <= (TotalRecvBytes - bytesReceived))
- {
- bytesReceived += tmp_Abailable;
- totalBytesReceived += tmp_Abailable;
- inBlock = this->readAll();
- receivedFile->write(inBlock);
- inBlock.resize(0);
- tmp_Abailable = 0;
- }
- //可用内容比整个文件长度-已接收长度长,则接收所需内容,并写入文件
- else
- {
- inBlock = this->read(TotalRecvBytes - bytesReceived);
- if(fileClass == filetype_wavfile)
- {
- totalBytesReceived += (TotalRecvBytes - bytesReceived);
- }
- bytesReceived = TotalRecvBytes;
- receivedFile->write(inBlock);
- inBlock.resize(0);
- tmp_Abailable = 0;
- }
- }
- emit updateProgressSignal(totalBytesReceived,totalRecvSize,num); //更新发送进度信号
- //善后:一个文件全部收完则重置变量关闭文件流,删除指针
- if (bytesReceived == TotalRecvBytes)
- {
- //变量重置
- TotalRecvBytes = 0;
- bytesReceived = 0;
- fileNameSize = 0;
- receiveClass = 255;
- receivedFile->close();
- delete receivedFile;
- //输出信息
- emit outputlog(tr("Have received file: %1 from client %2")
- .arg(receivedFileName)
- .arg(this->peerAddress().toString())); //log information
- //全部文件接收完成
- if(recvNum == recvFileNum)
- {
- //变量重置
- recvFileNum = 0;
- recvNum = 0;
- totalBytesReceived = 0;
- totalRecvSize = 0;
- emit outputlog(tr("Receive all done!"));
- }
- if(fileClass == filetype_result)
- {
- emit newClientInfo(tr("Evaluating"),num,4);
- }
- }
- }
void testClient::recvData() //接收数据,服务器只接收客户端的一个结果文件
{
QDataStream in(this); //本地数据流
in.setVersion(QDataStream::Qt_5_0); //设置流版本,以防数据格式错误
QString unit;
qint32 msg;
if(bytesReceived <= (sizeof(quint8)))
{
if(this->bytesAvailable() >= (sizeof(quint8)))
{
in >> receiveClass;
}
switch(receiveClass)
{
case sendtype_file: //接收文件
bytesReceived += sizeof(quint8);
qDebug() << "bytesReceived: " << bytesReceived;
break;
case sendtype_msg:
in >> msg;
if(msg == 0) //接收文件列表
{
emit outputlog(tr("client %1 have received list file")
.arg(this->peerAddress().toString()));
}
else if(msg == 1) //接收文件
{
emit outputlog(tr("client %1 have received file(s)")
.arg(this->peerAddress().toString()));
}
return;
default:
return;
}
}
if(bytesReceived >= (sizeof(quint8)) && bytesReceived <= (sizeof(quint8) + sizeof(qint64)*2 + sizeof(quint16))) //开始接收文件,先接受报头
{
//收3个int型数据,分别存储总长度、文件类型和文件名长度
if( ( this->bytesAvailable() >= (sizeof(qint64)*2 + sizeof(quint16)) ) && (fileNameSize == 0) )
{
in >> TotalRecvBytes >> fileClass >> fileNameSize;
bytesReceived += sizeof(qint64)*2; //收到多少字节
bytesReceived += sizeof(quint16);
if(fileClass == filetype_result)
{
recvNum = 1;
recvFileNum = 1;
totalRecvSize = TotalRecvBytes; //只有一个文件,文件总大小为该文件发送大小
totalBytesReceived += sizeof(qint64)*2;
totalBytesReceived += sizeof(quint16);
totalBytesReceived += sizeof(quint8);
emit newClientInfo(tr("receiving result"),num,4);
}
else
{
QMessageBox::warning(NULL,tr("WARNING"),tr("client %1 send wrong type of file to server")
.arg(this->peerAddress().toString()));
return;
}
}
//接着收文件名并建立文件
if((this->bytesAvailable() >= fileNameSize)&&(fileNameSize != 0))
{
in >> receivedFileName;
bytesReceived += fileNameSize;
totalBytesReceived += fileNameSize;
QString receiveFilePath = receive_path + "/" + receivedFileName; //接收文件路径
emit outputlog(tr("receive from client %1\nsave as:%2")
.arg(this->peerAddress().toString())
.arg(receiveFilePath));
//建立文件
receivedFile = new QFile(receiveFilePath);
if (!receivedFile->open(QFile::WriteOnly ))
{
QMessageBox::warning(NULL, tr("WARNING"),
tr("cannot open file %1:\n%2.").arg(receivedFileName).arg(receivedFile->errorString()));
return;
}
}
else
{
return;
}
}
//一个文件没有接受完
if (bytesReceived < TotalRecvBytes)
{
//可用内容比整个文件长度-已接收长度短,则全部接收并写入文件
qint64 tmp_Abailable = this->bytesAvailable();
if(tmp_Abailable <= (TotalRecvBytes - bytesReceived))
{
bytesReceived += tmp_Abailable;
totalBytesReceived += tmp_Abailable;
inBlock = this->readAll();
receivedFile->write(inBlock);
inBlock.resize(0);
tmp_Abailable = 0;
}
//可用内容比整个文件长度-已接收长度长,则接收所需内容,并写入文件
else
{
inBlock = this->read(TotalRecvBytes - bytesReceived);
if(fileClass == filetype_wavfile)
{
totalBytesReceived += (TotalRecvBytes - bytesReceived);
}
bytesReceived = TotalRecvBytes;
receivedFile->write(inBlock);
inBlock.resize(0);
tmp_Abailable = 0;
}
}
emit updateProgressSignal(totalBytesReceived,totalRecvSize,num); //更新发送进度信号
//善后:一个文件全部收完则重置变量关闭文件流,删除指针
if (bytesReceived == TotalRecvBytes)
{
//变量重置
TotalRecvBytes = 0;
bytesReceived = 0;
fileNameSize = 0;
receiveClass = 255;
receivedFile->close();
delete receivedFile;
//输出信息
emit outputlog(tr("Have received file: %1 from client %2")
.arg(receivedFileName)
.arg(this->peerAddress().toString())); //log information
//全部文件接收完成
if(recvNum == recvFileNum)
{
//变量重置
recvFileNum = 0;
recvNum = 0;
totalBytesReceived = 0;
totalRecvSize = 0;
emit outputlog(tr("Receive all done!"));
}
if(fileClass == filetype_result)
{
emit newClientInfo(tr("Evaluating"),num,4);
}
}
}
接收文件时,需要一步一步判断接收字节是否大于协议的下一项,若大于则再判断其值接收的文件类型必须是filetype_result
未接收完记录接收进度
接收完文件进行善后,关闭文件流删除指针等
每进行完一步,向上层发送信号,包括客户端信息和接收进度
更新客户端状态函数,向上层发送信号
- void testClient::newState(QAbstractSocket::SocketState state) //新状态
- {
- emit newClient(this->peerAddress().toString(), (int)state, num);
- }
void testClient::newState(QAbstractSocket::SocketState state) //新状态
{
emit newClient(this->peerAddress().toString(), (int)state, num);
}
发送的信号参数为:该客户端IP,状态序号,客户端编号
开始传输文件函数(发送包含文件信息的文件头)
- void testClient::startTransfer(const quint16 type) //开始传输文件
- {
- TotalSendBytes = 0; //总共需发送的字节数
- bytesWritten = 0; //已发送字节数
- bytesToWrite = 0; //待发送字节数
- //开始传输文件信号
- emit outputlog(tr("start sending file to client: %1\n filename: %2")
- .arg(this->peerAddress().toString())
- .arg(sendFileName));
- sendFile = new QFile(sendFilePath);
- if (!sendFile->open(QFile::ReadOnly )) //读取发送文件
- {
- QMessageBox::warning(NULL, tr("WARNING"),
- tr("can not read file %1:\n%2.")
- .arg(sendFilePath)
- .arg(sendFile->errorString()));
- return;
- }
- TotalSendBytes = sendFile->size();
- QDataStream sendOut(&outBlock, QIODevice::WriteOnly);
- sendOut.setVersion(QDataStream::Qt_5_0);
- //写入发送类型,数据大小,文件类型,文件名大小,文件名
- sendOut << quint8(0) << qint64(0) << quint16(0) << qint64(0) << sendFileName;
- TotalSendBytes += outBlock.size();
- sendOut.device()->seek(0);
- sendOut << quint8(sendtype_file)<< TotalSendBytes << quint16(type)
- << qint64((outBlock.size() - sizeof(qint64) * 2) - sizeof(quint16) - sizeof(quint8));
- this->writeData(outBlock,outBlock.size());
- //this->flush();
- bytesToWrite = TotalSendBytes - outBlock.size();
- outBlock.resize(0);
- }
void testClient::startTransfer(const quint16 type) //开始传输文件
{
TotalSendBytes = 0; //总共需发送的字节数
bytesWritten = 0; //已发送字节数
bytesToWrite = 0; //待发送字节数
//开始传输文件信号
emit outputlog(tr("start sending file to client: %1\n filename: %2")
.arg(this->peerAddress().toString())
.arg(sendFileName));
sendFile = new QFile(sendFilePath);
if (!sendFile->open(QFile::ReadOnly )) //读取发送文件
{
QMessageBox::warning(NULL, tr("WARNING"),
tr("can not read file %1:\n%2.")
.arg(sendFilePath)
.arg(sendFile->errorString()));
return;
}
TotalSendBytes = sendFile->size();
QDataStream sendOut(&outBlock, QIODevice::WriteOnly);
sendOut.setVersion(QDataStream::Qt_5_0);
//写入发送类型,数据大小,文件类型,文件名大小,文件名
sendOut << quint8(0) << qint64(0) << quint16(0) << qint64(0) << sendFileName;
TotalSendBytes += outBlock.size();
sendOut.device()->seek(0);
sendOut << quint8(sendtype_file)<< TotalSendBytes << quint16(type)
<< qint64((outBlock.size() - sizeof(qint64) * 2) - sizeof(quint16) - sizeof(quint8));
this->writeData(outBlock,outBlock.size());
//this->flush();
bytesToWrite = TotalSendBytes - outBlock.size();
outBlock.resize(0);
}
读取发送文件建立发送文件头
用writeData将文件头写入TCP发送流,记录已发送字节数
发送文件内容:
- void testClient::updateClientProgress(qint64 numBytes) //发送文件内容
- {
- if(TotalSendBytes == 0)
- return;
- bytesWritten += (int)numBytes;
- proBarValue += (int)numBytes;
- emit updateProgressSignal(proBarValue,proBarMax,num); //更新发送进度信号
- if (bytesToWrite > 0)
- {
- outBlock = sendFile->read(qMin(bytesToWrite, loadSize));
- bytesToWrite -= (int)this->writeData(outBlock,outBlock.size());
- outBlock.resize(0);
- }
- else
- {
- sendFile->close();
- //结束传输文件信号
- if(TotalSendBytes < 1024)
- {
- emit outputlog(tr("finish sending file to client: %1\n filename: %2 %3B")
- .arg(this->peerAddress().toString())
- .arg(sendFileName)
- .arg(TotalSendBytes));
- }
- else if(TotalSendBytes < 1024*1024)
- {
- emit outputlog(tr("finish sending file to client: %1\n filename: %2 %3KB")
- .arg(this->peerAddress().toString())
- .arg(sendFileName)
- .arg(TotalSendBytes / 1024.0));
- }
- else
- {
- emit outputlog(tr("finish sending file to client: %1\n filename: %2 %3MB")
- .arg(this->peerAddress().toString())
- .arg(sendFileName)
- .arg(TotalSendBytes / (1024.0*1024.0)));
- }
- if(sendNum < openList.size()) //还有文件需要发送
- {
- if(sendNum == 0)
- {
- //QFile::remove(sendFilePath); //删除列表文件
- proBarMax = totalSendSize;
- proBarValue = 0;
- }
- sendFilePath = openList[sendNum].filePath;
- sendFileName = openList[sendNum].fileName;
- sendNum++;
- startTransfer(filetype_wavfile);
- }
- else //发送结束
- {
- emit newClientInfo(tr("send complete"),num,4);
- TotalSendBytes = 0; //总共需发送的字节数
- bytesWritten = 0; //已发送字节数
- bytesToWrite = 0; //待发送字节数
- }
- }
- }
void testClient::updateClientProgress(qint64 numBytes) //发送文件内容
{
if(TotalSendBytes == 0)
return;
bytesWritten += (int)numBytes;
proBarValue += (int)numBytes;
emit updateProgressSignal(proBarValue,proBarMax,num); //更新发送进度信号
if (bytesToWrite > 0)
{
outBlock = sendFile->read(qMin(bytesToWrite, loadSize));
bytesToWrite -= (int)this->writeData(outBlock,outBlock.size());
outBlock.resize(0);
}
else
{
sendFile->close();
//结束传输文件信号
if(TotalSendBytes < 1024)
{
emit outputlog(tr("finish sending file to client: %1\n filename: %2 %3B")
.arg(this->peerAddress().toString())
.arg(sendFileName)
.arg(TotalSendBytes));
}
else if(TotalSendBytes < 1024*1024)
{
emit outputlog(tr("finish sending file to client: %1\n filename: %2 %3KB")
.arg(this->peerAddress().toString())
.arg(sendFileName)
.arg(TotalSendBytes / 1024.0));
}
else
{
emit outputlog(tr("finish sending file to client: %1\n filename: %2 %3MB")
.arg(this->peerAddress().toString())
.arg(sendFileName)
.arg(TotalSendBytes / (1024.0*1024.0)));
}
if(sendNum < openList.size()) //还有文件需要发送
{
if(sendNum == 0)
{
//QFile::remove(sendFilePath); //删除列表文件
proBarMax = totalSendSize;
proBarValue = 0;
}
sendFilePath = openList[sendNum].filePath;
sendFileName = openList[sendNum].fileName;
sendNum++;
startTransfer(filetype_wavfile);
}
else //发送结束
{
emit newClientInfo(tr("send complete"),num,4);
TotalSendBytes = 0; //总共需发送的字节数
bytesWritten = 0; //已发送字节数
bytesToWrite = 0; //待发送字节数
}
}
}
文件未发送完:记录发送字节数,writeData继续发送,writeData一旦写入发送流,自动又进入updateClientProgress函数文件已发送完:发出信号,检测是否还有文件需要发送,若有则调用startTransfer继续发送,若没有则发出信号,更新客户端信息
准备传输文件函数,被上层调用,参数为发送文件列表:
- void testClient::prepareTransfer(std::vector<openFileStruct>& openFileList) //准备传输文件
- {
- if(openFileList.size() == 0) //没有文件
- {
- return;
- }
- testtype_now = testtype_t;
- isSendKeyword = false;
- for(int i=0; i<2; i++)
- isReceived[i] = false;
- openList.clear();
- openList.assign(openFileList.begin(),openFileList.end()); //拷贝文件列表
- QString sendFileListName = "sendFileList.txt";
- QString sendFileListPath = temp_Path + "/" + sendFileListName;
- getSendFileList(sendFileListPath); //在指定路径生成发送文件列表
- emit newClientInfo(tr("sending test files"),num,4); //更新主窗口测试阶段
- sendFilePath = sendFileListPath;
- sendFileName = sendFileListName;
- sendNum = 0; //发送到第几个文件
- proBarMax = getSendTotalSize();
- proBarValue = 0;
- startTransfer(filetype_list); //开始传输文件
- }
void testClient::prepareTransfer(std::vector<openFileStruct>& openFileList) //准备传输文件
{
if(openFileList.size() == 0) //没有文件
{
return;
}
testtype_now = testtype_t;
isSendKeyword = false;
for(int i=0; i<2; i++)
isReceived[i] = false;
openList.clear();
openList.assign(openFileList.begin(),openFileList.end()); //拷贝文件列表
QString sendFileListName = "sendFileList.txt";
QString sendFileListPath = temp_Path + "/" + sendFileListName;
getSendFileList(sendFileListPath); //在指定路径生成发送文件列表
emit newClientInfo(tr("sending test files"),num,4); //更新主窗口测试阶段
sendFilePath = sendFileListPath;
sendFileName = sendFileListName;
sendNum = 0; //发送到第几个文件
proBarMax = getSendTotalSize();
proBarValue = 0;
startTransfer(filetype_list); //开始传输文件
}
拷贝文件列表生成发送文件列表文件
更新主窗口信息
开始传输列表文件
上面调用的生成列表文件函数如下:
- void testClient::getSendFileList(QString path) //在指定路径生成发送文件列表
- {
- sendFileNum = openList.size(); //记录发送文件个数
- totalSendSize = 0; //记录发送文件总大小
- for(int i = 0; i < sendFileNum; i++)
- {
- sendFileName = openList[i].fileName;
- sendFilePath = openList[i].filePath;
- totalSendSize += getSendTotalSize();
- }
- FILE *fp;
- fp = fopen(path.toLocal8Bit().data(),"w");
- fprintf(fp,"%d\n",sendFileNum);
- fprintf(fp,"%d\n",totalSendSize);
- for(int i = 0; i < sendFileNum; i++)
- {
- fprintf(fp,"%s\n",openList[i].fileName.toLocal8Bit().data());
- }
- fclose(fp);
- }
void testClient::getSendFileList(QString path) //在指定路径生成发送文件列表
{
sendFileNum = openList.size(); //记录发送文件个数
totalSendSize = 0; //记录发送文件总大小
for(int i = 0; i < sendFileNum; i++)
{
sendFileName = openList[i].fileName;
sendFilePath = openList[i].filePath;
totalSendSize += getSendTotalSize();
}
FILE *fp;
fp = fopen(path.toLocal8Bit().data(),"w");
fprintf(fp,"%d\n",sendFileNum);
fprintf(fp,"%d\n",totalSendSize);
for(int i = 0; i < sendFileNum; i++)
{
fprintf(fp,"%s\n",openList[i].fileName.toLocal8Bit().data());
}
fclose(fp);
}
被上面调用getSendTotalSize函数如下:
- quint64 testClient::getSendTotalSize() //获取sendFilePath对应文件加上头的大小
- {
- int totalsize;
- //计算列表文件及文件头总大小
- QFile *file = new QFile(sendFilePath);
- if (!file->open(QFile::ReadOnly )) //读取发送文件
- {
- QMessageBox::warning(NULL, tr("WARNING"),
- tr("can not read file %1:\n%2.")
- .arg(sendFilePath)
- .arg(file->errorString()));
- return 0;
- }
- totalsize = file->size(); //文件内容大小
- QDataStream sendOut(&outBlock, QIODevice::WriteOnly);
- sendOut.setVersion(QDataStream::Qt_5_0);
- //写入发送类型,数据大小,文件类型,文件名大小,文件名
- sendOut << quint8(0) << qint64(0) << quint16(0) << qint64(0) << sendFileName;
- totalsize += outBlock.size(); //文件头大小
- file->close();
- outBlock.resize(0);
- return totalsize;
- }
quint64 testClient::getSendTotalSize() //获取sendFilePath对应文件加上头的大小
{
int totalsize;
//计算列表文件及文件头总大小
QFile *file = new QFile(sendFilePath);
if (!file->open(QFile::ReadOnly )) //读取发送文件
{
QMessageBox::warning(NULL, tr("WARNING"),
tr("can not read file %1:\n%2.")
.arg(sendFilePath)
.arg(file->errorString()));
return 0;
}
totalsize = file->size(); //文件内容大小
QDataStream sendOut(&outBlock, QIODevice::WriteOnly);
sendOut.setVersion(QDataStream::Qt_5_0);
//写入发送类型,数据大小,文件类型,文件名大小,文件名
sendOut << quint8(0) << qint64(0) << quint16(0) << qint64(0) << sendFileName;
totalsize += outBlock.size(); //文件头大小
file->close();
outBlock.resize(0);
return totalsize;
}
3、testServer类
一个testClient类实例为一个socket服务器端描述
(1)类声明:
- class testServer : public QTcpServer
- {
- Q_OBJECT
- public:
- testServer(QObject *parent = 0);
- std::vector<testClientp> clientList; //客户端tcp连接
- std::vector<QString> ipList; //客户端ip
- int totalClient; //客户端数
- protected:
- void incomingConnection(int socketDescriptor); //虚函数,有tcp请求时会触发
- signals:
- void error(QTcpSocket::SocketError socketError); //错误信号
- void newClientSignal(QString clientIP,int state,int threadNum); //将新客户端信息发给主窗口
- void updateProgressSignal(qint64,qint64,int); //更新发送进度信号
- void outputlogSignal(QString); //发送日志消息信号
- void newClientInfoSignal(QString,int,int); //更新客户端信息
- public slots:
- void newClientSlot(QString clientIP,int state,int threadNum); //将新客户端信息发给主窗口
- void updateProgressSlot(qint64,qint64,int); //更新发送进度槽
- void outputlogSlot(QString); //发送日志消息槽
- void newClientInfoSlot(QString,int,int); //更新客户端信息
- private:
- int getClientNum(testClientp socket); //检测用户,若存在,返回下标,若不存在,返回用户数
- };
class testServer : public QTcpServer
{
Q_OBJECT
public:
testServer(QObject *parent = 0);
std::vector<testClientp> clientList; //客户端tcp连接
std::vector<QString> ipList; //客户端ip
int totalClient; //客户端数
protected:
void incomingConnection(int socketDescriptor); //虚函数,有tcp请求时会触发
signals:
void error(QTcpSocket::SocketError socketError); //错误信号
void newClientSignal(QString clientIP,int state,int threadNum); //将新客户端信息发给主窗口
void updateProgressSignal(qint64,qint64,int); //更新发送进度信号
void outputlogSignal(QString); //发送日志消息信号
void newClientInfoSignal(QString,int,int); //更新客户端信息
public slots:
void newClientSlot(QString clientIP,int state,int threadNum); //将新客户端信息发给主窗口
void updateProgressSlot(qint64,qint64,int); //更新发送进度槽
void outputlogSlot(QString); //发送日志消息槽
void newClientInfoSlot(QString,int,int); //更新客户端信息
private:
int getClientNum(testClientp socket); //检测用户,若存在,返回下标,若不存在,返回用户数
};
public:需要主窗口访问的变量incomingConnection:接收tcp请求
signals:发送客户端信息的信号
public slots:接收下层testClient信号的槽,并向上层主窗口发送信号
private:检测用户是否存在的辅助函数
(2)类定义:
构造函数:
- testServer::testServer(QObject *parent) :
- QTcpServer(parent)
- {
- totalClient = 0;
- clientList.clear();
- ipList.clear();
- }
testServer::testServer(QObject *parent) :
QTcpServer(parent)
{
totalClient = 0;
clientList.clear();
ipList.clear();
}
接收tcp请求函数:
- void testServer::incomingConnection(int socketDescriptor)
- {
- testClient *socket = new testClient(this);
- if (!socket->setSocketDescriptor(socketDescriptor))
- {
- QMessageBox::warning(NULL,"ERROR",socket->errorString());
- emit error(socket->error());
- return;
- }
- int num = getClientNum(socket); //检测用户,若存在,返回下标,若不存在,返回用户数
- socket->num = num; //记录序号
- emit newClientSignal(socket->peerAddress().toString(),(int)socket->state(),num); //将新客户端信息发给主窗口
- //连接信号和槽
- connect(socket, SIGNAL(newClient(QString,int,int)), this, SLOT(newClientSlot(QString,int,int)));
- connect(socket, SIGNAL(outputlog(QString)), this, SLOT(outputlogSlot(QString)));
- connect(socket, SIGNAL(updateProgressSignal(qint64,qint64,int)),
- this, SLOT(updateProgressSlot(qint64,qint64,int)));
- connect(socket, SIGNAL(newClientInfo(QString,int,int)),
- this, SLOT(newClientInfoSlot(QString,int,int)));
- connect(socket, SIGNAL(readyToTest(int,int)), this, SLOT(readyToTestSlot(int,int)));
- connect(socket, SIGNAL(startEvaluate(int,QString)), this, SLOT(startEvaluateSlot(int,QString)));
- connect(socket, SIGNAL(evaluateComplete(int,QString,int)),
- this, SLOT(evaluateCompleteSlot(int,QString,int)));
- if(num == totalClient)
- totalClient++;
- }
void testServer::incomingConnection(int socketDescriptor)
{
testClient *socket = new testClient(this);
if (!socket->setSocketDescriptor(socketDescriptor))
{
QMessageBox::warning(NULL,"ERROR",socket->errorString());
emit error(socket->error());
return;
}
int num = getClientNum(socket); //检测用户,若存在,返回下标,若不存在,返回用户数
socket->num = num; //记录序号
emit newClientSignal(socket->peerAddress().toString(),(int)socket->state(),num); //将新客户端信息发给主窗口
//连接信号和槽
connect(socket, SIGNAL(newClient(QString,int,int)), this, SLOT(newClientSlot(QString,int,int)));
connect(socket, SIGNAL(outputlog(QString)), this, SLOT(outputlogSlot(QString)));
connect(socket, SIGNAL(updateProgressSignal(qint64,qint64,int)),
this, SLOT(updateProgressSlot(qint64,qint64,int)));
connect(socket, SIGNAL(newClientInfo(QString,int,int)),
this, SLOT(newClientInfoSlot(QString,int,int)));
connect(socket, SIGNAL(readyToTest(int,int)), this, SLOT(readyToTestSlot(int,int)));
connect(socket, SIGNAL(startEvaluate(int,QString)), this, SLOT(startEvaluateSlot(int,QString)));
connect(socket, SIGNAL(evaluateComplete(int,QString,int)),
this, SLOT(evaluateCompleteSlot(int,QString,int)));
if(num == totalClient)
totalClient++;
}
通过setSocketDescriptor获取当前接入的socket描述符检测用户是否存在,记录序号
向主窗口发送信号,连接下层testClient信号和接收槽
更新客户端总数
检测用户是否存在的函数
- int testServer::getClientNum(testClientp socket) //检测用户,若存在,返回下标,若不存在,加入列表
- {
- for(int i = 0; i < ipList.size(); i++)
- {
- qDebug() << "No." << i << "ip: " << ipList[i];
- if(ipList[i] == socket->peerAddress().toString())
- {
- clientList[i] = socket;
- return i;
- }
- }
- clientList.push_back(socket); //存入客户列表
- ipList.push_back(socket->peerAddress().toString()); //存入ip列表
- return totalClient;
- }
int testServer::getClientNum(testClientp socket) //检测用户,若存在,返回下标,若不存在,加入列表
{
for(int i = 0; i < ipList.size(); i++)
{
qDebug() << "No." << i << "ip: " << ipList[i];
if(ipList[i] == socket->peerAddress().toString())
{
clientList[i] = socket;
return i;
}
}
clientList.push_back(socket); //存入客户列表
ipList.push_back(socket->peerAddress().toString()); //存入ip列表
return totalClient;
}
其他发送信号的函数均为连接下层和上层的中转,由于比较简单就不赘述了4、主窗口
在主窗口(继承自QMainWindow)绘制所需控件:
lineEdit_ipaddress:显示服务器IP
lineEdit_port:设置服务器端口
listWidget_sendwav:发送文件列表
tableWidget_clientlist:客户端信息列表,显示所有客户端IP,接入状态,发送/接收进度条
textEdit_status:状态栏
pushButton_addwav:添加发送文件按钮
pushButton_deletewav:删除发送文件按钮
pushButton_startserver:开启/关闭服务器按钮
pushButton_senddata:发送数据按钮
pushButton_deleteclient:删除客户端按钮
主窗口需定义的变量
- testServer *tcpServer; //tcp服务器指针
- bool isServerOn; //服务器是否开启
- std::vector<openFileStruct> openFileList; //存储目录和文件名对
testServer *tcpServer; //tcp服务器指针
bool isServerOn; //服务器是否开启
std::vector<openFileStruct> openFileList; //存储目录和文件名对
开启/关闭服务器按钮
- void testwhynot::on_pushButton_startserver_clicked() //开启服务器
- {
- if(isServerOn == false)
- {
- tcpServer = new testServer(this);
- ui->comboBox_testtype->setEnabled(false); //开启服务器后不能更改测试类型
- ui->pushButton_addwav->setEnabled(false); //不能更改wav列表
- ui->pushButton_deletewav->setEnabled(false);
- //监听本地主机端口,如果出错就输出错误信息,并关闭
- if(!tcpServer->listen(QHostAddress::Any,ui->lineEdit_port->text().toInt()))
- {
- QMessageBox::warning(NULL,"ERROR",tcpServer->errorString());
- return;
- }
- isServerOn = true;
- ui->pushButton_startserver->setText(tr("close server"));
- //显示到状态栏
- ui->textEdit_status->append(tr("%1 start server: %2:%3")
- .arg(getcurrenttime())
- .arg(ipaddress)
- .arg(ui->lineEdit_port->text()));
- //链接错误处理信号和槽
- //connect(this,SIGNAL(error(int,QString)),this,SLOT(displayError(int,QString)));
- //处理多客户端,连接从客户端线程发出的信号和主窗口的槽
- //连接客户端信息更改信号和槽
- connect(tcpServer,SIGNAL(newClientSignal(QString,int,int)),this,SLOT(acceptNewClient(QString,int,int)));
- //连接日志消息信号和槽
- connect(tcpServer,SIGNAL(outputlogSignal(QString)),
- this,SLOT(acceptOutputlog(QString)));
- //连接更新发送进度信号和槽
- connect(tcpServer, SIGNAL(updateProgressSignal(qint64,qint64,int)),
- this, SLOT(acceptUpdateProgress(qint64,qint64,int)));
- //连接更新客户端信息列表信号和槽
- connect(tcpServer, SIGNAL(newClientInfoSignal(QString,int,int)),
- this, SLOT(acceptNewClientInfo(QString,int,int)));
- //显示到状态栏
- ui->textEdit_status->append(tr("%1 wait for client...")
- .arg(getcurrenttime()));
- }
- else
- {
- isServerOn = false;
- ui->pushButton_startserver->setText(tr("start server"));
- //断开所有客户端连接,发出disconnected()信号
- for(int i=0; i<tcpServer->clientList.size(); i++)
- {
- if(ui->tableWidget_clientlist->item(i,2)->text() == clientStatus[3]) //处于连接状态才断开,否则无法访问testClientp指针
- {
- testClientp p = tcpServer->clientList[i];
- p->close();
- }
- }
- //清空列表
- tcpServer->clientList.clear();
- tcpServer->ipList.clear();
- clientInfoList.clear();
- for(int i=0; i<ui->tableWidget_clientlist->rowCount(); )
- ui->tableWidget_clientlist->removeRow(i);
- tcpServer->close(); //关闭服务器
- ui->textEdit_status->append(tr("%1 clost server.").arg(getcurrenttime()));
- ui->comboBox_testtype->setEnabled(true); //可以重新选择测试类型
- ui->pushButton_addwav->setEnabled(true); //能更改wav列表
- ui->pushButton_deletewav->setEnabled(true);
- //按钮无效化
- ui->pushButton_senddata->setEnabled(false);
- ui->pushButton_starttest->setEnabled(false);
- ui->pushButton_deleteclient->setEnabled(false);
- }
- }
void testwhynot::on_pushButton_startserver_clicked() //开启服务器
{
if(isServerOn == false)
{
tcpServer = new testServer(this);
ui->comboBox_testtype->setEnabled(false); //开启服务器后不能更改测试类型
ui->pushButton_addwav->setEnabled(false); //不能更改wav列表
ui->pushButton_deletewav->setEnabled(false);
//监听本地主机端口,如果出错就输出错误信息,并关闭
if(!tcpServer->listen(QHostAddress::Any,ui->lineEdit_port->text().toInt()))
{
QMessageBox::warning(NULL,"ERROR",tcpServer->errorString());
return;
}
isServerOn = true;
ui->pushButton_startserver->setText(tr("close server"));
//显示到状态栏
ui->textEdit_status->append(tr("%1 start server: %2:%3")
.arg(getcurrenttime())
.arg(ipaddress)
.arg(ui->lineEdit_port->text()));
//链接错误处理信号和槽
//connect(this,SIGNAL(error(int,QString)),this,SLOT(displayError(int,QString)));
//处理多客户端,连接从客户端线程发出的信号和主窗口的槽
//连接客户端信息更改信号和槽
connect(tcpServer,SIGNAL(newClientSignal(QString,int,int)),this,SLOT(acceptNewClient(QString,int,int)));
//连接日志消息信号和槽
connect(tcpServer,SIGNAL(outputlogSignal(QString)),
this,SLOT(acceptOutputlog(QString)));
//连接更新发送进度信号和槽
connect(tcpServer, SIGNAL(updateProgressSignal(qint64,qint64,int)),
this, SLOT(acceptUpdateProgress(qint64,qint64,int)));
//连接更新客户端信息列表信号和槽
connect(tcpServer, SIGNAL(newClientInfoSignal(QString,int,int)),
this, SLOT(acceptNewClientInfo(QString,int,int)));
//显示到状态栏
ui->textEdit_status->append(tr("%1 wait for client...")
.arg(getcurrenttime()));
}
else
{
isServerOn = false;
ui->pushButton_startserver->setText(tr("start server"));
//断开所有客户端连接,发出disconnected()信号
for(int i=0; i<tcpServer->clientList.size(); i++)
{
if(ui->tableWidget_clientlist->item(i,2)->text() == clientStatus[3]) //处于连接状态才断开,否则无法访问testClientp指针
{
testClientp p = tcpServer->clientList[i];
p->close();
}
}
//清空列表
tcpServer->clientList.clear();
tcpServer->ipList.clear();
clientInfoList.clear();
for(int i=0; i<ui->tableWidget_clientlist->rowCount(); )
ui->tableWidget_clientlist->removeRow(i);
tcpServer->close(); //关闭服务器
ui->textEdit_status->append(tr("%1 clost server.").arg(getcurrenttime()));
ui->comboBox_testtype->setEnabled(true); //可以重新选择测试类型
ui->pushButton_addwav->setEnabled(true); //能更改wav列表
ui->pushButton_deletewav->setEnabled(true);
//按钮无效化
ui->pushButton_senddata->setEnabled(false);
ui->pushButton_starttest->setEnabled(false);
ui->pushButton_deleteclient->setEnabled(false);
}
}
注意:1、listen(“监听类型”,“端口号”) 用于开启服务器,在指定端口监听客户端连接
2、connect连接了客户端更新信息的信号,来自下层testServer类
发送数据按钮
- void testwhynot::on_pushButton_senddata_clicked() //发送数据
- {
- //多客户端发送数据
- if(ui->comboBox_testtype->currentIndex() == testtype_keyword)
- {
- if(!check_file(keyword_filepath))
- return;
- }
- vector<clientInfo>::iterator it;
- //遍历所有客户端信息,确保都处于“已连接”状态
- for(it=clientInfoList.begin(); it!=clientInfoList.end(); it++)
- {
- if((*it).state != 3) //有客户端未处于“已连接”状态
- {
- QMessageBox::warning(this,tr("WARNING"),tr("client %1 is not connected.").arg((*it).ip));
- return;
- }
- }
- //没有文件
- if(openFileList.size() == 0)
- {
- QMessageBox::warning(NULL,tr("WARNING"),tr("no file! can not send!"));
- return;
- }
- for(int i = 0; i < tcpServer->clientList.size(); i++)
- {
- tcpServer->clientList[i]->prepareTransfer(openFileList,ui->comboBox_testtype->currentIndex());
- }
- //按钮无效化
- ui->pushButton_senddata->setEnabled(false);
- }
void testwhynot::on_pushButton_senddata_clicked() //发送数据
{
//多客户端发送数据
if(ui->comboBox_testtype->currentIndex() == testtype_keyword)
{
if(!check_file(keyword_filepath))
return;
}
vector<clientInfo>::iterator it;
//遍历所有客户端信息,确保都处于“已连接”状态
for(it=clientInfoList.begin(); it!=clientInfoList.end(); it++)
{
if((*it).state != 3) //有客户端未处于“已连接”状态
{
QMessageBox::warning(this,tr("WARNING"),tr("client %1 is not connected.").arg((*it).ip));
return;
}
}
//没有文件
if(openFileList.size() == 0)
{
QMessageBox::warning(NULL,tr("WARNING"),tr("no file! can not send!"));
return;
}
for(int i = 0; i < tcpServer->clientList.size(); i++)
{
tcpServer->clientList[i]->prepareTransfer(openFileList,ui->comboBox_testtype->currentIndex());
}
//按钮无效化
ui->pushButton_senddata->setEnabled(false);
}
调用底层prepareTransfer函数开始传输删除客户端按钮
- void testwhynot::on_pushButton_deleteclient_clicked()
- {
- QList<QTableWidgetItem*> list = ui->tableWidget_clientlist->selectedItems(); //读取所有被选中的item
- if(list.size() == 0) //没有被选中的就返回
- {
- QMessageBox::warning(this,QObject::tr("warning"),QObject::tr("please select a test client"));
- return;
- }
- std::set<int> del_row; //记录要删除的行号,用set防止重复
- for(int i=0; i<list.size(); i++) //删除选中的项
- {
- QTableWidgetItem* sel = list[i]; //指向选中的item的指针
- if (sel)
- {
- int row = ui->tableWidget_clientlist->row(sel); //获取行号
- del_row.insert(row);
- }
- }
- std::vector<int> del_list; //赋值给del_list,set本身为有序
- for(std::set<int>::iterator it=del_row.begin(); it!=del_row.end(); it++)
- {
- del_list.push_back(*it);
- }
- for(int i=del_list.size()-1; i>=0; i--) //逆序遍历
- {
- testClientp p = tcpServer->clientList[del_list[i]];
- p->close();
- ui->tableWidget_clientlist->removeRow(del_list[i]); //从显示列表中删除行
- clientInfoList.erase(clientInfoList.begin() + del_list[i]); //从内部列表删除
- tcpServer->ipList.erase(tcpServer->ipList.begin() + del_list[i]);
- tcpServer->clientList.erase(tcpServer->clientList.begin() + del_list[i]);
- tcpServer->totalClient--;
- }
- for(int i=0; i<tcpServer->clientList.size(); i++)
- {
- tcpServer->clientList[i]->num = i;
- }
- if(clientInfoList.empty()) //没有客户端,删除按钮无效化
- {
- ui->pushButton_deleteclient->setEnabled(false);
- }
- }
void testwhynot::on_pushButton_deleteclient_clicked()
{
QList<QTableWidgetItem*> list = ui->tableWidget_clientlist->selectedItems(); //读取所有被选中的item
if(list.size() == 0) //没有被选中的就返回
{
QMessageBox::warning(this,QObject::tr("warning"),QObject::tr("please select a test client"));
return;
}
std::set<int> del_row; //记录要删除的行号,用set防止重复
for(int i=0; i<list.size(); i++) //删除选中的项
{
QTableWidgetItem* sel = list[i]; //指向选中的item的指针
if (sel)
{
int row = ui->tableWidget_clientlist->row(sel); //获取行号
del_row.insert(row);
}
}
std::vector<int> del_list; //赋值给del_list,set本身为有序
for(std::set<int>::iterator it=del_row.begin(); it!=del_row.end(); it++)
{
del_list.push_back(*it);
}
for(int i=del_list.size()-1; i>=0; i--) //逆序遍历
{
testClientp p = tcpServer->clientList[del_list[i]];
p->close();
ui->tableWidget_clientlist->removeRow(del_list[i]); //从显示列表中删除行
clientInfoList.erase(clientInfoList.begin() + del_list[i]); //从内部列表删除
tcpServer->ipList.erase(tcpServer->ipList.begin() + del_list[i]);
tcpServer->clientList.erase(tcpServer->clientList.begin() + del_list[i]);
tcpServer->totalClient--;
}
for(int i=0; i<tcpServer->clientList.size(); i++)
{
tcpServer->clientList[i]->num = i;
}
if(clientInfoList.empty()) //没有客户端,删除按钮无效化
{
ui->pushButton_deleteclient->setEnabled(false);
}
}
删除客户端较麻烦,由于支持多选删除,需要先将选中的行号排序、去重、从大到小遍历删除(防止删除后剩余行号变化)不仅要关闭客户端,还要将其从显示列表和内部列表删除,保持显示和实际列表同步
上述便是基于tcp传输的发送/接收服务器端的搭建,客户端只需遵从上述的发送协议搭建即可,客户端发送与接收与服务器基本相同,也不赘述了。
本文偏重于工程实现,Qt的TCP传输原理叙述不多,若要深入了解qt套接字编程,请参考:http://cool.worm.blog.163.com/blog/static/64339006200842922851118/
- 顶
- 0
- 踩
- 0
- 上一篇Qt5 更新翻译
- 下一篇poj 2419 Forests
我的同类文章
- •Qt5 更新翻译2013-08-23
- •Qt5 在表格中加入控件2013-07-17
- •Qt5 在控件中绘图2013-05-31
- •Qt5 error LNK2019 无法解析的外部符号 解决办法2013-05-30
- •Qt5 写DOS格式文件2013-07-24
- •Qt5 在win7上发布 & 打包依赖dll生成exe方法2013-06-18
- •Qt5绘制wav波形图2013-05-30
一、实现功能
1、服务器端选择待发送的文件,可以是多个
2、开启服务器,支持多客户端接入,能够实时显示每个客户端接入状态
3、等待所有客户端都处于已连接状态时,依次发送文件集给每个客户端,显示每个客户端发送进度
4、发送完成后等待接收客户端发回的文件,显示接收进度
5、关闭服务器
二、实现要点
先讲一下实现上述功能的几个关键点,明白的这几个要点,功能的大框架就搭好了,细节在下一节再讲
1、新建服务器类testServer,继承自QTcpServer
功能:用于接收客户端TCP请求,存储所有客户端信息,向主窗口发送信息
在这个类中实例化QTcpServer的虚函数:
void incomingConnection(int socketDescriptor); //虚函数,有tcp请求时会触发
参数为描述socket ID的int变量
此函数在QTcpServer检测到外来TCP请求时,会自动调用。
注:若要接收局域网内的TCP请求,连接newConnection信号到自定义槽函数
connect(tcpServer,SIGNAL(newConnection()),this,SLOT(acceptConnection()));
但是这种方法无法处理多客户端
2、新建客户端类testClient,继承自QTcpSocket
功能:存储每个接入的客户端信息,控制发送/接收文件,向testServer发送信息
在testServer的incomingConnection函数中,通过设置socketDescriptor,初始化一个testClient实例
- testClient *socket = new testClient(this);
- if (!socket->setSocketDescriptor(socketDescriptor))
- {
- emit error(socket->error());
- return;
- }
testClient *socket = new testClient(this);
if (!socket->setSocketDescriptor(socketDescriptor))
{
emit error(socket->error());
return;
}
3、发送数据
实例化testClient类,通过void writeData(const char * data, qint64 size)函数将数据写入TCP流
testClient *a;
a->writeData("message",8);
一般来说,使用QByteArray和QDataStream进行格式化的数据写入,设置流化数据格式类型为QDataStream::Qt_5_0,与客户端一致
- testClient *a;
- QByteArray outBlock; //缓存一次发送的数据
- QDataStream sendOut(&outBlock, QIODevice::WriteOnly); //数据流
- sendOut.setVersion(QDataStream::Qt_5_0);
- sendOut << "message";
- a->writeData(outBlock,outBlock.size());
- outBlock.resize(0);
testClient *a;
QByteArray outBlock; //缓存一次发送的数据
QDataStream sendOut(&outBlock, QIODevice::WriteOnly); //数据流
sendOut.setVersion(QDataStream::Qt_5_0);
sendOut << "message";
a->writeData(outBlock,outBlock.size());
outBlock.resize(0);
注意outBlock如果为全局变量,最后需要resize,否则下一次写入时会出错
在testClient构造函数中连接数据成功发送后产生的bytesWritten()信号
connect(this, SIGNAL(bytesWritten(qint64)), this, SLOT(updateClientProgress(qint64)));
参数为成功写入TCP流的字节数
通过捕获这个信号,可以实现数据的连续发送。因为发送文件时,数据是一块一块发送的,比如设定一块的大小为20字节,那么发送完20字节,怎么继续发送呢?就靠的是这个函数,再用全局变量记录下总共发送的字节数,便可以控制发送的结束。
4、发送文件
先把文件读到QFile变量中,再通过QBytesArray发送
- testClient *a;
- QFile sendFile = new QFile(sendFilePath);<span style="WHITE-SPACE: pre"> </span>//读取发送文件路径
- if (!sendFile->open(QFile::ReadOnly )) //读取发送文件
- { return;}
- QByteArray outBlock;
- outBlock = sendFile->read(sendFile.size());
- a->writeData(outBlock,outBlock.size());
testClient *a;
QFile sendFile = new QFile(sendFilePath); //读取发送文件路径
if (!sendFile->open(QFile::ReadOnly )) //读取发送文件
{ return;}
QByteArray outBlock;
outBlock = sendFile->read(sendFile.size());
a->writeData(outBlock,outBlock.size());
5、接收数据
依旧是testClient类,连接有readyRead()信号,readyRead()信号为新连接中有可读数据时发出
connect(this, SIGNAL(readyRead()), this, SLOT(recvData()));
首先将testClient连接实例(如testClient *a)封装到QDataStream变量中,设置流化数据格式类型为QDataStream::Qt_5_0,与客户端一致。
在读入数据前,用a->bytesAvailable()检测数据流中可读入的字节数,然后就可以通过"<<"操作符读入数据了。
- QDataStream in(a); //本地数据流
- in.setVersion(QDataStream::Qt_5_0); //设置流版本,以防数据格式错误
- quint8 receiveClass;
- if(this->bytesAvailable() >= (sizeof(quint8)))
- {
- in >> receiveClass;
- }
QDataStream in(a); //本地数据流
in.setVersion(QDataStream::Qt_5_0); //设置流版本,以防数据格式错误
quint8 receiveClass;
if(this->bytesAvailable() >= (sizeof(quint8)))
{
in >> receiveClass;
}
6、接收文件
使用readAll读入TCP流中所有数据,再写入到QFile变量中
- QByteArray inBlock;
- inBlock = a->readAll(); //读入所有数据
- QFile receivedFile = new QFile(receiveFilePath); //打开接收文件
- if (!receivedFile->open(QFile::WriteOnly ))
- { return;}
- QFile receivedFile->write(inBlock); //写入文件
- inBlock.resize(0);
QByteArray inBlock;
inBlock = a->readAll(); //读入所有数据
QFile receivedFile = new QFile(receiveFilePath); //打开接收文件
if (!receivedFile->open(QFile::WriteOnly ))
{ return;}
QFile receivedFile->write(inBlock); //写入文件
inBlock.resize(0);
三、具体实现
1、全局定义
需要定义一些全局变量和常量,定义如下
- //存储文件路径和文件名的结构体
- struct openFileStruct
- {
- QString filePath;
- QString fileName;
- };
- struct clientInfo //客户端信息
- {
- QString ip; //ip
- int state; //状态
- QString id; //id
- };
- const quint8 sendtype_file = 0; //发送类型是文件
- const quint8 sendtype_start_test = 10; //发送类型是开始测试
- const quint8 sendtype_msg = 20; //发送类型是消息
- const quint16 filetype_list = 0; //文件类型为列表
- const quint16 filetype_wavfile = 1; //文件类型为wav文件
- const QString clientStatus[7] = //客户端状态
- {QObject::tr("Unconnected"),QObject::tr("HostLookup"),QObject::tr("Connecting"),
- QObject::tr("Connected"),QObject::tr("Bound"),QObject::tr("Listening"),QObject::tr("Closing")};
//存储文件路径和文件名的结构体
struct openFileStruct
{
QString filePath;
QString fileName;
};
struct clientInfo //客户端信息
{
QString ip; //ip
int state; //状态
QString id; //id
};
const quint8 sendtype_file = 0; //发送类型是文件
const quint8 sendtype_start_test = 10; //发送类型是开始测试
const quint8 sendtype_msg = 20; //发送类型是消息
const quint16 filetype_list = 0; //文件类型为列表
const quint16 filetype_wavfile = 1; //文件类型为wav文件
const QString clientStatus[7] = //客户端状态
{QObject::tr("Unconnected"),QObject::tr("HostLookup"),QObject::tr("Connecting"),
QObject::tr("Connected"),QObject::tr("Bound"),QObject::tr("Listening"),QObject::tr("Closing")};
openFileStruct 用于主窗口,存储发送文件的路径和文件名clientInfo 用于主窗口,存储每个客户端的信息
规定传输协议为先发送一个quint8的标志位,规定传输类型,即为sendtype定义的部分
sendtype为0时,发送文件。filetype为文件类型
发送文件协议为:发送类型 | 数据总大小 | 文件类型 | 文件名大小 | 文件名 | 文件内容
发送文件时,先发送一个文件列表,每行一个文件名,再逐个发送文件
clientStatus为客户端状态,与Qt内部的socket status定义相同
2、testClient类
一个testClient类实例为一个socket客户端描述
(1)类声明:
- class testClient : public QTcpSocket
- {
- Q_OBJECT
- public:
- testClient(QObject *parent = 0);
- int num; //客户端序号
- void prepareTransfer(std::vector<openFileStruct>& openFileList,int testtype_t); //准备传输文件
- signals:
- void newClient(QString, int, int); //客户端信息变更信号
- void outputlog(QString); //输出日志信息
- void updateProgressSignal(qint64,qint64,int); //更新发送进度信号
- void newClientInfo(QString,int,int); //更新客户端信息
- private slots:
- void recvData(); //接收数据
- void clientConnected(); //已连接
- void clientDisconnected(); //断开连接
- void newState(QAbstractSocket::SocketState); //新状态
- void startTransfer(const quint16); //开始传输文件
- void updateClientProgress(qint64 numBytes); //发送文件内容
- void getSendFileList(QString path); //在指定路径生成发送文件列表
- quint64 getSendTotalSize(); //获取sendFilePath对应文件加上头的大小
- private:
- //发送文件所需变量
- qint64 loadSize; //每次接收的数据块大小
- qint64 TotalSendBytes; //总共需发送的字节数
- qint64 bytesWritten; //已发送字节数
- qint64 bytesToWrite; //待发送字节数
- QString sendFileName; //待发送的文件的文件名
- QString sendFilePath; //待发送的文件的文件路径
- QFile *sendFile; //待发送的文件
- QByteArray outBlock; //缓存一次发送的数据
- int sendNum; //记录当前发送到第几个文件
- int sendFileNum; //记录发送文件个数
- int totalSendSize; //记录发送文件总大小
- quint64 proBarMax; //发送进度条最大值
- quint64 proBarValue; //进度条当前值
- std::vector<openFileStruct> openList; //发送文件列表
- //接收文件用变量
- quint8 receiveClass; //0:文件,1:开始测试,20:客户端接收到文件反馈
- quint16 fileClass; //待接收文件类型
- qint64 TotalRecvBytes; //总共需接收的字节数
- qint64 bytesReceived; //已接收字节数
- qint64 fileNameSize; //待接收文件名字节数
- QString receivedFileName; //待接收文件的文件名
- QFile *receivedFile; //待接收文件
- QByteArray inBlock; //接收临时存储块
- qint64 totalBytesReceived; //接收的总大小
- qint32 recvNum; //现在接收的是第几个
- quint64 recvFileNum; //接收文件个数
- quint64 totalRecvSize; //接收文件总大小
- bool isReceived[3]; //是否收到客户端的接收文件反馈
- };
class testClient : public QTcpSocket
{
Q_OBJECT
public:
testClient(QObject *parent = 0);
int num; //客户端序号
void prepareTransfer(std::vector<openFileStruct>& openFileList,int testtype_t); //准备传输文件
signals:
void newClient(QString, int, int); //客户端信息变更信号
void outputlog(QString); //输出日志信息
void updateProgressSignal(qint64,qint64,int); //更新发送进度信号
void newClientInfo(QString,int,int); //更新客户端信息
private slots:
void recvData(); //接收数据
void clientConnected(); //已连接
void clientDisconnected(); //断开连接
void newState(QAbstractSocket::SocketState); //新状态
void startTransfer(const quint16); //开始传输文件
void updateClientProgress(qint64 numBytes); //发送文件内容
void getSendFileList(QString path); //在指定路径生成发送文件列表
quint64 getSendTotalSize(); //获取sendFilePath对应文件加上头的大小
private:
//发送文件所需变量
qint64 loadSize; //每次接收的数据块大小
qint64 TotalSendBytes; //总共需发送的字节数
qint64 bytesWritten; //已发送字节数
qint64 bytesToWrite; //待发送字节数
QString sendFileName; //待发送的文件的文件名
QString sendFilePath; //待发送的文件的文件路径
QFile *sendFile; //待发送的文件
QByteArray outBlock; //缓存一次发送的数据
int sendNum; //记录当前发送到第几个文件
int sendFileNum; //记录发送文件个数
int totalSendSize; //记录发送文件总大小
quint64 proBarMax; //发送进度条最大值
quint64 proBarValue; //进度条当前值
std::vector<openFileStruct> openList; //发送文件列表
//接收文件用变量
quint8 receiveClass; //0:文件,1:开始测试,20:客户端接收到文件反馈
quint16 fileClass; //待接收文件类型
qint64 TotalRecvBytes; //总共需接收的字节数
qint64 bytesReceived; //已接收字节数
qint64 fileNameSize; //待接收文件名字节数
QString receivedFileName; //待接收文件的文件名
QFile *receivedFile; //待接收文件
QByteArray inBlock; //接收临时存储块
qint64 totalBytesReceived; //接收的总大小
qint32 recvNum; //现在接收的是第几个
quint64 recvFileNum; //接收文件个数
quint64 totalRecvSize; //接收文件总大小
bool isReceived[3]; //是否收到客户端的接收文件反馈
};
public为需要上层访问的公有变量和函数
signals为向上层发送的信号,保证主窗口实时显示信息
private slots包括底层实现发送、接收消息的函数,以及信号处理的槽
private为私有变量,包括发送文件和接收文件所需的变量
(2)类定义:
构造函数:
- testClient::testClient(QObject *parent) :
- QTcpSocket(parent)
- {
- //发送变量初始化
- num = -1;
- loadSize = 100*1024;
- openList.clear();
- //接收变量初始化
- TotalRecvBytes = 0;
- bytesReceived = 0;
- fileNameSize = 0;
- recvFileNum = 0;
- totalRecvSize = 0;
- totalBytesReceived = 0;
- recvNum = 0;
- receiveClass = 255;
- fileClass = 0;
- for(int i=0; i<3; i++)
- isReceived[i] = false;
- //连接信号和槽
- connect(this, SIGNAL(readyRead()), this, SLOT(recvData()));
- connect(this, SIGNAL(disconnected()), this, SLOT(clientDisconnected()));
- connect(this, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
- this, SLOT(newState(QAbstractSocket::SocketState)));
- connect(this, SIGNAL(bytesWritten(qint64)), this, SLOT(updateClientProgress(qint64)));
- }
testClient::testClient(QObject *parent) :
QTcpSocket(parent)
{
//发送变量初始化
num = -1;
loadSize = 100*1024;
openList.clear();
//接收变量初始化
TotalRecvBytes = 0;
bytesReceived = 0;
fileNameSize = 0;
recvFileNum = 0;
totalRecvSize = 0;
totalBytesReceived = 0;
recvNum = 0;
receiveClass = 255;
fileClass = 0;
for(int i=0; i<3; i++)
isReceived[i] = false;
//连接信号和槽
connect(this, SIGNAL(readyRead()), this, SLOT(recvData()));
connect(this, SIGNAL(disconnected()), this, SLOT(clientDisconnected()));
connect(this, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
this, SLOT(newState(QAbstractSocket::SocketState)));
connect(this, SIGNAL(bytesWritten(qint64)), this, SLOT(updateClientProgress(qint64)));
}
初始化变量
连接qt的tcp信号和自定义槽,包括:
readyRead() 接收消息信号
disconnected() 断开连接信号
stateChanged(QAbstractSocket::SocketState) 连接状态变更信号
bytesWritten(qint64) 已写入发送消息流信号
接收数据槽,设定服务器只接收一个文件:
- void testClient::recvData() //接收数据,服务器只接收客户端的一个结果文件
- {
- QDataStream in(this); //本地数据流
- in.setVersion(QDataStream::Qt_5_0); //设置流版本,以防数据格式错误
- QString unit;
- qint32 msg;
- if(bytesReceived <= (sizeof(quint8)))
- {
- if(this->bytesAvailable() >= (sizeof(quint8)))
- {
- in >> receiveClass;
- }
- switch(receiveClass)
- {
- case sendtype_file: //接收文件
- bytesReceived += sizeof(quint8);
- qDebug() << "bytesReceived: " << bytesReceived;
- break;
- case sendtype_msg:
- in >> msg;
- if(msg == 0) //接收文件列表
- {
- emit outputlog(tr("client %1 have received list file")
- .arg(this->peerAddress().toString()));
- }
- else if(msg == 1) //接收文件
- {
- emit outputlog(tr("client %1 have received file(s)")
- .arg(this->peerAddress().toString()));
- }
- return;
- default:
- return;
- }
- }
- if(bytesReceived >= (sizeof(quint8)) && bytesReceived <= (sizeof(quint8) + sizeof(qint64)*2 + sizeof(quint16))) //开始接收文件,先接受报头
- {
- //收3个int型数据,分别存储总长度、文件类型和文件名长度
- if( ( this->bytesAvailable() >= (sizeof(qint64)*2 + sizeof(quint16)) ) && (fileNameSize == 0) )
- {
- in >> TotalRecvBytes >> fileClass >> fileNameSize;
- bytesReceived += sizeof(qint64)*2; //收到多少字节
- bytesReceived += sizeof(quint16);
- if(fileClass == filetype_result)
- {
- recvNum = 1;
- recvFileNum = 1;
- totalRecvSize = TotalRecvBytes; //只有一个文件,文件总大小为该文件发送大小
- totalBytesReceived += sizeof(qint64)*2;
- totalBytesReceived += sizeof(quint16);
- totalBytesReceived += sizeof(quint8);
- emit newClientInfo(tr("receiving result"),num,4);
- }
- else
- {
- QMessageBox::warning(NULL,tr("WARNING"),tr("client %1 send wrong type of file to server")
- .arg(this->peerAddress().toString()));
- return;
- }
- }
- //接着收文件名并建立文件
- if((this->bytesAvailable() >= fileNameSize)&&(fileNameSize != 0))
- {
- in >> receivedFileName;
- bytesReceived += fileNameSize;
- totalBytesReceived += fileNameSize;
- QString receiveFilePath = receive_path + "/" + receivedFileName; //接收文件路径
- emit outputlog(tr("receive from client %1\nsave as:%2")
- .arg(this->peerAddress().toString())
- .arg(receiveFilePath));
- //建立文件
- receivedFile = new QFile(receiveFilePath);
- if (!receivedFile->open(QFile::WriteOnly ))
- {
- QMessageBox::warning(NULL, tr("WARNING"),
- tr("cannot open file %1:\n%2.").arg(receivedFileName).arg(receivedFile->errorString()));
- return;
- }
- }
- else
- {
- return;
- }
- }
- //一个文件没有接受完
- if (bytesReceived < TotalRecvBytes)
- {
- //可用内容比整个文件长度-已接收长度短,则全部接收并写入文件
- qint64 tmp_Abailable = this->bytesAvailable();
- if(tmp_Abailable <= (TotalRecvBytes - bytesReceived))
- {
- bytesReceived += tmp_Abailable;
- totalBytesReceived += tmp_Abailable;
- inBlock = this->readAll();
- receivedFile->write(inBlock);
- inBlock.resize(0);
- tmp_Abailable = 0;
- }
- //可用内容比整个文件长度-已接收长度长,则接收所需内容,并写入文件
- else
- {
- inBlock = this->read(TotalRecvBytes - bytesReceived);
- if(fileClass == filetype_wavfile)
- {
- totalBytesReceived += (TotalRecvBytes - bytesReceived);
- }
- bytesReceived = TotalRecvBytes;
- receivedFile->write(inBlock);
- inBlock.resize(0);
- tmp_Abailable = 0;
- }
- }
- emit updateProgressSignal(totalBytesReceived,totalRecvSize,num); //更新发送进度信号
- //善后:一个文件全部收完则重置变量关闭文件流,删除指针
- if (bytesReceived == TotalRecvBytes)
- {
- //变量重置
- TotalRecvBytes = 0;
- bytesReceived = 0;
- fileNameSize = 0;
- receiveClass = 255;
- receivedFile->close();
- delete receivedFile;
- //输出信息
- emit outputlog(tr("Have received file: %1 from client %2")
- .arg(receivedFileName)
- .arg(this->peerAddress().toString())); //log information
- //全部文件接收完成
- if(recvNum == recvFileNum)
- {
- //变量重置
- recvFileNum = 0;
- recvNum = 0;
- totalBytesReceived = 0;
- totalRecvSize = 0;
- emit outputlog(tr("Receive all done!"));
- }
- if(fileClass == filetype_result)
- {
- emit newClientInfo(tr("Evaluating"),num,4);
- }
- }
- }
void testClient::recvData() //接收数据,服务器只接收客户端的一个结果文件
{
QDataStream in(this); //本地数据流
in.setVersion(QDataStream::Qt_5_0); //设置流版本,以防数据格式错误
QString unit;
qint32 msg;
if(bytesReceived <= (sizeof(quint8)))
{
if(this->bytesAvailable() >= (sizeof(quint8)))
{
in >> receiveClass;
}
switch(receiveClass)
{
case sendtype_file: //接收文件
bytesReceived += sizeof(quint8);
qDebug() << "bytesReceived: " << bytesReceived;
break;
case sendtype_msg:
in >> msg;
if(msg == 0) //接收文件列表
{
emit outputlog(tr("client %1 have received list file")
.arg(this->peerAddress().toString()));
}
else if(msg == 1) //接收文件
{
emit outputlog(tr("client %1 have received file(s)")
.arg(this->peerAddress().toString()));
}
return;
default:
return;
}
}
if(bytesReceived >= (sizeof(quint8)) && bytesReceived <= (sizeof(quint8) + sizeof(qint64)*2 + sizeof(quint16))) //开始接收文件,先接受报头
{
//收3个int型数据,分别存储总长度、文件类型和文件名长度
if( ( this->bytesAvailable() >= (sizeof(qint64)*2 + sizeof(quint16)) ) && (fileNameSize == 0) )
{
in >> TotalRecvBytes >> fileClass >> fileNameSize;
bytesReceived += sizeof(qint64)*2; //收到多少字节
bytesReceived += sizeof(quint16);
if(fileClass == filetype_result)
{
recvNum = 1;
recvFileNum = 1;
totalRecvSize = TotalRecvBytes; //只有一个文件,文件总大小为该文件发送大小
totalBytesReceived += sizeof(qint64)*2;
totalBytesReceived += sizeof(quint16);
totalBytesReceived += sizeof(quint8);
emit newClientInfo(tr("receiving result"),num,4);
}
else
{
QMessageBox::warning(NULL,tr("WARNING"),tr("client %1 send wrong type of file to server")
.arg(this->peerAddress().toString()));
return;
}
}
//接着收文件名并建立文件
if((this->bytesAvailable() >= fileNameSize)&&(fileNameSize != 0))
{
in >> receivedFileName;
bytesReceived += fileNameSize;
totalBytesReceived += fileNameSize;
QString receiveFilePath = receive_path + "/" + receivedFileName; //接收文件路径
emit outputlog(tr("receive from client %1\nsave as:%2")
.arg(this->peerAddress().toString())
.arg(receiveFilePath));
//建立文件
receivedFile = new QFile(receiveFilePath);
if (!receivedFile->open(QFile::WriteOnly ))
{
QMessageBox::warning(NULL, tr("WARNING"),
tr("cannot open file %1:\n%2.").arg(receivedFileName).arg(receivedFile->errorString()));
return;
}
}
else
{
return;
}
}
//一个文件没有接受完
if (bytesReceived < TotalRecvBytes)
{
//可用内容比整个文件长度-已接收长度短,则全部接收并写入文件
qint64 tmp_Abailable = this->bytesAvailable();
if(tmp_Abailable <= (TotalRecvBytes - bytesReceived))
{
bytesReceived += tmp_Abailable;
totalBytesReceived += tmp_Abailable;
inBlock = this->readAll();
receivedFile->write(inBlock);
inBlock.resize(0);
tmp_Abailable = 0;
}
//可用内容比整个文件长度-已接收长度长,则接收所需内容,并写入文件
else
{
inBlock = this->read(TotalRecvBytes - bytesReceived);
if(fileClass == filetype_wavfile)
{
totalBytesReceived += (TotalRecvBytes - bytesReceived);
}
bytesReceived = TotalRecvBytes;
receivedFile->write(inBlock);
inBlock.resize(0);
tmp_Abailable = 0;
}
}
emit updateProgressSignal(totalBytesReceived,totalRecvSize,num); //更新发送进度信号
//善后:一个文件全部收完则重置变量关闭文件流,删除指针
if (bytesReceived == TotalRecvBytes)
{
//变量重置
TotalRecvBytes = 0;
bytesReceived = 0;
fileNameSize = 0;
receiveClass = 255;
receivedFile->close();
delete receivedFile;
//输出信息
emit outputlog(tr("Have received file: %1 from client %2")
.arg(receivedFileName)
.arg(this->peerAddress().toString())); //log information
//全部文件接收完成
if(recvNum == recvFileNum)
{
//变量重置
recvFileNum = 0;
recvNum = 0;
totalBytesReceived = 0;
totalRecvSize = 0;
emit outputlog(tr("Receive all done!"));
}
if(fileClass == filetype_result)
{
emit newClientInfo(tr("Evaluating"),num,4);
}
}
}
接收文件时,需要一步一步判断接收字节是否大于协议的下一项,若大于则再判断其值接收的文件类型必须是filetype_result
未接收完记录接收进度
接收完文件进行善后,关闭文件流删除指针等
每进行完一步,向上层发送信号,包括客户端信息和接收进度
更新客户端状态函数,向上层发送信号
- void testClient::newState(QAbstractSocket::SocketState state) //新状态
- {
- emit newClient(this->peerAddress().toString(), (int)state, num);
- }
void testClient::newState(QAbstractSocket::SocketState state) //新状态
{
emit newClient(this->peerAddress().toString(), (int)state, num);
}
发送的信号参数为:该客户端IP,状态序号,客户端编号
开始传输文件函数(发送包含文件信息的文件头)
- void testClient::startTransfer(const quint16 type) //开始传输文件
- {
- TotalSendBytes = 0; //总共需发送的字节数
- bytesWritten = 0; //已发送字节数
- bytesToWrite = 0; //待发送字节数
- //开始传输文件信号
- emit outputlog(tr("start sending file to client: %1\n filename: %2")
- .arg(this->peerAddress().toString())
- .arg(sendFileName));
- sendFile = new QFile(sendFilePath);
- if (!sendFile->open(QFile::ReadOnly )) //读取发送文件
- {
- QMessageBox::warning(NULL, tr("WARNING"),
- tr("can not read file %1:\n%2.")
- .arg(sendFilePath)
- .arg(sendFile->errorString()));
- return;
- }
- TotalSendBytes = sendFile->size();
- QDataStream sendOut(&outBlock, QIODevice::WriteOnly);
- sendOut.setVersion(QDataStream::Qt_5_0);
- //写入发送类型,数据大小,文件类型,文件名大小,文件名
- sendOut << quint8(0) << qint64(0) << quint16(0) << qint64(0) << sendFileName;
- TotalSendBytes += outBlock.size();
- sendOut.device()->seek(0);
- sendOut << quint8(sendtype_file)<< TotalSendBytes << quint16(type)
- << qint64((outBlock.size() - sizeof(qint64) * 2) - sizeof(quint16) - sizeof(quint8));
- this->writeData(outBlock,outBlock.size());
- //this->flush();
- bytesToWrite = TotalSendBytes - outBlock.size();
- outBlock.resize(0);
- }
void testClient::startTransfer(const quint16 type) //开始传输文件
{
TotalSendBytes = 0; //总共需发送的字节数
bytesWritten = 0; //已发送字节数
bytesToWrite = 0; //待发送字节数
//开始传输文件信号
emit outputlog(tr("start sending file to client: %1\n filename: %2")
.arg(this->peerAddress().toString())
.arg(sendFileName));
sendFile = new QFile(sendFilePath);
if (!sendFile->open(QFile::ReadOnly )) //读取发送文件
{
QMessageBox::warning(NULL, tr("WARNING"),
tr("can not read file %1:\n%2.")
.arg(sendFilePath)
.arg(sendFile->errorString()));
return;
}
TotalSendBytes = sendFile->size();
QDataStream sendOut(&outBlock, QIODevice::WriteOnly);
sendOut.setVersion(QDataStream::Qt_5_0);
//写入发送类型,数据大小,文件类型,文件名大小,文件名
sendOut << quint8(0) << qint64(0) << quint16(0) << qint64(0) << sendFileName;
TotalSendBytes += outBlock.size();
sendOut.device()->seek(0);
sendOut << quint8(sendtype_file)<< TotalSendBytes << quint16(type)
<< qint64((outBlock.size() - sizeof(qint64) * 2) - sizeof(quint16) - sizeof(quint8));
this->writeData(outBlock,outBlock.size());
//this->flush();
bytesToWrite = TotalSendBytes - outBlock.size();
outBlock.resize(0);
}
读取发送文件建立发送文件头
用writeData将文件头写入TCP发送流,记录已发送字节数
发送文件内容:
- void testClient::updateClientProgress(qint64 numBytes) //发送文件内容
- {
- if(TotalSendBytes == 0)
- return;
- bytesWritten += (int)numBytes;
- proBarValue += (int)numBytes;
- emit updateProgressSignal(proBarValue,proBarMax,num); //更新发送进度信号
- if (bytesToWrite > 0)
- {
- outBlock = sendFile->read(qMin(bytesToWrite, loadSize));
- bytesToWrite -= (int)this->writeData(outBlock,outBlock.size());
- outBlock.resize(0);
- }
- else
- {
- sendFile->close();
- //结束传输文件信号
- if(TotalSendBytes < 1024)
- {
- emit outputlog(tr("finish sending file to client: %1\n filename: %2 %3B")
- .arg(this->peerAddress().toString())
- .arg(sendFileName)
- .arg(TotalSendBytes));
- }
- else if(TotalSendBytes < 1024*1024)
- {
- emit outputlog(tr("finish sending file to client: %1\n filename: %2 %3KB")
- .arg(this->peerAddress().toString())
- .arg(sendFileName)
- .arg(TotalSendBytes / 1024.0));
- }
- else
- {
- emit outputlog(tr("finish sending file to client: %1\n filename: %2 %3MB")
- .arg(this->peerAddress().toString())
- .arg(sendFileName)
- .arg(TotalSendBytes / (1024.0*1024.0)));
- }
- if(sendNum < openList.size()) //还有文件需要发送
- {
- if(sendNum == 0)
- {
- //QFile::remove(sendFilePath); //删除列表文件
- proBarMax = totalSendSize;
- proBarValue = 0;
- }
- sendFilePath = openList[sendNum].filePath;
- sendFileName = openList[sendNum].fileName;
- sendNum++;
- startTransfer(filetype_wavfile);
- }
- else //发送结束
- {
- emit newClientInfo(tr("send complete"),num,4);
- TotalSendBytes = 0; //总共需发送的字节数
- bytesWritten = 0; //已发送字节数
- bytesToWrite = 0; //待发送字节数
- }
- }
- }
void testClient::updateClientProgress(qint64 numBytes) //发送文件内容
{
if(TotalSendBytes == 0)
return;
bytesWritten += (int)numBytes;
proBarValue += (int)numBytes;
emit updateProgressSignal(proBarValue,proBarMax,num); //更新发送进度信号
if (bytesToWrite > 0)
{
outBlock = sendFile->read(qMin(bytesToWrite, loadSize));
bytesToWrite -= (int)this->writeData(outBlock,outBlock.size());
outBlock.resize(0);
}
else
{
sendFile->close();
//结束传输文件信号
if(TotalSendBytes < 1024)
{
emit outputlog(tr("finish sending file to client: %1\n filename: %2 %3B")
.arg(this->peerAddress().toString())
.arg(sendFileName)
.arg(TotalSendBytes));
}
else if(TotalSendBytes < 1024*1024)
{
emit outputlog(tr("finish sending file to client: %1\n filename: %2 %3KB")
.arg(this->peerAddress().toString())
.arg(sendFileName)
.arg(TotalSendBytes / 1024.0));
}
else
{
emit outputlog(tr("finish sending file to client: %1\n filename: %2 %3MB")
.arg(this->peerAddress().toString())
.arg(sendFileName)
.arg(TotalSendBytes / (1024.0*1024.0)));
}
if(sendNum < openList.size()) //还有文件需要发送
{
if(sendNum == 0)
{
//QFile::remove(sendFilePath); //删除列表文件
proBarMax = totalSendSize;
proBarValue = 0;
}
sendFilePath = openList[sendNum].filePath;
sendFileName = openList[sendNum].fileName;
sendNum++;
startTransfer(filetype_wavfile);
}
else //发送结束
{
emit newClientInfo(tr("send complete"),num,4);
TotalSendBytes = 0; //总共需发送的字节数
bytesWritten = 0; //已发送字节数
bytesToWrite = 0; //待发送字节数
}
}
}
文件未发送完:记录发送字节数,writeData继续发送,writeData一旦写入发送流,自动又进入updateClientProgress函数文件已发送完:发出信号,检测是否还有文件需要发送,若有则调用startTransfer继续发送,若没有则发出信号,更新客户端信息
准备传输文件函数,被上层调用,参数为发送文件列表:
- void testClient::prepareTransfer(std::vector<openFileStruct>& openFileList) //准备传输文件
- {
- if(openFileList.size() == 0) //没有文件
- {
- return;
- }
- testtype_now = testtype_t;
- isSendKeyword = false;
- for(int i=0; i<2; i++)
- isReceived[i] = false;
- openList.clear();
- openList.assign(openFileList.begin(),openFileList.end()); //拷贝文件列表
- QString sendFileListName = "sendFileList.txt";
- QString sendFileListPath = temp_Path + "/" + sendFileListName;
- getSendFileList(sendFileListPath); //在指定路径生成发送文件列表
- emit newClientInfo(tr("sending test files"),num,4); //更新主窗口测试阶段
- sendFilePath = sendFileListPath;
- sendFileName = sendFileListName;
- sendNum = 0; //发送到第几个文件
- proBarMax = getSendTotalSize();
- proBarValue = 0;
- startTransfer(filetype_list); //开始传输文件
- }
void testClient::prepareTransfer(std::vector<openFileStruct>& openFileList) //准备传输文件
{
if(openFileList.size() == 0) //没有文件
{
return;
}
testtype_now = testtype_t;
isSendKeyword = false;
for(int i=0; i<2; i++)
isReceived[i] = false;
openList.clear();
openList.assign(openFileList.begin(),openFileList.end()); //拷贝文件列表
QString sendFileListName = "sendFileList.txt";
QString sendFileListPath = temp_Path + "/" + sendFileListName;
getSendFileList(sendFileListPath); //在指定路径生成发送文件列表
emit newClientInfo(tr("sending test files"),num,4); //更新主窗口测试阶段
sendFilePath = sendFileListPath;
sendFileName = sendFileListName;
sendNum = 0; //发送到第几个文件
proBarMax = getSendTotalSize();
proBarValue = 0;
startTransfer(filetype_list); //开始传输文件
}
拷贝文件列表生成发送文件列表文件
更新主窗口信息
开始传输列表文件
上面调用的生成列表文件函数如下:
- void testClient::getSendFileList(QString path) //在指定路径生成发送文件列表
- {
- sendFileNum = openList.size(); //记录发送文件个数
- totalSendSize = 0; //记录发送文件总大小
- for(int i = 0; i < sendFileNum; i++)
- {
- sendFileName = openList[i].fileName;
- sendFilePath = openList[i].filePath;
- totalSendSize += getSendTotalSize();
- }
- FILE *fp;
- fp = fopen(path.toLocal8Bit().data(),"w");
- fprintf(fp,"%d\n",sendFileNum);
- fprintf(fp,"%d\n",totalSendSize);
- for(int i = 0; i < sendFileNum; i++)
- {
- fprintf(fp,"%s\n",openList[i].fileName.toLocal8Bit().data());
- }
- fclose(fp);
- }
void testClient::getSendFileList(QString path) //在指定路径生成发送文件列表
{
sendFileNum = openList.size(); //记录发送文件个数
totalSendSize = 0; //记录发送文件总大小
for(int i = 0; i < sendFileNum; i++)
{
sendFileName = openList[i].fileName;
sendFilePath = openList[i].filePath;
totalSendSize += getSendTotalSize();
}
FILE *fp;
fp = fopen(path.toLocal8Bit().data(),"w");
fprintf(fp,"%d\n",sendFileNum);
fprintf(fp,"%d\n",totalSendSize);
for(int i = 0; i < sendFileNum; i++)
{
fprintf(fp,"%s\n",openList[i].fileName.toLocal8Bit().data());
}
fclose(fp);
}
被上面调用getSendTotalSize函数如下:
- quint64 testClient::getSendTotalSize() //获取sendFilePath对应文件加上头的大小
- {
- int totalsize;
- //计算列表文件及文件头总大小
- QFile *file = new QFile(sendFilePath);
- if (!file->open(QFile::ReadOnly )) //读取发送文件
- {
- QMessageBox::warning(NULL, tr("WARNING"),
- tr("can not read file %1:\n%2.")
- .arg(sendFilePath)
- .arg(file->errorString()));
- return 0;
- }
- totalsize = file->size(); //文件内容大小
- QDataStream sendOut(&outBlock, QIODevice::WriteOnly);
- sendOut.setVersion(QDataStream::Qt_5_0);
- //写入发送类型,数据大小,文件类型,文件名大小,文件名
- sendOut << quint8(0) << qint64(0) << quint16(0) << qint64(0) << sendFileName;
- totalsize += outBlock.size(); //文件头大小
- file->close();
- outBlock.resize(0);
- return totalsize;
- }
quint64 testClient::getSendTotalSize() //获取sendFilePath对应文件加上头的大小
{
int totalsize;
//计算列表文件及文件头总大小
QFile *file = new QFile(sendFilePath);
if (!file->open(QFile::ReadOnly )) //读取发送文件
{
QMessageBox::warning(NULL, tr("WARNING"),
tr("can not read file %1:\n%2.")
.arg(sendFilePath)
.arg(file->errorString()));
return 0;
}
totalsize = file->size(); //文件内容大小
QDataStream sendOut(&outBlock, QIODevice::WriteOnly);
sendOut.setVersion(QDataStream::Qt_5_0);
//写入发送类型,数据大小,文件类型,文件名大小,文件名
sendOut << quint8(0) << qint64(0) << quint16(0) << qint64(0) << sendFileName;
totalsize += outBlock.size(); //文件头大小
file->close();
outBlock.resize(0);
return totalsize;
}
3、testServer类
一个testClient类实例为一个socket服务器端描述
(1)类声明:
- class testServer : public QTcpServer
- {
- Q_OBJECT
- public:
- testServer(QObject *parent = 0);
- std::vector<testClientp> clientList; //客户端tcp连接
- std::vector<QString> ipList; //客户端ip
- int totalClient; //客户端数
- protected:
- void incomingConnection(int socketDescriptor); //虚函数,有tcp请求时会触发
- signals:
- void error(QTcpSocket::SocketError socketError); //错误信号
- void newClientSignal(QString clientIP,int state,int threadNum); //将新客户端信息发给主窗口
- void updateProgressSignal(qint64,qint64,int); //更新发送进度信号
- void outputlogSignal(QString); //发送日志消息信号
- void newClientInfoSignal(QString,int,int); //更新客户端信息
- public slots:
- void newClientSlot(QString clientIP,int state,int threadNum); //将新客户端信息发给主窗口
- void updateProgressSlot(qint64,qint64,int); //更新发送进度槽
- void outputlogSlot(QString); //发送日志消息槽
- void newClientInfoSlot(QString,int,int); //更新客户端信息
- private:
- int getClientNum(testClientp socket); //检测用户,若存在,返回下标,若不存在,返回用户数
- };
class testServer : public QTcpServer
{
Q_OBJECT
public:
testServer(QObject *parent = 0);
std::vector<testClientp> clientList; //客户端tcp连接
std::vector<QString> ipList; //客户端ip
int totalClient; //客户端数
protected:
void incomingConnection(int socketDescriptor); //虚函数,有tcp请求时会触发
signals:
void error(QTcpSocket::SocketError socketError); //错误信号
void newClientSignal(QString clientIP,int state,int threadNum); //将新客户端信息发给主窗口
void updateProgressSignal(qint64,qint64,int); //更新发送进度信号
void outputlogSignal(QString); //发送日志消息信号
void newClientInfoSignal(QString,int,int); //更新客户端信息
public slots:
void newClientSlot(QString clientIP,int state,int threadNum); //将新客户端信息发给主窗口
void updateProgressSlot(qint64,qint64,int); //更新发送进度槽
void outputlogSlot(QString); //发送日志消息槽
void newClientInfoSlot(QString,int,int); //更新客户端信息
private:
int getClientNum(testClientp socket); //检测用户,若存在,返回下标,若不存在,返回用户数
};
public:需要主窗口访问的变量incomingConnection:接收tcp请求
signals:发送客户端信息的信号
public slots:接收下层testClient信号的槽,并向上层主窗口发送信号
private:检测用户是否存在的辅助函数
(2)类定义:
构造函数:
- testServer::testServer(QObject *parent) :
- QTcpServer(parent)
- {
- totalClient = 0;
- clientList.clear();
- ipList.clear();
- }
testServer::testServer(QObject *parent) :
QTcpServer(parent)
{
totalClient = 0;
clientList.clear();
ipList.clear();
}
接收tcp请求函数:
- void testServer::incomingConnection(int socketDescriptor)
- {
- testClient *socket = new testClient(this);
- if (!socket->setSocketDescriptor(socketDescriptor))
- {
- QMessageBox::warning(NULL,"ERROR",socket->errorString());
- emit error(socket->error());
- return;
- }
- int num = getClientNum(socket); //检测用户,若存在,返回下标,若不存在,返回用户数
- socket->num = num; //记录序号
- emit newClientSignal(socket->peerAddress().toString(),(int)socket->state(),num); //将新客户端信息发给主窗口
- //连接信号和槽
- connect(socket, SIGNAL(newClient(QString,int,int)), this, SLOT(newClientSlot(QString,int,int)));
- connect(socket, SIGNAL(outputlog(QString)), this, SLOT(outputlogSlot(QString)));
- connect(socket, SIGNAL(updateProgressSignal(qint64,qint64,int)),
- this, SLOT(updateProgressSlot(qint64,qint64,int)));
- connect(socket, SIGNAL(newClientInfo(QString,int,int)),
- this, SLOT(newClientInfoSlot(QString,int,int)));
- connect(socket, SIGNAL(readyToTest(int,int)), this, SLOT(readyToTestSlot(int,int)));
- connect(socket, SIGNAL(startEvaluate(int,QString)), this, SLOT(startEvaluateSlot(int,QString)));
- connect(socket, SIGNAL(evaluateComplete(int,QString,int)),
- this, SLOT(evaluateCompleteSlot(int,QString,int)));
- if(num == totalClient)
- totalClient++;
- }
void testServer::incomingConnection(int socketDescriptor)
{
testClient *socket = new testClient(this);
if (!socket->setSocketDescriptor(socketDescriptor))
{
QMessageBox::warning(NULL,"ERROR",socket->errorString());
emit error(socket->error());
return;
}
int num = getClientNum(socket); //检测用户,若存在,返回下标,若不存在,返回用户数
socket->num = num; //记录序号
emit newClientSignal(socket->peerAddress().toString(),(int)socket->state(),num); //将新客户端信息发给主窗口
//连接信号和槽
connect(socket, SIGNAL(newClient(QString,int,int)), this, SLOT(newClientSlot(QString,int,int)));
connect(socket, SIGNAL(outputlog(QString)), this, SLOT(outputlogSlot(QString)));
connect(socket, SIGNAL(updateProgressSignal(qint64,qint64,int)),
this, SLOT(updateProgressSlot(qint64,qint64,int)));
connect(socket, SIGNAL(newClientInfo(QString,int,int)),
this, SLOT(newClientInfoSlot(QString,int,int)));
connect(socket, SIGNAL(readyToTest(int,int)), this, SLOT(readyToTestSlot(int,int)));
connect(socket, SIGNAL(startEvaluate(int,QString)), this, SLOT(startEvaluateSlot(int,QString)));
connect(socket, SIGNAL(evaluateComplete(int,QString,int)),
this, SLOT(evaluateCompleteSlot(int,QString,int)));
if(num == totalClient)
totalClient++;
}
通过setSocketDescriptor获取当前接入的socket描述符检测用户是否存在,记录序号
向主窗口发送信号,连接下层testClient信号和接收槽
更新客户端总数
检测用户是否存在的函数
- int testServer::getClientNum(testClientp socket) //检测用户,若存在,返回下标,若不存在,加入列表
- {
- for(int i = 0; i < ipList.size(); i++)
- {
- qDebug() << "No." << i << "ip: " << ipList[i];
- if(ipList[i] == socket->peerAddress().toString())
- {
- clientList[i] = socket;
- return i;
- }
- }
- clientList.push_back(socket); //存入客户列表
- ipList.push_back(socket->peerAddress().toString()); //存入ip列表
- return totalClient;
- }
int testServer::getClientNum(testClientp socket) //检测用户,若存在,返回下标,若不存在,加入列表
{
for(int i = 0; i < ipList.size(); i++)
{
qDebug() << "No." << i << "ip: " << ipList[i];
if(ipList[i] == socket->peerAddress().toString())
{
clientList[i] = socket;
return i;
}
}
clientList.push_back(socket); //存入客户列表
ipList.push_back(socket->peerAddress().toString()); //存入ip列表
return totalClient;
}
其他发送信号的函数均为连接下层和上层的中转,由于比较简单就不赘述了4、主窗口
在主窗口(继承自QMainWindow)绘制所需控件:
lineEdit_ipaddress:显示服务器IP
lineEdit_port:设置服务器端口
listWidget_sendwav:发送文件列表
tableWidget_clientlist:客户端信息列表,显示所有客户端IP,接入状态,发送/接收进度条
textEdit_status:状态栏
pushButton_addwav:添加发送文件按钮
pushButton_deletewav:删除发送文件按钮
pushButton_startserver:开启/关闭服务器按钮
pushButton_senddata:发送数据按钮
pushButton_deleteclient:删除客户端按钮
主窗口需定义的变量
- testServer *tcpServer; //tcp服务器指针
- bool isServerOn; //服务器是否开启
- std::vector<openFileStruct> openFileList; //存储目录和文件名对
testServer *tcpServer; //tcp服务器指针
bool isServerOn; //服务器是否开启
std::vector<openFileStruct> openFileList; //存储目录和文件名对
开启/关闭服务器按钮
- void testwhynot::on_pushButton_startserver_clicked() //开启服务器
- {
- if(isServerOn == false)
- {
- tcpServer = new testServer(this);
- ui->comboBox_testtype->setEnabled(false); //开启服务器后不能更改测试类型
- ui->pushButton_addwav->setEnabled(false); //不能更改wav列表
- ui->pushButton_deletewav->setEnabled(false);
- //监听本地主机端口,如果出错就输出错误信息,并关闭
- if(!tcpServer->listen(QHostAddress::Any,ui->lineEdit_port->text().toInt()))
- {
- QMessageBox::warning(NULL,"ERROR",tcpServer->errorString());
- return;
- }
- isServerOn = true;
- ui->pushButton_startserver->setText(tr("close server"));
- //显示到状态栏
- ui->textEdit_status->append(tr("%1 start server: %2:%3")
- .arg(getcurrenttime())
- .arg(ipaddress)
- .arg(ui->lineEdit_port->text()));
- //链接错误处理信号和槽
- //connect(this,SIGNAL(error(int,QString)),this,SLOT(displayError(int,QString)));
- //处理多客户端,连接从客户端线程发出的信号和主窗口的槽
- //连接客户端信息更改信号和槽
- connect(tcpServer,SIGNAL(newClientSignal(QString,int,int)),this,SLOT(acceptNewClient(QString,int,int)));
- //连接日志消息信号和槽
- connect(tcpServer,SIGNAL(outputlogSignal(QString)),
- this,SLOT(acceptOutputlog(QString)));
- //连接更新发送进度信号和槽
- connect(tcpServer, SIGNAL(updateProgressSignal(qint64,qint64,int)),
- this, SLOT(acceptUpdateProgress(qint64,qint64,int)));
- //连接更新客户端信息列表信号和槽
- connect(tcpServer, SIGNAL(newClientInfoSignal(QString,int,int)),
- this, SLOT(acceptNewClientInfo(QString,int,int)));
- //显示到状态栏
- ui->textEdit_status->append(tr("%1 wait for client...")
- .arg(getcurrenttime()));
- }
- else
- {
- isServerOn = false;
- ui->pushButton_startserver->setText(tr("start server"));
- //断开所有客户端连接,发出disconnected()信号
- for(int i=0; i<tcpServer->clientList.size(); i++)
- {
- if(ui->tableWidget_clientlist->item(i,2)->text() == clientStatus[3]) //处于连接状态才断开,否则无法访问testClientp指针
- {
- testClientp p = tcpServer->clientList[i];
- p->close();
- }
- }
- //清空列表
- tcpServer->clientList.clear();
- tcpServer->ipList.clear();
- clientInfoList.clear();
- for(int i=0; i<ui->tableWidget_clientlist->rowCount(); )
- ui->tableWidget_clientlist->removeRow(i);
- tcpServer->close(); //关闭服务器
- ui->textEdit_status->append(tr("%1 clost server.").arg(getcurrenttime()));
- ui->comboBox_testtype->setEnabled(true); //可以重新选择测试类型
- ui->pushButton_addwav->setEnabled(true); //能更改wav列表
- ui->pushButton_deletewav->setEnabled(true);
- //按钮无效化
- ui->pushButton_senddata->setEnabled(false);
- ui->pushButton_starttest->setEnabled(false);
- ui->pushButton_deleteclient->setEnabled(false);
- }
- }
void testwhynot::on_pushButton_startserver_clicked() //开启服务器
{
if(isServerOn == false)
{
tcpServer = new testServer(this);
ui->comboBox_testtype->setEnabled(false); //开启服务器后不能更改测试类型
ui->pushButton_addwav->setEnabled(false); //不能更改wav列表
ui->pushButton_deletewav->setEnabled(false);
//监听本地主机端口,如果出错就输出错误信息,并关闭
if(!tcpServer->listen(QHostAddress::Any,ui->lineEdit_port->text().toInt()))
{
QMessageBox::warning(NULL,"ERROR",tcpServer->errorString());
return;
}
isServerOn = true;
ui->pushButton_startserver->setText(tr("close server"));
//显示到状态栏
ui->textEdit_status->append(tr("%1 start server: %2:%3")
.arg(getcurrenttime())
.arg(ipaddress)
.arg(ui->lineEdit_port->text()));
//链接错误处理信号和槽
//connect(this,SIGNAL(error(int,QString)),this,SLOT(displayError(int,QString)));
//处理多客户端,连接从客户端线程发出的信号和主窗口的槽
//连接客户端信息更改信号和槽
connect(tcpServer,SIGNAL(newClientSignal(QString,int,int)),this,SLOT(acceptNewClient(QString,int,int)));
//连接日志消息信号和槽
connect(tcpServer,SIGNAL(outputlogSignal(QString)),
this,SLOT(acceptOutputlog(QString)));
//连接更新发送进度信号和槽
connect(tcpServer, SIGNAL(updateProgressSignal(qint64,qint64,int)),
this, SLOT(acceptUpdateProgress(qint64,qint64,int)));
//连接更新客户端信息列表信号和槽
connect(tcpServer, SIGNAL(newClientInfoSignal(QString,int,int)),
this, SLOT(acceptNewClientInfo(QString,int,int)));
//显示到状态栏
ui->textEdit_status->append(tr("%1 wait for client...")
.arg(getcurrenttime()));
}
else
{
isServerOn = false;
ui->pushButton_startserver->setText(tr("start server"));
//断开所有客户端连接,发出disconnected()信号
for(int i=0; i<tcpServer->clientList.size(); i++)
{
if(ui->tableWidget_clientlist->item(i,2)->text() == clientStatus[3]) //处于连接状态才断开,否则无法访问testClientp指针
{
testClientp p = tcpServer->clientList[i];
p->close();
}
}
//清空列表
tcpServer->clientList.clear();
tcpServer->ipList.clear();
clientInfoList.clear();
for(int i=0; i<ui->tableWidget_clientlist->rowCount(); )
ui->tableWidget_clientlist->removeRow(i);
tcpServer->close(); //关闭服务器
ui->textEdit_status->append(tr("%1 clost server.").arg(getcurrenttime()));
ui->comboBox_testtype->setEnabled(true); //可以重新选择测试类型
ui->pushButton_addwav->setEnabled(true); //能更改wav列表
ui->pushButton_deletewav->setEnabled(true);
//按钮无效化
ui->pushButton_senddata->setEnabled(false);
ui->pushButton_starttest->setEnabled(false);
ui->pushButton_deleteclient->setEnabled(false);
}
}
注意:1、listen(“监听类型”,“端口号”) 用于开启服务器,在指定端口监听客户端连接
2、connect连接了客户端更新信息的信号,来自下层testServer类
发送数据按钮
- void testwhynot::on_pushButton_senddata_clicked() //发送数据
- {
- //多客户端发送数据
- if(ui->comboBox_testtype->currentIndex() == testtype_keyword)
- {
- if(!check_file(keyword_filepath))
- return;
- }
- vector<clientInfo>::iterator it;
- //遍历所有客户端信息,确保都处于“已连接”状态
- for(it=clientInfoList.begin(); it!=clientInfoList.end(); it++)
- {
- if((*it).state != 3) //有客户端未处于“已连接”状态
- {
- QMessageBox::warning(this,tr("WARNING"),tr("client %1 is not connected.").arg((*it).ip));
- return;
- }
- }
- //没有文件
- if(openFileList.size() == 0)
- {
- QMessageBox::warning(NULL,tr("WARNING"),tr("no file! can not send!"));
- return;
- }
- for(int i = 0; i < tcpServer->clientList.size(); i++)
- {
- tcpServer->clientList[i]->prepareTransfer(openFileList,ui->comboBox_testtype->currentIndex());
- }
- //按钮无效化
- ui->pushButton_senddata->setEnabled(false);
- }
void testwhynot::on_pushButton_senddata_clicked() //发送数据
{
//多客户端发送数据
if(ui->comboBox_testtype->currentIndex() == testtype_keyword)
{
if(!check_file(keyword_filepath))
return;
}
vector<clientInfo>::iterator it;
//遍历所有客户端信息,确保都处于“已连接”状态
for(it=clientInfoList.begin(); it!=clientInfoList.end(); it++)
{
if((*it).state != 3) //有客户端未处于“已连接”状态
{
QMessageBox::warning(this,tr("WARNING"),tr("client %1 is not connected.").arg((*it).ip));
return;
}
}
//没有文件
if(openFileList.size() == 0)
{
QMessageBox::warning(NULL,tr("WARNING"),tr("no file! can not send!"));
return;
}
for(int i = 0; i < tcpServer->clientList.size(); i++)
{
tcpServer->clientList[i]->prepareTransfer(openFileList,ui->comboBox_testtype->currentIndex());
}
//按钮无效化
ui->pushButton_senddata->setEnabled(false);
}
调用底层prepareTransfer函数开始传输删除客户端按钮
- void testwhynot::on_pushButton_deleteclient_clicked()
- {
- QList<QTableWidgetItem*> list = ui->tableWidget_clientlist->selectedItems(); //读取所有被选中的item
- if(list.size() == 0) //没有被选中的就返回
- {
- QMessageBox::warning(this,QObject::tr("warning"),QObject::tr("please select a test client"));
- return;
- }
- std::set<int> del_row; //记录要删除的行号,用set防止重复
- for(int i=0; i<list.size(); i++) //删除选中的项
- {
- QTableWidgetItem* sel = list[i]; //指向选中的item的指针
- if (sel)
- {
- int row = ui->tableWidget_clientlist->row(sel); //获取行号
- del_row.insert(row);
- }
- }
- std::vector<int> del_list; //赋值给del_list,set本身为有序
- for(std::set<int>::iterator it=del_row.begin(); it!=del_row.end(); it++)
- {
- del_list.push_back(*it);
- }
- for(int i=del_list.size()-1; i>=0; i--) //逆序遍历
- {
- testClientp p = tcpServer->clientList[del_list[i]];
- p->close();
- ui->tableWidget_clientlist->removeRow(del_list[i]); //从显示列表中删除行
- clientInfoList.erase(clientInfoList.begin() + del_list[i]); //从内部列表删除
- tcpServer->ipList.erase(tcpServer->ipList.begin() + del_list[i]);
- tcpServer->clientList.erase(tcpServer->clientList.begin() + del_list[i]);
- tcpServer->totalClient--;
- }
- for(int i=0; i<tcpServer->clientList.size(); i++)
- {
- tcpServer->clientList[i]->num = i;
- }
- if(clientInfoList.empty()) //没有客户端,删除按钮无效化
- {
- ui->pushButton_deleteclient->setEnabled(false);
- }
- }
void testwhynot::on_pushButton_deleteclient_clicked()
{
QList<QTableWidgetItem*> list = ui->tableWidget_clientlist->selectedItems(); //读取所有被选中的item
if(list.size() == 0) //没有被选中的就返回
{
QMessageBox::warning(this,QObject::tr("warning"),QObject::tr("please select a test client"));
return;
}
std::set<int> del_row; //记录要删除的行号,用set防止重复
for(int i=0; i<list.size(); i++) //删除选中的项
{
QTableWidgetItem* sel = list[i]; //指向选中的item的指针
if (sel)
{
int row = ui->tableWidget_clientlist->row(sel); //获取行号
del_row.insert(row);
}
}
std::vector<int> del_list; //赋值给del_list,set本身为有序
for(std::set<int>::iterator it=del_row.begin(); it!=del_row.end(); it++)
{
del_list.push_back(*it);
}
for(int i=del_list.size()-1; i>=0; i--) //逆序遍历
{
testClientp p = tcpServer->clientList[del_list[i]];
p->close();
ui->tableWidget_clientlist->removeRow(del_list[i]); //从显示列表中删除行
clientInfoList.erase(clientInfoList.begin() + del_list[i]); //从内部列表删除
tcpServer->ipList.erase(tcpServer->ipList.begin() + del_list[i]);
tcpServer->clientList.erase(tcpServer->clientList.begin() + del_list[i]);
tcpServer->totalClient--;
}
for(int i=0; i<tcpServer->clientList.size(); i++)
{
tcpServer->clientList[i]->num = i;
}
if(clientInfoList.empty()) //没有客户端,删除按钮无效化
{
ui->pushButton_deleteclient->setEnabled(false);
}
}
删除客户端较麻烦,由于支持多选删除,需要先将选中的行号排序、去重、从大到小遍历删除(防止删除后剩余行号变化)不仅要关闭客户端,还要将其从显示列表和内部列表删除,保持显示和实际列表同步
上述便是基于tcp传输的发送/接收服务器端的搭建,客户端只需遵从上述的发送协议搭建即可,客户端发送与接收与服务器基本相同,也不赘述了。
本文偏重于工程实现,Qt的TCP传输原理叙述不多,若要深入了解qt套接字编程,请参考:http://cool.worm.blog.163.com/blog/static/64339006200842922851118/
上一篇: 订单详情页
下一篇: python day02