小项目试验:QProcess进程通讯 使用标准输入/输出流

1.概要

需求:QProcess 启动的进程默认是子进程,父进程可通过其标准输入/输出流(stdin/stdout)与子进程通信,举一个完整的例子,使用c++,父进程直接打开子进程,可以使父子进程完成用不同的类执行,用一个程序,靠运行的输入参数区分父子进程。

代码要点梳理 :

//父进程
{
    // 启动子进程
    process.start(program, args);
    
    // 等待子进程响应(同步方式)
    if (!process.waitForReadyRead(3000)) {
        qCritical() << "Timeout waiting for child response!";
        break;
    }

    // 写入子进程标准输入
    process.write(msg.toUtf8() + "\n");
    
    //子进程
    {
	    QTextStream in(stdin);
    	QTextStream out(stdout);

 		QString line = in.readLine().trimmed();

	    out << response << "\n";
       	out.flush();  // 确保立即输出
	}
    

    // 读取子进程输出
    QByteArray response = process.readAllStandardOutput();
}

2.内容

以下是一个使用C++和Qt的QProcess实现父子进程通信的完整示例。程序通过命令行参数区分角色,父子进程通过标准输入输出交换数据:

#include <QCoreApplication>
#include <QProcess>
#include <QTextStream>
#include <QDebug>
#include <QFile>

// 子进程处理函数
void runChildProcess() {
    QTextStream in(stdin);
    QTextStream out(stdout);

    qDebug() << "[Child] Started with PID:" << QCoreApplication::applicationPid();

    while (!in.atEnd()) {
        QString line = in.readLine().trimmed();
        if (line.isEmpty()) continue;

        // 示例处理:转换为大写并添加PID前缀
        QString response = QString("[%1] Response: %2")
                               .arg(QCoreApplication::applicationPid())
                               .arg(line.toUpper());

        out << response << "\n";
        out.flush();  // 确保立即输出
    }
}

// 父进程处理函数
void runParentProcess() {
    QProcess process;
    QString program = QCoreApplication::applicationFilePath();
    QStringList args;
    args << "child_process";  // 任意非空参数标识子进程

    // 启动子进程
    process.start(program, args);
    if (!process.waitForStarted()) {
        qCritical() << "Failed to start child process!";
        return;
    }

    qDebug() << "[Parent] Started with PID:" << QCoreApplication::applicationPid();

    // 发送测试数据
    QStringList testMessages = {
        "hello from parent",
        "Qt IPC example",
        "12345"
    };

    for (const QString& msg : testMessages) {
        // 写入子进程标准输入
        process.write(msg.toUtf8() + "\n");

        // 等待子进程响应(同步方式)
        if (!process.waitForReadyRead(3000)) {
            qCritical() << "Timeout waiting for child response!";
            break;
        }

        // 读取子进程输出
        QByteArray response = process.readAllStandardOutput();
        qDebug() << "[" << QCoreApplication::applicationPid()
                 << "] Received:" << response.trimmed();
    }

    // 关闭输入并等待结束
    process.closeWriteChannel();
    process.waitForFinished();
}

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    if (argc > 1) {  // 有参数时作为子进程
        runChildProcess();
    } else {         // 无参数时作为父进程
        runParentProcess();
    }

    return 0;
}

使用说明:

  1. 保存为process_comm.cpp使用Qt的qmake编译:

    qmake -project
    qmake
    make
    运行方式:
    • 父进程模式:./process_comm --parent
    • 子进程模式:./process_comm --child

示例输出:

[Parent] Started with PID: 448426
[ 448426 ] Received: "[448427] Response: HELLO FROM PARENT"
[ 448426 ] Received: "[448427] Response: QT IPC EXAMPLE"
[ 448426 ] Received: "[448427] Response: 12345"

代码分析: 

关键点说明:

  1. 进程区分:通过命令行参数--parent--child区分角色
  2. 进程启动
    • 父进程使用QProcess::start()启动自身程序并附加--child参数
    • QCoreApplication::applicationFilePath()确保正确获取可执行文件路径
  3. 通信机制
    • 父进程通过process.write()发送数据
    • 子进程使用QTextStream读取标准输入
    • 双方通过标准输出交换数据
  4. 同步控制
    • 使用waitForReadyRead()实现同步通信
    • closeWriteChannel()通知子进程输入结束
  5. 错误处理
    • 检查进程启动状态
    • 设置超时机制防止死锁
    • 输出详细的调试信息

扩展建议:

  • 添加更复杂的通信协议(如JSON消息格式)
  • 实现异步通信(使用信号槽机制)
  • 添加错误恢复机制
  • 支持多线程处理多个子进程

这个示例演示了基本的进程间通信模式,可根据具体需求扩展通信协议和处理逻辑。

3.关联链接

4.管理知识

1.QProcess::start详解

QProcess::start() 是 Qt 框架中用于启动外部进程的核心方法,支持多种重载形式以适应不同场景。以下是详细解析:


函数原型

// 基础重载(程序名 + 参数列表)
bool QProcess::start(const QString &program, const QStringList &arguments = QStringList(), 
OpenMode mode = ReadWrite);


// 带工作目录的重载
bool QProcess::start(const QString &program, const QStringList &arguments, 
const QString &workingDirectory);


// 带环境变量的重载
bool QProcess::start(const QString &program, const QStringList &arguments, 
OpenMode mode, const QIODevice::OpenMode &ioMode);

参数详解

  1. program
    • 要启动的外部程序路径(绝对路径或可执行文件名)。
    • 跨平台注意:Windows 需包含 .exe 后缀(如 "notepad.exe"),Linux/macOS 可直接写命令(如 "ls")。
  2. arguments
    • 命令行参数列表,按顺序传递。
    • 示例:QStringList() << "-f" << "input.txt" 对应命令 program -f input.txt
  3. workingDirectory(可选)
    • 设置进程的工作目录,影响相对路径的解析。
  4. mode(可选)
    • 控制进程的输入/输出模式,默认为 ReadWrite(可读写)。
    • 其他选项:ReadOnly(只读)、WriteOnly(只写)、Unbuffered(禁用缓冲)。

执行模式

1. 异步执行(默认)
  • start() 立即返回,进程在后台运行。
  • 通过信号槽处理输出和结束事件:

    QProcess process;
    process.start("ping", {"-c", "4", "example.com"});
    
    
    connect(&process, &QProcess::readyReadStandardOutput, [&](){
        qDebug() << "输出:" << process.readAllStandardOutput();
    });
    
    
    connect(&process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
    [](int exitCode, QProcess::ExitStatus status){
        qDebug() << "进程结束,退出码:" << exitCode;
    });
2. 同步执行
  • 使用 waitForStarted() 阻塞当前线程,直到进程启动:

    if (process.start("ping", {"-c", "4", "example.com"}) && process.waitForStarted()) {
        process.waitForFinished(); // 等待进程结束
        QString output = process.readAllStandardOutput();
    }


返回值

  • 返回 bool 类型,表示进程是否成功启动。
  • 失败时可通过 errorString() 获取原因:

    if (!process.start("non_existent_program")) {
        qDebug() << "启动失败:" << process.errorString();
    }

常见用例

1. 启动带参数的程序

QProcess process;
process.start("ffmpeg", {"-i", "input.mp4", "-c:v", "libx264", "output.mp4"});
2. 指定工作目录
process.start("python", {"script.py"}, "/path/to/working/dir");
3. 传递环境变量

QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
env.insert("CUSTOM_VAR", "value");
process.setProcessEnvironment(env);
process.start("my_app");

注意事项

  1. 路径问题
    • 优先使用绝对路径,或确保程序在系统 PATH 中。
    • Windows 示例:C:\\Windows\\System32\\notepad.exe(注意转义反斜杠)。
  2. 阻塞风险
    • 避免在 GUI 主线程中使用同步模式(waitFor*),否则会导致界面卡顿。
  3. 输出处理
    • 异步模式下需及时读取输出,或通过信号实时处理,防止缓冲区溢出。
  4. 跨平台差异
    • 命令行参数格式可能不同(如 Windows 用 / 而 Linux/macOS 用 -)。

高级技巧

  • 重定向输入/输出

    process.setStandardInputFile("input.txt");
    process.setStandardOutputFile("output.txt");

  • 后台进程(不关联父进程)

    QProcess::startDetached("program", arguments, workingDirectory, &pid);

通过合理使用 QProcess::start(),可以灵活集成外部工具到 Qt 应用中,实现文件操作、网络诊断、自动化测试等复杂功能。

2.QProcess 管道 通讯,只能是父子进程间通讯吗

QProcess 的管道通信不局限于父子进程间,但需结合操作系统特性或设计模式实现。以下是详细解析:


1. QProcess 默认行为

QProcess 启动的进程默认是子进程,父进程可通过其标准输入/输出流(stdin/stdout)与子进程通信。例如:

QProcess processA;
processA.start("programA"); // 启动子进程A


QProcess processB;
processB.start("programB"); // 启动子进程B


// 父进程将A的输出转发给B的输入
connect(&processA, &QProcess::readyReadStandardOutput, [&]() {
    QByteArray data = processA.readAllStandardOutput();
    processB.write(data); // 手动传递数据
});

2. 非父子进程间管道通信

若需让两个独立进程(非父子)直接通过管道通信,需借助操作系统特性:

方案 1:命名管道(Named Pipe/FIFO)
  • 步骤
    1. 创建命名管道(如 /tmp/my_pipe)。
    2. 进程A向管道写入数据,进程B从管道读取。
  • 示例代码

    // 进程A(写入端)
    QProcess processA;
    processA.start("programA", {"--output-pipe", "/tmp/my_pipe"});
    
    
    // 进程B(读取端)
    QProcess processB;
    processB.start("programB", {"--input-pipe", "/tmp/my_pipe"});

方案 2:Shell 管道符(仅限命令行启动)
  • 通过系统 Shell 的管道符 | 直接连接两个进程:

    QProcess process;
    process.start("programA | programB"); // Linux/macOS 示例
    // Windows 需使用 cmd /C "programA | programB"

3. QProcess 的局限性

  • 无法直接连接子进程管道:QProcess 不提供内置方法将两个子进程的管道直接连接(如 programA | programB 需通过系统 Shell 实现)。
  • 需手动中转数据:若需在 Qt 代码中实现子进程间通信,需通过父进程读取一个进程的输出并写入另一个进程的输入(如示例1)。

4. 推荐方案

场景方案优点缺点
简单子进程间通信父进程手动中转数据兼容所有平台需编写中转逻辑
跨平台独立进程通信命名管道 + 第三方库(如 Boost)完全解耦需处理同步和错误
命令行工具链式调用Shell 管道符代码简洁仅限命令行启动

总结

  • 父子进程间:QProcess 可直接通过 stdin/stdout 通信。
  • 非父子进程间:需借助命名管道、套接字或系统 Shell 管道符,QProcess 不直接支持但可配合使用。
  • 最佳实践:优先通过父进程中转数据实现简单通信,复杂场景建议使用专用 IPC 机制(如 ZeroMQ、共享内存)。

3.QProcess 管道可以进行二次通讯 

QProcess 的管道通信支持多次双向交互,并非仅限单次消息传递。以下是详细说明:


1. QProcess 管道通信原理

  • 标准流重定向:QProcess 默认将子进程的 stdin/stdout/stderr 映射到父进程的管道。
  • 双向通信:父进程可随时通过 write() 向子进程发送数据,子进程也可通过 stdout 持续输出结果。

2. 实现多次通信的示例

父进程代码

QProcess process;
process.start("child_app"); // 启动子进程

// 发送第一条消息
process.write("Hello from Parent!\n"); // 注意添加换行符(若子进程按行读取)
process.flush(); // 确保数据立即发送

// 读取子进程响应
if (process.waitForReadyRead()) {
    QString response = process.readAllStandardOutput();
    qDebug() << "子进程回复:" << response;
}

// 发送第二条消息
process.write("Another message!\n");
process.flush();

// 再次读取响应
if (process.waitForReadyRead()) {
    QString response2 = process.readAllStandardOutput();
    qDebug() << "子进程二次回复:" << response2;
}

process.close(); // 关闭进程
子进程代码(child_app)

#include <QCoreApplication>
#include <QTextStream>

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);
    QTextStream in(stdin);
    QTextStream out(stdout);

    while (!in.atEnd()) {
        QString line = in.readLine(); // 按行读取父进程输入
        out << "收到: " << line << "\n"; // 回复父进程
        out.flush(); // 确保立即发送
    }

    return a.exec();
}

3. 关键点

  1. 持续读写
    • 父进程可多次调用 write() 发送数据,子进程通过循环(如 while (!in.atEnd()))持续接收。
    • 子进程的每次输出都会触发父进程的 readyReadStandardOutput 信号(若使用异步模式)。
  2. 缓冲区管理
    • 使用 flush() 确保数据立即发送,避免因缓冲区未满导致延迟。
    • 父进程需及时读取子进程输出,防止阻塞子进程(尤其在使用 waitForReadyRead 时)。
  3. 协议设计
    • 定义明确的消息边界(如换行符 \n 或固定长度),避免数据粘连。
    • 可使用 QDataStream 传输结构化数据(如 JSON、二进制协议)。

4. 异步模式优化

父进程(异步)

QProcess process;
process.start("child_app");

// 连接子进程输出信号
connect(&process, &QProcess::readyReadStandardOutput, [&]() {
    QString response = process.readAllStandardOutput();
    qDebug() << "子进程回复:" << response;
});

// 发送消息(非阻塞)
process.write("Async message 1\n");
process.flush();

// 稍后再次发送
QTimer::singleShot(1000, [&]() {
    process.write("Async message 2\n");
    process.flush();
});
子进程(同上)

5. 注意事项

  • 进程生命周期:子进程退出后,管道自动关闭,无法继续通信。
  • 错误处理:检查 QProcess::error() 和 QProcess::exitStatus() 处理异常。
  • 跨平台行为:Windows 下子进程可能继承父进程的控制台,需测试输出行为。

总结

QProcess 管道通信完全支持多次双向交互,关键在于:

  1. 子进程设计为持续监听输入(如循环读取)。
  2. 父进程及时读写数据,避免缓冲区阻塞。
  3. 定义清晰的消息协议确保数据完整性。

此机制适用于命令行工具集成、实时日志处理等场景,性能优于本地套接字(因无系统调用开销)。

4.标准输入输出流的宏,Windows 平台的 C/C++ 开发环境

#ifndef _STDSTREAM_DEFINED
#define stdin (__acrt_iob_func(0))
#define stdout (__acrt_iob_func(1))
#define stderr (__acrt_iob_func(2))
#define _STDSTREAM_DEFINED
#endif  

这段代码是用于在 Microsoft Visual C++ 运行时库(CRT)中定义标准输入输出流的宏,常见于 Windows 平台的 C/C++ 开发环境。以下是逐行解释:

  1. #ifndef _STDSTREAM_DEFINED
    • 条件编译指令,检查 _STDSTREAM_DEFINED 宏是否未定义。如果未定义,则处理后续代码块,防止重复包含。
  2. #define stdin (__acrt_iob_func(0))
    • 定义 stdin 宏,指向标准输入流。__acrt_iob_func 是微软 CRT 的内部函数,参数 0 表示标准输入(stdin)。
  3. #define stdout (__acrt_iob_func(1))
    • 定义 stdout 宏,指向标准输出流。参数 1 表示标准输出(stdout)。
  4. #define stderr (__acrt_iob_func(2))
    • 定义 stderr 宏,指向标准错误流。参数 2 表示标准错误(stderr)。
  5. #define _STDSTREAM_DEFINED
    • 标记标准流已定义,防止后续重复定义。
  6. #endif
    • 结束条件编译块。

作用说明:

  • 兼容性处理:微软的 CRT 在某些情况下(如宽字符模式或特定编译选项)需要特殊处理标准流,通过 __acrt_iob_func 函数获取对应的 FILE* 指针。
  • 隐藏实现细节:将底层实现封装在宏中,使代码对用户透明。
  • 防止重复定义:通过 _STDSTREAM_DEFINED 宏确保这些定义只生效一次。

典型使用场景:

当需要直接操作 stdin/stdout/stderr 的底层实现时(如重定向流、调试或特定 I/O 操作),这段代码提供了跨编译模式的统一访问方式。

5.C语言标准库中关于标准输入输出流

/* Standard streams.  */
extern FILE *stdin;		/* Standard input stream.  */
extern FILE *stdout;		/* Standard output stream.  */
extern FILE *stderr;		/* Standard error output stream.  */
/* C89/C99 say they're macros.  Make them happy.  */
#define stdin stdin
#define stdout stdout
#define stderr stderr

这段代码是C语言标准库中关于标准输入输出流的典型实现,主要目的是同时满足标准规范要求和实际工程需求。以下是分步解释:

  1. 标准流声明

extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;
  • 声明三个全局FILE指针变量,分别对应:
    • stdin:标准输入流(默认关联键盘)
    • stdout:标准输出流(默认关联控制台)
    • stderr:标准错误流(默认关联控制台,允许无缓冲输出)
  • extern关键字表示这些变量在其他源文件中定义(如C标准库实现文件)
  1. 标准兼容性宏定义

#define stdin stdin
#define stdout stdout
#define stderr stderr
  • 看似冗余的宏定义,实为满足C89/C99标准要求:
    • 标准规定这三个符号必须作为宏存在
    • 同时允许它们也可以作为变量存在
    • 这种"自引用宏"实现方式既保持标准兼容,又不影响实际变量访问
  1. 设计意图解析
  • 双模式访问:允许通过两种方式访问标准流:

    // 通过变量直接操作
    fprintf(stdout, "Hello");
    
    
    // 通过宏(可能被重定向)
    #define stdout my_log_file
    fprintf(stdout, "Debug");

  • 底层实现分离
    • 实际实现中,这些变量可能通过系统调用(如Unix的fileno(0/1/2))关联标准文件描述符
    • 宏定义留有扩展空间,例如某些实现可能通过宏实现线程局部存储或重定向功能
  1. 典型应用场景
  • 标准I/O操作:printf()/scanf()等函数底层通过这些流进行输入输出
  • 流重定向:通过修改宏定义实现运行时重定向(如将stdout重定向到文件)
  • 跨平台兼容:不同操作系统(Windows/Linux/macOS)通过统一接口抽象底层差异
  1. 与MSVC实现的对比
  • 之前示例的Microsoft实现通过__acrt_iob_func函数动态获取流指针,而本示例采用静态声明
  • 两种方式都是标准允许的实现方式,区别在于:
    • MSVC方式更灵活(支持运行时配置)
    • 本示例方式更直接(适合静态链接场景)

这种实现模式是C标准库设计的经典范例,通过巧妙的宏定义在保持标准兼容性的同时,提供了实际可用的变量接口,兼顾了规范要求和工程实用性。

6.stdin和stdout是什么 

stdin 和 stdout 是计算机编程中的两个核心概念,属于标准输入输出流的范畴。以下是分步解释:


1. 定义与作用

  • stdin(Standard Input)
    • 标准输入流,默认关联键盘输入。
    • 程序通过它读取用户输入的数据(例如:scanf()fgets() 等函数)。
  • stdout(Standard Output)
    • 标准输出流,默认关联屏幕显示。
    • 程序通过它输出结果(例如:printf()puts() 等函数)。
  • stderr(Standard Error)
    • 标准错误流,默认也关联屏幕,但用于输出错误信息(可与 stdout 分离重定向)。

2. 是否所有应用程序都有?

不是所有应用程序都直接使用它们,但绝大多数命令行工具和终端程序会依赖这些流。具体取决于程序类型和运行环境:

情况 1:命令行程序(如 lsgrep, Python 脚本等)
  • 必然使用:这些程序默认通过终端交互,stdin/stdout 是它们与用户沟通的唯一通道。
  • 示例

    $ echo "Hello" | ./my_program # 将 "Hello" 通过 stdin 输入给程序
    $ ./my_program > output.txt # 将 stdout 重定向到文件
情况 2:图形界面程序(如记事本、浏览器等)
  • 通常不直接使用:它们通过窗口、按钮等 GUI 元素交互,而非终端输入输出。
  • 例外:某些 GUI 程序可能通过子进程调用命令行工具,间接使用 stdin/stdout
情况 3:后台服务/守护进程(Daemon)
  • 可能无关联:如果程序完全脱离终端运行(如 Web 服务器),stdin/stdout 可能被重定向到文件或关闭。
情况 4:嵌入式系统或无操作系统环境
  • 不存在:在资源受限的环境中,可能没有标准库支持,因此无 stdin/stdout 概念。

3. 跨平台差异

  • 类 Unix 系统(Linux/macOS)
    所有进程启动时默认打开 stdin(文件描述符 0)、stdout(1)、stderr(2),即使无终端(如通过 cron 运行的任务)。

  • Windows
    CMD/PowerShell 中的程序类似 Unix,但某些 GUI 应用可能不继承控制台流(需通过 AllocConsole() 显式创建)。


4. 编程语言中的表现

  • C/C++:直接操作 FILE* 指针(如 stdin/stdout 宏)。
  • Python:通过 sys.stdin/sys.stdout 对象访问。
  • Java:通过 System.in/System.out 对象访问。

总结

  • stdin/stdout 是标准 I/O 的抽象接口,为程序提供统一的输入输出方式。
  • 绝大多数命令行工具和终端程序依赖它们,但图形界面、后台服务或嵌入式程序可能不直接使用。
  • 它们的存在性取决于程序类型、运行环境和操作系统支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值