简体   繁体   English

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

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

Use the answer in the question: simultaneous read and write to child's stdio using boost.process ,使用问题中的答案: 使用 boost.process 同时读取和写入孩子的标准输入输出

I refactored the code and hybridized the new method using the Boost library.我重构了代码并使用 Boost 库混合了新方法。 I've been successful in making a pipes connection with Stockfish, but this is also where I get errors I've never seen before, not even Google helps.我已经成功地与 Stockfish 建立了管道连接,但这也是我遇到以前从未见过的错误的地方,甚至谷歌也没有帮助。

Here is what I have tried:这是我尝试过的:

#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;
}

To make it easier to follow, I left out std::std_out > out at the line:为了更容易理解,我在该行省略了std::std_out > out

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

so that the engine results are immediately displayed in the Terminal window, so I'll know if I've gone astray.以便引擎结果立即显示在终端 window 中,所以我会知道我是否误入歧途。 And this is when I discovered the strange thing这就是我发现奇怪的事情的时候

When I launch the application, it outputs on Terminal as follows:当我启动应用程序时,它在终端上输出如下:

[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

Contrasting with the code above, the two commands were sent through pipes.与上面的代码相比,这两个命令是通过管道发送的。 is uci and isready , this is fine.uciisready ,这很好。 The first uci command runs successfully, but the isready command, instead of returning readyok , it returns:第一个uci命令运行成功,但是isready命令没有返回readyok ,而是返回:

Unknown command: isready

I keep trying to type quit , which sends a quit command to the pipe as the exit engine, and it also fails:我一直在尝试输入quit ,它会向 pipe 作为退出引擎发送quit命令,但它也失败了:

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

Write: 9

Unknown command: quit
Write: 5
Engine quit!

The program will then exit with the engine.然后程序将与引擎一起退出。 I'm still wondering what was going on at the time, but the clues are really hazy as to what was going on behind the scenes.我仍然想知道当时发生了什么,但对于幕后发生的事情,线索真的很模糊。

Please help me.请帮我。 Any help is highly appreciated.非常感谢任何帮助。 Thank you so much everyone谢谢大家!谢谢

UPDATE : The error continued when Unknown Command: Quit appeared.更新:出现Unknown Command: Quit时错误继续。 I typed these commands in Terminal while running Stockfish directly through Terminal, they work as a result, but my program still can't我在终端中直接通过终端运行 Stockfish 时输入了这些命令,结果它们可以工作,但我的程序仍然不能

You are printing to cout as if the async operations happen immediately.您正在打印到cout ,就好像异步操作立即发生一样。 That's not the case.事实并非如此。 The async operations only happen when the io service runs.异步操作仅在 io 服务运行时发生。

svc.run();

Is at the very end of your code.位于代码的最后。 So no async_ operation ever completes (or even starts) before that.所以在此之前没有async_操作完成(甚至开始)。

Other problems:其他问题:

  1. Your out async pipe is never used (not even connected).out异步 pipe 从未使用过(甚至未连接)。 It's unclear to me how you intend to communicate with the child process that way.我不清楚您打算如何以这种方式与子进程进行通信。

  2. In fairness, you only every write to the child process, so maybe you're not at all interested in the output.公平地说,您只对子进程进行每次写入,因此您可能对 output 根本不感兴趣。 (But then perhaps recv_buffer can be deleted just as well). (但也许recv_buffer也可以被删除)。

  3. Your buffers include the terminating NUL characters.您的缓冲区包括终止NUL字符。 ( asio::buffer("uci\n") sends {'u','c','i','\n','\0'} ). asio::buffer("uci\n")发送{'u','c','i','\n','\0'} )。 That's going to mess up the child processes's parsing.这会打乱子进程的解析。

  4. You do in.close() in response to every single async_write completion.您执行in.close()以响应每个async_write完成。 This guarantees that subsequent writes never can happen, as you closed the pipe.这保证了后续写入永远不会发生,因为您关闭了 pipe。

  5. Then when you send quit you fail to include the '\n' as well然后,当您发送quit时,您也无法包含 '\n'

  6. You are reading into a char[64] with operator>> which makes no sense at all.您正在使用operator>>读取char[64] ,这根本没有意义。 Maybe you are using c++20 (so width of 64 might be assumed) but you never set a width.也许您使用的是 c++20(因此可能假定宽度为 64),但您从未设置宽度。 Most likely you would want to read into a string instead.您很可能希望改为读入字符串。

  7. However, doing so cannot accept commands with whitespace (because std::ios::skipws is set by default).但是,这样做不能接受带有空格的命令(因为std::ios::skipws是默认设置的)。 So, likely you wanted std::getline instead...所以,可能你想要std::getline代替......

  8. The fact that you include a boatload of C headers makes me think you're porting some C code (badly).您包含大量 C 标头的事实让我认为您正在移植一些 C 代码(非常糟糕)。 That's also exemplified by the strcmp use and others, eg no need to use ::stat strcmp的使用和其他也体现了这一点,例如不需要使用::stat

  9. Don't use using namespace std;不要使用using namespace std; ( Why is "using namespace std;" considered bad practice? ) 为什么“使用命名空间标准;”被认为是不好的做法?

  10. Don't use global variables ( errDetails )不要使用全局变量( errDetails

  11. Don't use loops to wait for a time delay不要使用循环来等待时间延迟

  12. No need to manually print backtraces.无需手动打印回溯。 Instead, use Boost:相反,使用 Boost:

     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(); }

Existing Stockfish Client: Playing Games现有 Stockfish 客户端:玩游戏

You're in luck: I have a written full demo using stockfish on this site: Interfacing with executable using boost in c++ .你很幸运:我在这个网站上有一个使用 stockfish 的书面完整演示: Interfacing with executable using boost in c++

This example shows how to correctly await and parse expected replies from the child process(es).此示例显示如何正确等待和解析来自子进程的预期回复。

You will note that I chose coroutines for the async version:你会注意到我为异步版本选择了协程:

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.使用默认的 Asio 回调样式可能会变得笨拙,所以我想将 Boost Coroutine 用于堆栈式协程。 That makes it so the implementation can be 99% similar to the synchronous version这使得它的实现可以与同步版本有 99% 的相似性

Just for comparison, here's what your code should look like if you didn't use coroutines:只是为了比较,如果你不使用协程,你的代码应该是这样的:

Fixing Up Your Code修复你的代码

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
}

Prints, eg印刷品,例如

在此处输入图像描述

Or if Stockfish is in fact installed:或者,如果 Stockfish实际上已安装:

在此处输入图像描述

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM