Qt之进程通信-IPC(QLocalServer,QLocalSocket 含源码+注释)

一、IPC通信示例图

1.1 设置关键字并连接的示例图

如下,分别在各个界面的关键字控件中填入key,依次连接。
请添加图片描述

1.2 进程间简单的数据通信示例图

如下,简单演示了server与全部、指定socket通信及接收socket发送的数据。
请添加图片描述

1.3 断开连接的示例图

1.3.1 由Server主动断开连接

如下,演示了单独断开一个及断开全部的操作,其中断开操作是由server发送数据通知socket断开,server这边则等待断开返回。
请添加图片描述

1.3.2 由Socket主动断开连接

如下演示了socket程序主动断开的操作
请添加图片描述

1.4 Server停止监听后的效果

如下,演示了server停止监听后仍可以与已经连接过的socket的通信的效果。
请添加图片描述

二、个人理解与一些心得

  1. 若要使用QLocalServer/QLocalSocket,需要在 pro添加network模块(添加这一行QT += network)。
  2. 在我个人使用中发现,在同一进程中,调用socket的write是不会触发当前进程的readyRead信号链接的信号槽。
  3. 在QLocalServer停止监听后不会影响已经连接好的Socket对象,因为QLocalServer的close仅负责停止监听,并不断开。

三、一些疑问(求教 家人们😂)

  1. 在帮助中又下方的帮助代码,但是在本地测试发现不能先调用disconnectFromServer,后面的waitForDisconnected总是拿不到状态。
	socket->disconnectFromServer();
  	if (socket->waitForDisconnected(1000))
      qDebug("Disconnected!");
  1. 以及在个人理解中的第2点也存在一些疑问

四、源码

CMainWindowServer

CMainWindowServer.h

#ifndef CMAINWINDOWSERVER_H
#define CMAINWINDOWSERVER_H

#include <QMainWindow>
#include <QLocalServer>

namespace Ui {
class CMainWindowServer;
}

class QLocalSocket;
class CMainWindowServer : public QMainWindow
{
    Q_OBJECT

public:
    explicit CMainWindowServer(QWidget *parent = nullptr);
    ~CMainWindowServer();

private:
    /**
     * @brief disconnectSocketByStr 指定socket断开函数(复用)
     * @param socketStr 指定的socket套接字字符串
     */
    void disconnectSocketByStr(const QString &socketStr);

private slots:
    /**
     * @brief on_btnListen_clicked 开始监听按钮
     */
    void on_btnListen_clicked();

    /**
     * @brief on_btnStopListen_clicked 停止监听按钮
     */
    void on_btnStopListen_clicked();

    /**
     * @brief on_newConnection 新连接槽函数
     */
    void on_newConnection();

    /**
     * @brief on_socketReadyRead 数据接收槽函数
     */
    void on_socketReadyRead();

    /**
     * @brief on_btnDisconnectSocket_clicked 断开socket槽函数
     */
    void on_btnDisconnectSocket_clicked();

    /**
     * @brief on_btnSend_clicked 数据发送按钮
     */
    void on_btnSend_clicked();

private:
    Ui::CMainWindowServer *ui;

    QLocalServer            m_localServer;          // 通信服务对象

    QList<QLocalSocket *>   m_listLocalSockets;     //  本地套接字列表
};

#endif // CMAINWINDOWSERVER_H

CMainWindowServer.cpp

#include "CMainWindowServer.h"
#include "ui_CMainWindowServer.h"

#include <QLocalServer>
#include <QMessageBox>
#include <QLocalSocket>
#include <QDebug>
#include <QTimer>

CMainWindowServer::CMainWindowServer(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::CMainWindowServer)
{
    ui->setupUi(this);
    // 关联套接字连接槽函数
    connect(&m_localServer, &QLocalServer::newConnection, this, &CMainWindowServer::on_newConnection);
}

CMainWindowServer::~CMainWindowServer()
{
    delete ui;
}

void CMainWindowServer::disconnectSocketByStr(const QString &socketStr)
{
    // 强转当前指针字符串或者socket指针对象
    QLocalSocket *socket = (QLocalSocket *)socketStr.toUInt();
    // 判断是否存在于socket容器中
    if(m_listLocalSockets.contains(socket)) {
        // 发送关闭提示给socket
        socket->write(u8"服务器断开!");
        // 等待3000毫秒接收断开链接的信息
        if(!socket->waitForDisconnected(3000)) {
            QMessageBox::information(this, u8"提示", "断开超时");
        }
        else {
            // 移除当前位置的控件
            QMessageBox::information(this, u8"提示", "断开成功");
            // 移除当前指定的
            ui->comboBoxSockets->removeItem(ui->comboBoxSockets->findText(socketStr));
            m_listLocalSockets.removeOne(socket);
        }
    }
    else {
        QMessageBox::information(this, u8"提示", socketStr + u8"地址无记录");
    }
}

void CMainWindowServer::on_btnListen_clicked()
{
    QString listenKey = ui->lineEditListenKey->text();
    // 获取是否监听成功
    bool flag =  m_localServer.listen(listenKey);
    if(!flag) {
        QMessageBox::information(this, u8"提示", m_localServer.errorString());
    }
    else {
        QMessageBox::information(this, u8"提示", u8"监听成功");
        // 监听后‘开始监听’按钮禁用,‘停止监听’按钮启用
        ui->btnListen->setEnabled(false);
        ui->btnStopListen->setEnabled(true);
    }
}

void CMainWindowServer::on_btnStopListen_clicked()
{
    m_localServer.close();
    if(!m_localServer.isListening()) {
        QMessageBox::information(this, u8"提示", u8"停止监听成功");
        // 停止监听后‘开始监听’按钮启用,‘停止监听’按钮禁用
        ui->btnListen->setEnabled(true);
        ui->btnStopListen->setEnabled(false);
    }
    else {
        QMessageBox::information(this, u8"提示", u8"停止监听失败");
    }
}

void CMainWindowServer::on_newConnection()
{
    // 判断是否存在新的socket连接
    if(m_localServer.hasPendingConnections()) {
        // 获取套接字对象
        QLocalSocket *socketTmp = m_localServer.nextPendingConnection();
        // 套接字对象添加到套接字容器中
        m_listLocalSockets.append(socketTmp);
        // 套接字地址转为数值
        QString socketStr = QString::number((uint64_t)socketTmp);
        // 套接字文本添加到下拉列表中并在界面做出连接提示
        ui->comboBoxSockets->addItem(socketStr);
        ui->textEdit->append(socketStr + "加入连接!");
        // 关联新数据的信号槽
        connect(socketTmp, &QLocalSocket::readyRead, this, &CMainWindowServer::on_socketReadyRead);
    }
}

void CMainWindowServer::on_socketReadyRead()
{
    // 获取发送信号的对象
    QLocalSocket *curSocket = dynamic_cast<QLocalSocket *>(sender());
    // 将数据直接读取并添加到多行文本框中
    ui->textEdit->append(QString::number((uint64_t)curSocket) + ":" + curSocket->readAll());
}

void CMainWindowServer::on_btnDisconnectSocket_clicked()
{
    // 获取将要断开的文本并弹出断开提示
    QString socketStr = ui->comboBoxSockets->currentText();
    QMessageBox::StandardButton flag = QMessageBox::information(this, u8"提示", u8"是否断开" + socketStr + "?");
    if(QMessageBox::Ok != flag) {
        return;
    }

    // 根据断开文本做不不同断开操作
    if(0 == socketStr.compare(u8"全部")) {
        foreach(QLocalSocket *socket, m_listLocalSockets) {
            disconnectSocketByStr(QString::number((uint64_t)socket));
        }
    }
    else {
        disconnectSocketByStr(socketStr);
    }
}

void CMainWindowServer::on_btnSend_clicked()
{
    // 获取将要接收数据的识别文本
    QString socketStr = ui->comboBoxSockets->currentText();
    // 获取将要发送的数据
    QString data = ui->textEditSendData->toPlainText();

    // 根据识别文本做出不同的操作
    if(0 == socketStr.compare(u8"全部")) {
        foreach(QLocalSocket *socket, m_listLocalSockets) {
            socket->write(data.toUtf8());
        }
    }
    else {
        // 直接将当前文本强转为套接字对象(因为该文本为指针地址强转而来)
        QLocalSocket *socket = (QLocalSocket *)socketStr.toUInt();
        if(m_listLocalSockets.contains(socket)) {
            socket->write(data.toUtf8());
        }
        else {
            QMessageBox::information(this, u8"提示", socketStr + "地址找不到");
        }
    }

}

CMainWindowServer.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>CMainWindowServer</class>
 <widget class="QMainWindow" name="CMainWindowServer">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>340</width>
    <height>420</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>CMainWindow</string>
  </property>
  <widget class="QWidget" name="centralWidget">
   <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,3,1,0">
    <item>
     <layout class="QHBoxLayout" name="horizontalLayout">
      <item>
       <widget class="QLineEdit" name="lineEditListenKey"/>
      </item>
      <item>
       <widget class="QPushButton" name="btnListen">
        <property name="text">
         <string>监听</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QPushButton" name="btnStopListen">
        <property name="enabled">
         <bool>false</bool>
        </property>
        <property name="text">
         <string>停止监听</string>
        </property>
       </widget>
      </item>
     </layout>
    </item>
    <item>
     <layout class="QHBoxLayout" name="horizontalLayout_3">
      <item>
       <widget class="QComboBox" name="comboBoxSockets">
        <item>
         <property name="text">
          <string>全部</string>
         </property>
        </item>
       </widget>
      </item>
      <item>
       <widget class="QPushButton" name="btnDisconnectSocket">
        <property name="text">
         <string>断开当前链接选项</string>
        </property>
       </widget>
      </item>
     </layout>
    </item>
    <item>
     <widget class="QTextEdit" name="textEdit"/>
    </item>
    <item>
     <widget class="QTextEdit" name="textEditSendData"/>
    </item>
    <item>
     <layout class="QHBoxLayout" name="horizontalLayout_2">
      <item>
       <spacer name="horizontalSpacer">
        <property name="orientation">
         <enum>Qt::Horizontal</enum>
        </property>
        <property name="sizeHint" stdset="0">
         <size>
          <width>40</width>
          <height>20</height>
         </size>
        </property>
       </spacer>
      </item>
      <item>
       <widget class="QPushButton" name="btnSend">
        <property name="text">
         <string>发送</string>
        </property>
       </widget>
      </item>
     </layout>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menuBar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>340</width>
     <height>23</height>
    </rect>
   </property>
  </widget>
  <widget class="QToolBar" name="mainToolBar">
   <attribute name="toolBarArea">
    <enum>TopToolBarArea</enum>
   </attribute>
   <attribute name="toolBarBreak">
    <bool>false</bool>
   </attribute>
  </widget>
  <widget class="QStatusBar" name="statusBar"/>
 </widget>
 <layoutdefault spacing="6" margin="11"/>
 <resources/>
 <connections/>
</ui>

CMainWindowSocket

CMainWindowSocket.h

#ifndef CMAINWINDOWSOCKET_H
#define CMAINWINDOWSOCKET_H

#include <QMainWindow>
#include <QLocalSocket>

namespace Ui {
class CMainWindowSocket;
}

class CMainWindowSocket : public QMainWindow
{
    Q_OBJECT

public:
    explicit CMainWindowSocket(QWidget *parent = nullptr);
    ~CMainWindowSocket();

private slots:
    /**
     * @brief on_btnConnect_clicked 连接按钮信号槽
     */
    void on_btnConnect_clicked();

    /**
     * @brief on_btnSend_clicked 发送按钮信号槽
     */
    void on_btnSend_clicked();

    /**
     * @brief on_btnDisConnected_clicked 断开连接信号槽
     */
    void on_btnDisConnected_clicked();

    /**
     * @brief on_socketReadyRead 数据接收信号槽
     */
    void on_socketReadyRead();

private:
    Ui::CMainWindowSocket *ui;

    QLocalSocket    m_localSocket;  // 套接字对象
};

#endif // CMAINWINDOWSOCKET_H

CMainWindowSocket.cpp

#include "CMainWindowSocket.h"
#include "ui_CMainWindowSocket.h"

#include <QMessageBox>
#include <QTimer>

CMainWindowSocket::CMainWindowSocket(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::CMainWindowSocket)
{
    ui->setupUi(this);
    // 关联数据接收信号槽
    connect(&m_localSocket, &QLocalSocket::readyRead, this, &CMainWindowSocket::on_socketReadyRead);
}

CMainWindowSocket::~CMainWindowSocket()
{
    delete ui;
}

void CMainWindowSocket::on_btnConnect_clicked()
{
    // 根据key连接服务
    m_localSocket.connectToServer(ui->lineEditConnectKey->text());
    // 等待一秒是否连接成功
    if(m_localSocket.waitForConnected(1000)) {
        QString tip = u8"连接成功";
        // 连接成功后打开读写通道
        if(!m_localSocket.open(QIODevice::ReadWrite)) {
            tip.append(QString(u8",但Socket读写打开失败(%1)").arg(m_localSocket.errorString()));
        }
        QMessageBox::information(this, u8"提示", tip);
        // 连接后‘连接’按钮禁用,‘断开连接’按钮启用
        ui->btnConnect->setEnabled(false);
        ui->btnDisConnected->setEnabled(true);

    }
    else {
        QMessageBox::information(this, u8"提示", u8"连接失败");
    }
}

void CMainWindowSocket::on_btnSend_clicked()
{
    // 写入数据
    m_localSocket.write(ui->textEditSendData->toPlainText().toUtf8());
    // 等待写入信号,若未写入成功弹出提示
    if(!m_localSocket.waitForBytesWritten(100)) {
        QMessageBox::information(this, u8"提示", m_localSocket.errorString());
    }
}

void CMainWindowSocket::on_btnDisConnected_clicked()
{
    if(QLocalSocket::ConnectedState == m_localSocket.state()) {
        m_localSocket.write(QString(u8"%1已断开!").arg((uint64_t)this).toUtf8());
        m_localSocket.disconnectFromServer();
        // 断开连接后‘连接’按钮启用,‘断开连接’按钮禁用
        ui->btnConnect->setEnabled(true);
        ui->btnDisConnected->setEnabled(false);
    }
    else {
        QMessageBox::information(this, u8"提示", u8"断开失败,当前并非连接状态!" );
    }
}

void CMainWindowSocket::on_socketReadyRead()
{
    // 读取索引数据
    QString data = m_localSocket.readAll();
    // 识别数据文本,当复合条件是断开连接
    if(0 == data.compare(u8"服务器断开!")) {
        on_btnDisConnected_clicked();
    }
    ui->textEdit->append(data);
}

CMainWindowSocket.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>CMainWindowSocket</class>
 <widget class="QMainWindow" name="CMainWindowSocket">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>300</width>
    <height>420</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>CMainWindowSocket</string>
  </property>
  <widget class="QWidget" name="centralWidget">
   <layout class="QVBoxLayout" name="verticalLayout" stretch="0,3,1,0">
    <item>
     <layout class="QHBoxLayout" name="horizontalLayout">
      <item>
       <widget class="QLineEdit" name="lineEditConnectKey"/>
      </item>
      <item>
       <widget class="QPushButton" name="btnConnect">
        <property name="text">
         <string>连接</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QPushButton" name="btnDisConnected">
        <property name="enabled">
         <bool>false</bool>
        </property>
        <property name="text">
         <string>断开连接</string>
        </property>
       </widget>
      </item>
     </layout>
    </item>
    <item>
     <widget class="QTextEdit" name="textEdit"/>
    </item>
    <item>
     <widget class="QTextEdit" name="textEditSendData"/>
    </item>
    <item>
     <layout class="QHBoxLayout" name="horizontalLayout_2">
      <item>
       <spacer name="horizontalSpacer">
        <property name="orientation">
         <enum>Qt::Horizontal</enum>
        </property>
        <property name="sizeHint" stdset="0">
         <size>
          <width>40</width>
          <height>20</height>
         </size>
        </property>
       </spacer>
      </item>
      <item>
       <widget class="QPushButton" name="btnSend">
        <property name="text">
         <string>发送</string>
        </property>
       </widget>
      </item>
     </layout>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menuBar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>300</width>
     <height>23</height>
    </rect>
   </property>
  </widget>
  <widget class="QToolBar" name="mainToolBar">
   <attribute name="toolBarArea">
    <enum>TopToolBarArea</enum>
   </attribute>
   <attribute name="toolBarBreak">
    <bool>false</bool>
   </attribute>
  </widget>
  <widget class="QStatusBar" name="statusBar"/>
 </widget>
 <layoutdefault spacing="6" margin="11"/>
 <resources/>
 <connections/>
</ui>

总结

在使用QLocalServer和QLocalSocket的过程中,发现QLocalSocket不是数据通道的持有对象,而是数据通道本身(如共享内存是通过data获取共享内存的地址,而QLocalSocket是直接调用write写入,当然和他的继承有关系),而相对来说QLocalServer更像使用者。不过IPC相对于共享内存来说可能有及时性的特点,因为数据一来IPC就直接读取,而共享内存则是需要定时读取数据。

相关文章

Qt之进程通信-共享内存(含源码+注释)
Qt之进程通信-QProcess(含源码+注释)

友情提示——哪里看不懂可私哦,让我们一起互相进步吧
(创作不易,请留下一个免费的赞叭 谢谢 ^o^/)

注:文章为作者编程过程中所遇到的问题和总结,内容仅供参考,若有错误欢迎指出。
注:如有侵权,请联系作者删除

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,我可以为您提供一个使用QLocalServer和QLocalSocket进行进程通信的示例。 这是一个简单的例子,包括一个客户端和一个服务器进程。客户端将向服务器发送一条消息,服务器将接收到消息并将其显示在控制台上。以下是代码示例: server.cpp: ```cpp #include <QtNetwork/QLocalServer> #include <QtNetwork/QLocalSocket> #include <QtCore/QDebug> int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QLocalServer server; if (!server.listen("test")) { qWarning() << "Unable to start the server:" << server.errorString(); return 1; } qDebug() << "Server started, waiting for client connection..."; QLocalSocket *socket = server.nextPendingConnection(); if (!socket) { qWarning() << "Unable to accept incoming connection:" << server.errorString(); return 1; } qDebug() << "Client connected!"; socket->waitForReadyRead(); qDebug() << "Message received:" << socket->readAll(); return 0; } ``` client.cpp: ```cpp #include <QtNetwork/QLocalSocket> #include <QtCore/QDebug> int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QLocalSocket socket; socket.connectToServer("test"); if (!socket.waitForConnected()) { qWarning() << "Unable to connect to the server:" << socket.errorString(); return 1; } qDebug() << "Connected to server!"; socket.write("Hello, world!"); socket.flush(); return 0; } ``` 在这个例子中,服务器监听本地套接字“test”,并等待客户端连接。客户端连接到服务器并发送一条消息。服务器等待读取消息并将其显示在控制台上。 请注意,这只是一个简单的例子,并且没有进行错误处理或其他的安全措施。在实际的应用程序中,您需要考虑这些因素,并采取适当的措施来确保通信的安全性和正确性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lw向北.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值