基于QT的TCP双端传输demo
Ubuntu的服务器和Windows的客户端,两者可以进行互相传输数据,包括文字和文件,并且支持并行传输。
demo介绍
下面介绍页面和系统功能:
1.客户端和服务器连接后可以进行通信;
2.客户端可以向服务器同时上传多个文件,页面上会有进度条进行展示进度;
3.客户端在登陆后会获取服务器开放的一些文件,并且支持同时下载多个文件;
4.客户端在上传和下载后会向数据库插入数据,允许对文件的类型和标记进行搜索,可以右键删除历史记录;
5. 服务器在收到客户端上传的文件或者客户端下载文件后也会数据库插入数控,允许对文件的类型和标记进行搜索;
6. 客户端和服务器都可以自己选择保存文件的路径;
7. 页面展示
客户端代码设计思路
设置页
1.在输入想要连接的ip以及端口后,可以点击连接按钮。主线程在创建的时候会有一个QTcpSocket的client,在这时可以对client的连接状态进行判断。
client->state()==QAbstractSocket::ConnectedState 断开之前的连接
client->state()==QAbstractSocket::UnconnectedState 开始连接client->connectToHost(address,port);
加一个连接判断connected = client->waitForConnected(100);如果连接不成功的话,可以禁止客户端上传和下载文件。
如果client连接成功的话,可以触发信号connected,可以用这个写一个连接进行一些处理,
之后可以使用这个处理。
QDataStream out(client);
out.setVersion(QDataStream::Qt_5_12);
这里需要注意,发送端和接收端的版本要一致,否则可能会出错!
有了这个后,就可以传输数据了。
out<<quint8(3);
发送的时候是什么类型接收也要是什么类型。
至于这个QDataStream 类似队列,可以放入不同类型的数据,需要有序取放,它会自动发送。
2.之后会向服务器请求公开的文件。
可以让QDataStream 第一个存在请求类型,需要自己定义。
在服务器返回请求后,会触发client的readyRead信号,需要写一个connect。
在这里,需要对返回类型进行判断,也就是看QDataStream 第一个数据。由于前面发送了quint8(3),也就是请求公开目录。所以这时返回的是文件数据。再取出个文件数量(也是服务器返回的)循环这么多次依次取出文件的相关数据,以下操作都是放在循环里面的。
QString fileName;
QString filePath;
QString fileSize;
in>>fileName>>filePath>>fileSize;
要在ui里面设计一个表,然后插入一行,这里操作时先创建小格子,再放到表格里面。
ui->tableDir->insertRow(i);
QTableWidgetItem *fileNameItem = new QTableWidgetItem(fileName);
QTableWidgetItem *fileSizeItem = new QTableWidgetItem(fileSize);
QProgressBar *progressBar = new QProgressBar();
ui->tableDir->setItem(i, 0, fileNameItem);
ui->tableDir->setItem(i, 1, fileSizeItem);
ui->tableDir->setCellWidget(i, 2, progressBar);
我这里使用的是这种方式接收不同文件的不同信息,当然二维数组也可以。
struct fileDir{
QString fileName;
QString filePath;
QString fileSize;
};
QVector dir;
之后,可以勾选想要下载的文件,同时向服务器请求下载。
在获得服务器的目录文件的同时,插入表格并在后面放入一个复选框,然后建立下面的连接,再用个二维数组保存需要下载的数据,后面用一个循环依次开辟线程进行获取。
connect(checkBox, &QCheckBox::stateChanged,[=](int state){
if(state==2){
downRequest.push_back({i,filePath});
}
else if(state==0){
downRequest.removeAll(QPair<qint32, QString>(i,filePath));
}
});
ui->tableDir->resizeRowsToContents();
下面就是循环里面使用多线程的方法。
downloadThread *recvWorker=new downloadThread(nullptr,downRequest[i].second,downRequest[i].first,address,port,folderPath);
QThread *recvThread=new QThread();
recvWorker->moveToThread(recvThread);
recvThread->start();
connect(this,&MainWindow::recvStart,recvWorker,&downloadThread::handleDownload);
//发射信号激活工作函数
emit recvStart();
disconnect(this,&MainWindow::recvStart,recvWorker,&downloadThread::handleDownload);
在这里使用的是moveToThread的方法使用多线程,还有一种重写run函数的方法暂不介绍。
先创建工作类对象,并且父指针为空,再创建一个子线程,把工作类对象移到里面。由于这种方法工作类没有run函数,所以只能通过外界的信号启动槽函数的方法。
下载页
由于是多开线程的方法,所以要在槽函数里面创建新的tcp连接。要注意,用数据流传输数据时要设定版本。
void downloadThread::handleDownload(){
socketDown=new QTcpSocket;
socketDown->connectToHost(m_address,m_port);
QDataStream out(socketDown);
out.setVersion(QDataStream::Qt_5_12);
out<<quint8(5)<<m_filePath;
connect(socketDown, &QTcpSocket::readyRead,this,&downloadThread::readData);
}
quint8(5)是我设定的向服务器请求下载文件数据的协议。在这里建立个连接,用于接收QTcpSocket的readyRead的信号。
//1是发送文字,2是发送文件,3是请求目录
//1、2是通过tcp连接,对tcpsocket进行read和write来通信
//3向服务器获取文件目录,4是服务器返回的目录,5是从服务器下载文件,6是服务器返回文件数据
bytesAvailable()是QTcpSocket的一个获取socket里面数据的函数。在获取数据前可以用这个进行判断。
数据传输方法
在QT的TCP传输中,有两种传输数据的方法。
1.socket.write/socket.read
这种是直接读写的方法,不需要对数据进行序列化,不过数据要是QByteArray类型才行,优点是使用方便,用起来简单。
socket.write(QByteArray data)
2.QDatastream out(&socket)
数据流传输数据。在传入的时候对数据进行序列化,可同时传输多种类型的数据,不过取的时候接收的变量类型和放的时候发送的变量类型一样。
3.总结
我一般用第一种传大量的数据,第二种传复杂的数据。