无法使用 C++ 中的 Boost 库通过管道发送和执行正确的命令

Cannot send and execute correct command through pipes using Boost library in C++

使用问题中的答案:,

我重构了代码并使用 Boost 库混合了新方法。我已经成功地与 Stockfish 建立了管道连接,但这也是我遇到以前从未见过的错误的地方,甚至 Google 也无济于事。

这是我尝试过的:

#include <stdio.h>
#include <time.h>
#include <string>
#include <memory.h>
#include <unistd.h>
#include <iostream>
#include <stddef.h>
#include <execinfo.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fstream>
#include </usr/local/include/backtrace.h>
#include </usr/local/include/backtrace-supported.h>
#include <boost/process.hpp>
#include <boost/asio.hpp>
#include <boost/process/async.hpp>
#include <vector>
#include <iomanip>
#include <stdlib.h>
#include <string.h>

using namespace std;
namespace bp = boost::process;
using boost::system::error_code;
using namespace std::chrono_literals;

string errDetails = "Error Details: ";

void delay(int number_of_seconds) {
    int ms = 1000 * number_of_seconds;
    clock_t start_time = clock();

    while (clock() < start_time + ms) 
        ;
}

static void full_write(int fd, const char* buf, size_t len) {
    while (len > 0) {
        ssize_t ret = write(fd, buf, len);

        if ((ret == -1) && (errno != EINTR)) {
            break;
        }

        buf += (size_t) ret;
        len -= (size_t) ret;
    }
}

void print_backtrace() {
    static const char start[] = "--------BACKTRACE--------\n\n";
    static const char end[] = "-------------------------\n\n";

    void *bt[1024];
    int bt_size;
    char **bt_syms;
    int i;

    bt_size = backtrace(bt, 1024);
    bt_syms = backtrace_symbols(bt, bt_size);
    full_write(STDERR_FILENO, start, strlen(start));
    full_write(STDERR_FILENO, errDetails.c_str(), strlen(errDetails.c_str()));

    for (i = 1; i < bt_size; i++) {
        size_t len = strlen(bt_syms[i]);
        full_write(STDERR_FILENO, bt_syms[i], len);
        full_write(STDERR_FILENO, "\n", 1);
    }

    full_write(STDERR_FILENO, end, strlen(end));

    free(bt_syms); 
}

void abort_application() {
    size_t memLeakCount, staticMemLeakCount;
    uint64_t memLeakSize, staticMemLeakSize;

    for (int i = 0; i < 3; i++) {
        /**
         * Delay
         */
        delay(1);
    }

    print_backtrace();

    abort();
}

inline bool stockfish_check_exists(const std::string& name) {
    struct stat buffer;
    return (stat(name.c_str(), &buffer) == 0);
}

int main() {
    std::future<std::string> data;
    boost::asio::io_service svc;
    bp::async_pipe in{svc}, out{svc};
    string proc = "";
    char command[64];
    string output = "";

    if (stockfish_check_exists("stockfish")) {
        proc = "stockfish"; } else {
        errDetails = "Stockfish not found!\n\n";

        abort_application();
    }

    std::string const program_dir = proc;

    auto on_exit = [](int code, std::error_code ec) {
        std::cout << "Exited " << code << "(" << ec.message() << ")\n";
    };

    bp::child process(proc, bp::std_in < in, svc);

    boost::asio::streambuf recv_buffer;

    std::cout << "uci send" << std::endl;
    boost::asio::async_write(in, boost::asio::buffer("uci\n"),
        [&](boost::system::error_code ec, size_t transferred) {
            std::cout << "Write: " << transferred << "\n" << std::endl;
            in.close();
        }
    );

    std::cout << "isready send" << std::endl;
    boost::asio::async_write(in, boost::asio::buffer("isready\n"),
        [&](boost::system::error_code ec, size_t transferred) {
            std::cout << "Write: " << transferred << "\n" << std::endl;
            in.close();
        }
    );

    cout << "Enter your command: ";
    cin >> command;
    cout << "Your command is: " << command << endl;

    if (strcmp(command, "quit") == 0) {
        cout << "Quiting......." << endl;
        boost::asio::async_write(in, boost::asio::buffer("quit"),
            [&](boost::system::error_code ec, size_t transferred) {
                std::cout << "Write: " << transferred << std::endl;
                in.close();

                cout << "Engine quit!" << endl;
            }
        );
    }

    svc.run();

    return 0;
}

为了便于理解,我在行中省略了 std::std_out > out

bp::child process(proc, bp::std_in < in, svc); 

以便引擎结果立即显示在终端中 window,这样我就知道我是否误入歧途了。这就是我发现奇怪的事情的时候

当我启动应用程序时,它在终端上输出如下:

[2022-01-14 20:25:55]
duythanh@DuyThanhs-MacBook-Pro:/Volumes/Data/ChessGUI$ ./ChessGUI
uci send
isready send
Enter your command: Stockfish 120122 by the Stockfish developers (see AUTHORS file)
id name Stockfish 120122
id author the Stockfish developers (see AUTHORS file)

option name Debug Log File type string default 
option name Threads type spin default 1 min 1 max 512
option name Hash type spin default 16 min 1 max 33554432
option name Clear Hash type button
option name Ponder type check default false
option name MultiPV type spin default 1 min 1 max 500
option name Skill Level type spin default 20 min 0 max 20
option name Move Overhead type spin default 10 min 0 max 5000
option name Slow Mover type spin default 100 min 10 max 1000
option name nodestime type spin default 0 min 0 max 10000
option name UCI_Chess960 type check default false
option name UCI_AnalyseMode type check default false
option name UCI_LimitStrength type check default false
option name UCI_Elo type spin default 1350 min 1350 max 2850
option name UCI_ShowWDL type check default false
option name SyzygyPath type string default <empty>
option name SyzygyProbeDepth type spin default 1 min 1 max 100
option name Syzygy50MoveRule type check default true
option name SyzygyProbeLimit type spin default 7 min 0 max 7
option name Use NNUE type check default true
option name EvalFile type string default nn-ac07bd334b62.nnue
uciok
Unknown command: isready

对比上面的代码,这两个命令是通过管道发送的。就是uciisready,这样就好了。第一个 uci 命令成功运行,但是 isready 命令没有返回 readyok,而是 returns:

Unknown command: isready

我一直在尝试键入 quit,它向作为出口引擎的管道发送了一个 quit 命令,但它也失败了:

Your command is: quit
Quiting.......
Write: 5

Write: 9

Unknown command: quit
Write: 5
Engine quit!

然后程序将与引擎一起退出。我还在想当时是怎么回事,但是幕后发生的事情线索真的很模糊。

请帮帮我。非常感谢任何帮助。非常感谢大家

更新:出现Unknown Command: Quit时错误继续。我在终端中输入这些命令,而 运行 Stockfish 直接通过终端输入,结果它们起作用了,但我的程序仍然不能

您正在打印到 cout,就好像异步操作会立即发生一样。事实并非如此。异步操作仅在 io 服务运行时发生。

svc.run();

在您的代码的最后。所以在此之前没有 async_ 操作完成(甚至开始)。

其他问题:

  1. 您的 out 异步管道从未使用过(甚至未连接)。我不清楚您打算如何以这种方式与 child 进程进行通信。

  2. 公平地说,您每次都只写入 child 进程,所以您可能对输出根本不感兴趣。 (但也许 recv_buffer 也可以删除)。

  3. 您的缓冲区包含终止 NUL 字符。 (asio::buffer("uci\n") 发送 {'u','c','i','\n','[=19=]'})。这会搞乱 child 进程的解析。

  4. 你做 in.close() 来回应 每一个 async_write 完成。这保证了后续写入永远不会发生,因为您关闭了管道。

  5. 然后当你发送quit时你也没有包含'\n'

  6. 您正在阅读带有 operator>>char[64],这根本没有任何意义。也许您正在使用 c++20(因此可能假定宽度为 64)但您从未设置宽度。您很可能想读入一个字符串。

  7. 但是,这样做不能接受带空格的命令(因为默认设置了std::ios::skipws)。所以,您可能想要 std::getline 而不是...

  8. 事实上你包含了大量的 C headers 让我觉得你正在移植一些 C 代码(很糟糕)。 strcmp 的使用和其他例子也证明了这一点,例如无需使用 ::stat

  9. 不要使用 using namespace std; (Why is "using namespace std;" considered bad practice?)

  10. 不要使用全局变量(errDetails)

  11. 不要使用循环等待时间延迟

  12. 无需手动打印回溯。相反,使用升压:

    void abort_application(std::string const& errDetails) {
        std::cerr << errDetails << "\n";
        std::cerr << boost::stacktrace::stacktrace{} << std::endl;
        std::this_thread::sleep_for(3s);
        abort();
    }
    

现有 Stockfish 客户端:玩游戏

你很幸运:我在这个网站上有一个使用鳕鱼的书面完整演示:Interfacing with executable using boost in c++

此示例说明如何正确等待和解析来自 child 个进程的预期回复。

你会注意到我为异步版本选择了协程:

Just for completeness, I thought I'd try an asynchronous implementation. Using the default Asio callback style this could become unwieldy, so I thought to use Boost Coroutine for the stackful coroutines. That makes it so the implementation can be 99% similar to the synchronous version

仅供比较,如果您不使用协程,您的代码应该如下所示:

修复你的代码

Live On Coliru

#include <boost/asio.hpp>
#include <boost/process.hpp>
#include <boost/process/async.hpp>
#include <boost/stacktrace/stacktrace.hpp>
#include <chrono>
#include <iomanip>
#include <iostream>

namespace bp = boost::process;
using boost::system::error_code;
using namespace std::literals;

static void abort_application(std::string const& errDetails) {
    std::cerr << errDetails << "\n";
    std::cerr << boost::stacktrace::stacktrace{} << std::endl;
    std::this_thread::sleep_for(3s);
    abort();
}

inline static bool stockfish_check_exists(std::string& name) {
    return boost::filesystem::exists(name);
}

int main() {
    boost::asio::io_service  svc;
    bp::async_pipe           in{svc};
    std::string              proc = "/usr/games/stockfish";

    if (!stockfish_check_exists(proc)) {
        abort_application("Stockfish not found!");
    }

    auto on_exit = [](int code, std::error_code ec) {
        std::cout << "Exited " << code << "(" << ec.message() << ")\n";
    };

    bp::child process(proc, bp::std_in < in, svc, bp::on_exit = on_exit);

    std::function<void()> command_loop;
    std::string           command_buffer;
    command_loop = [&] {
        std::cout << "Enter your command: " << std::flush;
        // boost::asio::streambuf recv_buffer;
        if (getline(std::cin, command_buffer)) {
            std::cout << "Your command is: " << command_buffer << std::endl;
            command_buffer += '\n';

            async_write( //
                in, boost::asio::buffer(command_buffer),
                [&](error_code ec, size_t transferred) {
                    std::cout << "Write: " << transferred << " (" << ec.message() << ")" << std::endl;

                    if (command_buffer == "quit\n") {
                        std::cout << "Quiting......." << std::endl;
                        // in.close();
                        std::cout << "Engine quit!" << std::endl;
                    } else {
                        command_loop(); // loop
                    }
                });
        }
    };

    std::cout << "uci send" << std::endl;
    async_write(
        in, boost::asio::buffer("uci\n"sv),
        [&](error_code ec, size_t transferred) {
            std::cout << "Write: " << transferred << "\n" << std::endl;
            std::cout << "isready send" << std::endl;
            async_write(in, boost::asio::buffer("isready\n"sv),
                        [&](error_code ec, size_t n) {
                            std::cout << "Write: " << n << std::endl;
                            command_loop(); // start command loop
                        });
        });

    svc.run(); // only here any of the operations start
}

打印,例如

或者如果 Stockfish 实际上已安装: