Qt中跨进程Socket通信以及socket跨线程通信
一 QTcpServer 创建流程
- 创建套接字服务器 QTcpServer 对象,
- 通过 QTcpServer 对象设置监听,即:QTcpServer::listen()
- 基于 QTcpServer::newConnection() 信号检测是否有新的客户端连接
- 如果有新的客户端连接调用*QTcpServer::nextPendingConnection() 得到通信的QTcpSocket对象 使用通信的套接字对象 QTcpSocket 和客户端进行通信,readyRead信号
//1.创建server对象 auto server=new QTcpServer(this); //2.设置服务器监听listen(ipAddr,port) auto res=server->listen(QHostAddress::Any,8888);//返回监听成功与否,可能存在端口占用情况 //3.基于 QTcpServer::newConnection() 信号检测是否有新的客户端连接 connect(server,&QTcpServer::newConnection,[=]() { QTcpSocket* tcpSocket=server->nextPendingConnection();//接收新的客户端连接,用于实际的收发处理 //4.收发处理, //4.1 当收到数据请求时,tcpSocket会发射readyread信号 connect(tcpSocket,&QTcpSocket::readyRead,[=]() { //收到信息请求 auto sMsg=tcpSocket->readAll(); qDebug()<<"Datas from the remote client:"<<sMsg; }); //4.2 写数据 QByteArray sWriteMsg="Hello Client"; tcpSocket->write(sWriteMsg); });
二 QTcpSocketClient创建流程
- 创建套接字服务器 QTcpSocket对象,
- 连接服务器,绑定服务器端绑定的IP和端口信息, QAbstractSocket::connectToHost(QHostAddress("127.0.0.1"),8888)
- 检测是否与服务器连接成功,connectToHost() 函数并成功建立连接之后发出 connected() 信号
- 使用通信的套接字对象 QTcpSocket 和客户端进行通信,readyRead信号
//client建立流程 //1.创建通信的套接字类 QTcpSocket 对象 QTcpSocket* tcpSocket=new QTcpSocket(this); //2.使用服务器端绑定的 IP 和端口连接服务器 QAbstractSocket::connectToHost() tcpSocket->connectToHost(QHostAddress("127.0.0.1"),8888); //3.检测是否与服务器连接成功,connectToHost() 函数并成功建立连接之后发出 connected() 信号 connect(tcpSocket,&QTcpSocket::connected,[=]() { qDebug()<<"Success to connect to the remote server"; }); //4.使用 QTcpSocket 对象和服务器进行通信,收到数据请求时,tcpSocket会发射readyread信号 connect(tcpSocket,&QTcpSocket::readyRead,[=]() { //收到信息请求 auto sMsg=tcpSocket->readAll(); qDebug()<<"Datas from the remote server:"<<sMsg; });
三 qtSocket多线程通信
模拟客户端发送文件,服务器接收文件为例子,使用多线程方式进行通信,部分代码实现思路。
3.1 socketClient 多线程发送文件实现思路
3.1 .1 SendFile线程任务类实现思路
由于该线程需要完成多个子功能,因此使用moveToThread方式可更加灵活实现多线程
class SendFile : public QObject //通过 slot 机智定义两个work()函数处理不同的任务功能: public slots: //创建线程任务函数 void connectServer();//连接服务器 void sendFileTask();//发送文件 …… //通过signal信号来向主线程发送任务完成情况及线程间的通信 signals: void connectOk(); void disconnectOk(); void sendCurrentPercen(int n);
3.1.2 主线程中实现思路
//1.创建线程对象 QThread* pThread=new QThread; //2.创建线程任务对像 SendFile* worker=new SendFile; //3.将任务对像象添加到线程中 worker->moveToThread(pThread); //4.启动线程 pThread->start(); //5.信号槽机制关联执行线程任务及线程完成情况 connect(this,&Dialogtest::startConnectServer,worker,&SendFile::connectServer); connect(this,&Dialogtest::sendFileSignal,worker,&SendFile::sendFileTask); connect(worker,&SendFile::connectOk,this,[=](){//连接成功 }); connect(worker,&SendFile::disconnectOk,this,[=](){ //已断开连接}); connect(worker,&SendFile::sendCurrentPercen,this,[=](int nPercent){ //进度条处理 });
3.2 socketServer多线程接收文件实现思路
3.2.1 TcpServerHelper派生于QTcpServer类
qt中server间跨线程通信时,要保证socket对象的创建与使用线程一致,不能在主线程创建,通过指针传递给子线程进程使用,正确做法是重写socketServer中的incommingConnetction()方法,与客户端连接请求进行设定匹配,即创建一个QTcpServer派生的server类,并重写incomingConnection方法。主要功能仅是 当客户端发起新链接时,该函数会被自动调用,仅向外发送socket描述符。
class TcpServerHelper : public QTcpServer { Q_OBJECT public: explicit TcpServerHelper(QObject *parent = nullptr); protected: //重写incomingConnection,用于多线程通讯,子线程中不能使用主线程中创建的套接字对象 void incomingConnection(qintptr socketDescriptor); signals: void newSockDescriptor(qintptr _sock); }; //.cpp TcpServerHelper::TcpServerHelper(QObject *parent) : QTcpServer(parent){} //当客户端发起新链接时,该函数会被自动调用,仅向外发送socket描述符 void TcpServerHelper::incomingConnection(qintptr socketDescriptor) { emit newSockDescriptor(socketDescriptor); }
3.2.2 ReceFile线程任务类
由于该线程仅处理接收数据功能,线程功能比较单一,故派生QThread子类,并重写run()方法来实现多线程
class ReceFile() : public Qthread class ReceFile(qintptr _socketDesc,QObject* parent=nullptr); void run();//taskInterface,run函数中要注意使用exec()保持子线程时刻监听,避免子线程退出问题
//通过signal信号来向主线程接收任务完成情况及线程间的通信 signals: void readDoneSig();
void ReceFile::run() { //子线程中创建tcpSocket对象,设置socket描述符,此时将和发起链接的客户端进行通信 m_tcpSocket=new QTcpSocket; m_tcpSocket->setSocketDescriptor(m_sockDecriptor); QFile* file=new QFile("recv.txt"); file->open(QFile::WriteOnly); //接收数据 connect(m_tcpSocket,&QTcpSocket::readyRead,[=]() { static int count=0; static int total=0; //第一次读文件信息,文件大小等 if(count==0) m_tcpSocket->read((char*)&total,4); //读剩余数据 QByteArray readDatas=m_tcpSocket->readAll(); count+=readDatas.size(); //判断数据是否接收完毕 if(count==total) { m_tcpSocket->close(); m_tcpSocket->deleteLater(); m_tcpSocket=nullptr; file->close(); file->deleteLater(); emit readDoneSig(); } }); //子线程进入事件循环,保持时刻监听 exec(); }
3.2.3 主线程
主线程中当客户端发起连接请求时才开启子线程工作,即: connect(m_tcpServer,&TcpServerHelper::newSockDescriptor,this,[=](qintptr _sockDesc) { //创建子线程,并启动线程 ReceFile* recvThread=new ReceFile(_sockDesc); recvThread->start(); //资源释放,善后工作 connect(recvThread,&ReceFile::readDoneSig,[=]() { //子线程退出 recvThread->exit(); recvThread->wait(); recvThread->deleteLater(); }); });
3.3 关于Qt中socket跨线程通讯问题
主线程创建的socket套接字对象,传入到子线程中,可能因为qt版本或者windows平台问题,日志可能会报主线程创建的socket对象不能在子线程工作。另外,一个server对应一个client线程,可能没有问题出现,但是QTcpServer若为每个客户端分配一个独立线程【典型的如聊天室】,必须重写 IncomingConnection()函数。 Qt帮助文档:不能在线程中调用QTcpServer自动创建的QTcpSocket对象,并且在incomingConnection()的帮助中有提到,若将客户端的连接传入单独的线程,则QTcpSocket对象必须创建在线程中,socket对象的创建通过重写incomingConnection()函数实现。
QTcpServer类的工作机制:
- 在有传入连接时,QTcpServer会创建一个与客户端匹配的socket,并返回一个指向socket内存的socketDescriptor(socket描述符),在QT中该描述符是qintptr类型的。
- 然后,QTcpServer会自动调用incomingConnection()函数,该函数接收这个socketDescriptor。
incomingConnection源码实现:
- 首先,创建了一个QTcpSocket对象,
- 然后,调用QTcpSocket::setSocketDescriptor(qintptr socketDescriptor),设置socket描述符;
- 最后,调用addPendingConnection(QTcpSocket * socket),将创建的QTcpSocket对象指针挂起在已创建列表中,该行为可终止waitForNewConnection()的阻塞,并且用户可以通过调用nextPendingConnection()函数获得这个QTcpSocket对象指针。注意:在线程版的incomingConnection()函数中,可以省略这步addPendingConnection()的调用,因为不再需要通过nextPendingConnection()函数来获得socket指针了。 重写incomingConnection()函数: 重写函数需要创建一个QTcpServer的派生类,另外还需创建一个线程类。