Cross-process Socket communication and socket cross-thread communication in Qt

A QTcpServer creation process

  1. Create a socket server QTcpServer object,
  2. Set the listener through the QTcpServer object, namely: QTcpServer::listen()
  3. Detect whether there is a new client connection based on the QTcpServer::newConnection() signal
  4. If there is a new client connection, call *QTcpServer::nextPendingConnection() to get the communication QTcpSocket object, use the communication socket object QTcpSocket to communicate with the client, and the readyRead signal
 //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);
    });

The benefits of this article, free to receive Qt development learning materials package, technical video, including (C++ language foundation, introduction to Qt programming, QT signal and slot mechanism, QT interface development-image drawing, QT network, QT database programming, QT project combat, QSS, OpenCV, Quick module, interview questions, etc.) ↓↓↓↓↓↓See below↓↓Click on the bottom of the article to receive the fee↓↓ 

Two QTcpSocketClient creation process

  1. Create a socket server QTcpSocket object,
  2. Connect to the server, bind the IP and port information bound to the server, QAbstractSocket::connectToHost(QHostAddress("127.0.0.1"),8888)
  3. Detect whether the connection with the server is successful, connectToHost() function and send the connected() signal after the connection is successfully established
  4. Use the communication socket object QTcpSocket to communicate with the client, readyRead signal
    //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;
    });

Three qtSocket multi-thread communication

Simulate the client sending files and the server receiving files as an example, using multi-threaded communication, and some codes to implement ideas.

3.1 Implementation idea of ​​socketClient multi-threaded sending files

3.1.1 SendFile thread task class implementation ideas

Since the thread needs to complete multiple sub-functions, using the moveToThread method can achieve multi-threading more flexibly

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 Implementation ideas in the main thread

//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 Implementation idea of ​​socketServer multi-threaded receiving files

3.2.1 TcpServerHelper is derived from the QTcpServer class

When communicating across threads between servers in qt, it is necessary to ensure that the creation of the socket object is consistent with the thread used. It cannot be created in the main thread and passed to the child thread process through the pointer. The correct way is to rewrite the incommingConnection() method in the socketServer, and the client Set up matching with the client connection request, that is, create a server class derived from QTcpServer, and rewrite the incomingConnection method. The main function is that when the client initiates a new connection, this function will be called automatically, and only the socket descriptor will be sent out.

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 thread task class

Since the thread only handles the function of receiving data, the thread function is relatively simple, so the QThread subclass is derived, and the run() method is rewritten to achieve multi-threading

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 Main thread

主线程中当客户端发起连接请求时才开启子线程工作,即:
 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 About socket cross-thread communication in Qt

The socket object created by the main thread is passed to the sub-thread. It may be due to the qt version or the windows platform problem. The log may report that the socket object created by the main thread cannot work in the sub-thread. In addition, a server corresponds to a client thread, there may be no problem, but if QTcpServer allocates an independent thread for each client [typically such as a chat room], the IncomingConnection() function must be rewritten. Qt help documentation: The QTcpSocket object automatically created by QTcpServer cannot be called in a thread, and it is mentioned in the help of incomingConnection() that if the client connection is passed into a separate thread, the QTcpSocket object must be created in the thread, and the socket object The creation is achieved by rewriting the incomingConnection() function.

The working mechanism of the QTcpServer class:

  1. When there is an incoming connection, QTcpServer will create a socket that matches the client, and return a socketDescriptor (socket descriptor) pointing to the socket memory, which is of type qintptr in QT.
  2. Then, QTcpServer will automatically call the incomingConnection() function, which receives this socketDescriptor.

incomingConnection source implementation:

  1. First, create a QTcpSocket object,
  2. Then, call QTcpSocket::setSocketDescriptor(qintptr socketDescriptor) to set the socket descriptor;
  3. Finally, call addPendingConnection(QTcpSocket * socket) to suspend the created QTcpSocket object pointer in the created list. This behavior can terminate the blocking of waitForNewConnection(), and the user can obtain the QTcpSocket object pointer by calling the nextPendingConnection() function. Note: In the thread version of the incomingConnection() function, the call to addPendingConnection() can be omitted, because it is no longer necessary to obtain the socket pointer through the nextPendingConnection() function. Rewrite the incomingConnection() function: To rewrite the function, you need to create a derived class of QTcpServer, and you need to create a thread class.

The article is transferred from the blog garden (fighting against the Buddha and the Monkey King): Qt inter-process Socket communication and socket cross-thread communication- Fighting against the Buddha and the Monkey King- Blog Garden

The benefits of this article, free to receive Qt development learning materials package, technical video, including (C++ language foundation, introduction to Qt programming, QT signal and slot mechanism, QT interface development-image drawing, QT network, QT database programming, QT project combat, QSS, OpenCV, Quick module, interview questions, etc.) ↓↓↓↓↓↓See below↓↓Click on the bottom of the article to receive the fee↓↓

Guess you like

Origin blog.csdn.net/QtCompany/article/details/132239177