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
相关产品推荐
相关产品推荐

