简体   繁体   中英

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 ,

I refactored the code and hybridized the new method using the Boost library. 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.

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:

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. 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. The first uci command runs successfully, but the isready command, instead of returning readyok , it returns:

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:

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. I typed these commands in Terminal while running Stockfish directly through Terminal, they work as a result, but my program still can't

You are printing to cout as if the async operations happen immediately. That's not the case. The async operations only happen when the io service runs.

svc.run();

Is at the very end of your code. So no async_ operation ever completes (or even starts) before that.

Other problems:

  1. Your out async pipe is never used (not even connected). 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. (But then perhaps recv_buffer can be deleted just as well).

  3. Your buffers include the terminating NUL characters. ( asio::buffer("uci\n") sends {'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. This guarantees that subsequent writes never can happen, as you closed the pipe.

  5. Then when you send quit you fail to include the '\n' as well

  6. You are reading into a char[64] with operator>> which makes no sense at all. Maybe you are using c++20 (so width of 64 might be assumed) but you never set a width. 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). So, likely you wanted std::getline instead...

  8. The fact that you include a boatload of C headers makes me think you're porting some C code (badly). That's also exemplified by the strcmp use and others, eg no need to use ::stat

  9. Don't use using namespace std; ( Why is "using namespace std;" considered bad practice? )

  10. Don't use global variables ( errDetails )

  11. Don't use loops to wait for a time delay

  12. No need to manually print backtraces. Instead, use 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

You're in luck: I have a written full demo using stockfish on this site: 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. That makes it so the implementation can be 99% similar to the synchronous version

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:

在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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