You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++中以子进程运行Stockfish引擎的问题排查

Stockfish子进程交互问题排查

尝试在C++中启动Stockfish国际象棋引擎作为子进程,通过管道传递UCI命令获取最佳走法,但运行代码后Stockfish仅启动无后续响应,手动在CLI输入UCI命令可正常工作,代码如下:

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

void sig_handler(int sig) {}

int main() {
    int fd1[2];
    if (pipe(fd1) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }    

    int pid = fork();
    if (pid == 0) {
        execl("./stockfish", "./stockfish", nullptr);
        close(fd1[1]);
        char position[60];
        read(fd1[0], position, 60);
        cout << "position " << position << endl;
        cout << "go" << endl;
        sleep(5000);
        cout << "stop" << endl;
        string move;
        cin >> move;
        exit(0);
    }

    else {
        close(fd1[0]);
        cout << "Parent process" << endl;
        string line = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
        write(fd1[1], line.c_str(), strlen(line.c_str()) + 1);
        close(fd1[1]);

        signal(SIGINT, &sig_handler);
        wait(NULL);
        cout << "Parent done" << endl;
    }
    return 0;
}

核心问题分析

  • 子进程逻辑完全失效:execl函数执行后会直接替换当前子进程的代码段和内存空间,其后的所有代码(包括read、cout输出命令等)都不会被执行。你试图让子进程同时启动Stockfish和处理管道,但Stockfish启动后原有的子进程代码已经被覆盖,根本无法执行后续的命令发送逻辑。

  • 管道未关联Stockfish的标准IO:当前创建的管道仅存在于父进程和未启动Stockfish的子进程之间,Stockfish启动后,它的标准输入(stdin)和标准输出(stdout)仍然绑定到终端,而非你创建的管道,因此无法接收父进程通过管道发送的命令。

修正后的代码示例

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstring>

using namespace std;

int main() {
    int parent_to_child[2]; // 父进程写 → Stockfish的stdin
    int child_to_parent[2]; // Stockfish的stdout → 父进程读

    if (pipe(parent_to_child) == -1 || pipe(child_to_parent) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }    

    int pid = fork();
    if (pid == 0) {
        // 子进程:重定向标准IO到管道,再启动Stockfish
        close(parent_to_child[1]); // 关闭子进程不需要的写端
        close(child_to_parent[0]); // 关闭子进程不需要的读端

        // 将管道读端替换为Stockfish的stdin
        dup2(parent_to_child[0], STDIN_FILENO);
        // 将管道写端替换为Stockfish的stdout
        dup2(child_to_parent[1], STDOUT_FILENO);

        // 关闭原管道描述符(已通过dup2复制)
        close(parent_to_child[0]);
        close(child_to_parent[1]);

        // 启动Stockfish,失败则报错
        execl("./stockfish", "./stockfish", nullptr);
        perror("execl failed to start stockfish");
        exit(EXIT_FAILURE);
    } else {
        // 父进程:关闭不需要的管道端
        close(parent_to_child[0]);
        close(child_to_parent[1]);

        cout << "Parent process initializing Stockfish..." << endl;

        // 发送UCI初始化命令
        const char* uci_cmd = "uci\n";
        write(parent_to_child[1], uci_cmd, strlen(uci_cmd));

        // 等待Stockfish初始化完成(直到收到uciok)
        char buffer[1024];
        ssize_t bytes_read;
        bool uci_initialized = false;
        while (!uci_initialized && (bytes_read = read(child_to_parent[0], buffer, sizeof(buffer)-1)) > 0) {
            buffer[bytes_read] = '\0';
            cout << "Stockfish: " << buffer;
            if (strstr(buffer, "uciok") != nullptr) {
                uci_initialized = true;
            }
        }

        // 发送初始局面命令
        string position_cmd = "position startpos\n";
        write(parent_to_child[1], position_cmd.c_str(), position_cmd.size());

        // 命令Stockfish计算5秒最佳走法
        const char* go_cmd = "go movetime 5000\n";
        write(parent_to_child[1], go_cmd, strlen(go_cmd));

        // 读取输出直到获取bestmove
        bool got_bestmove = false;
        while (!got_bestmove && (bytes_read = read(child_to_parent[0], buffer, sizeof(buffer)-1)) > 0) {
            buffer[bytes_read] = '\0';
            cout << "Stockfish: " << buffer;
            char* bestmove = strstr(buffer, "bestmove");
            if (bestmove != nullptr) {
                cout << "\n=== 最佳走法: " << bestmove << endl;
                got_bestmove = true;
            }
        }

        // 发送退出命令关闭Stockfish
        const char* quit_cmd = "quit\n";
        write(parent_to_child[1], quit_cmd, strlen(quit_cmd));

        // 等待子进程退出
        wait(NULL);
        cout << "Parent done" << endl;

        // 关闭剩余管道端
        close(parent_to_child[1]);
        close(child_to_parent[0]);
    }
    return 0;
}

关键修正点

  • 双管道设计:创建两个管道分别处理命令输入和结果输出,避免单管道的读写冲突。
  • 标准IO重定向:在execl前用dup2将Stockfish的stdin/stdout绑定到管道,确保Stockfish能接收父进程的命令并返回结果。
  • 遵循UCI协议流程:先发送uci命令初始化Stockfish,等待uciok后再发送后续命令;用movetime参数控制计算时间,替代sleep+stop的非标准方式。
  • 结果捕获逻辑:循环读取Stockfish的输出,直到捕获到bestmove字段,确保正确获取计算结果。

内容的提问来源于stack exchange,提问作者ImanityDev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 21:41:11