select - 安排一个异步事件,当 stdin 在 boost::asio 中有等待数据时将完成该事件?

标签 select boost boost-asio stdin ncurses

我正在使用 boost::asio 和 ncurses 来玩命令行游戏。游戏需要以固定的时间间隔在屏幕上进行绘制,并且在需要时也会执行其他操作(例如网络或文件操作)。所有这些事情都可以使用 async_read()/async_write() 或 boost::asio 上的等效项来完成。

但是,我还需要读取键盘输入,(我认为)来自标准输入。在 ncurses 中读取输入的常用方法是调用 getch(),它可以配置为阻塞(等待直到有字符可供使用)或非阻塞(返回标记值没有可用的字符)模式。

使用阻塞模式需要在单独的线程上运行 getch(),这与 ncurses 配合不佳。然而,使用非阻塞模式会导致我的应用程序消耗 CPU 时间循环旋转,直到用户按下键盘。我读过this答案,这表明我们可以在 select() 调用中将 stdin 添加到文件描述符列表中,这会阻塞,直到其中一个文件描述符有新数据为止。

由于我使用的是 boost::asio,因此无法直接使用 select()。我无法调用 async_read,因为这会消耗该字符,从而使 getch() 无法读取任何内容。 boost::asio 中是否有类似 async_read 的东西,但仅检查输入是否存在而不消耗它?

最佳答案

我认为您应该能够使用 posix 流描述符来监视文件描述符 0 上的输入:

ba::posix::stream_descriptor d(io, 0);
input_loop = [&](error_code ec) {
    if (!ec) {
        program.on_input();
        d.async_wait(ba::posix::descriptor::wait_type::wait_read, input_loop);
    }
};

在那里,program::on_input() 会调用 getch() 而没有 timeout(),直到返回 ERR:

struct Program {
    Program() {
        initscr();
        ESCDELAY = 0;
        timeout(0);
        cbreak(); 

        noecho();
        keypad(stdscr, TRUE); // receive special keys

        clock   = newwin(2, 40, 0, 0);
        monitor = newwin(10, 40, 2, 0);

        syncok(clock, true);    // automatic updating
        syncok(monitor, true);

        scrollok(monitor, true); // scroll the input monitor window
    }
    ~Program() {
        delwin(monitor);
        delwin(clock);
        endwin();
    }

    void on_clock() {
        wclear(clock);

        char buf[32];
        time_t t = time(NULL);
        if (auto tmp = localtime(&t)) {
            if (strftime(buf, sizeof(buf), "%T", tmp) == 0) {
                strncpy(buf, "[error formatting time]", sizeof(buf));
            }
        } else {
            strncpy(buf, "[error getting time]", sizeof(buf));
        }

        wprintw(clock, "Async: %s", buf);
        wrefresh(clock);
    }

    void on_input() {
        for (auto ch = getch(); ch != ERR; ch = getch()) {
            wprintw(monitor, "received key %d ('%c')\n", ch, ch);
        }
        wrefresh(monitor);
    }

    WINDOW *monitor = nullptr;
    WINDOW *clock = nullptr;
};

使用以下 main 程序,您将运行它 10 秒(因为 Program 尚不知道如何退出):

int main() {
    Program program;

    namespace ba = boost::asio;
    using boost::system::error_code;
    using namespace std::literals;

    ba::io_service io;
    std::function<void(error_code)> input_loop, clock_loop;

    // Reading input when ready on stdin
    ba::posix::stream_descriptor d(io, 0);
    input_loop = [&](error_code ec) {
        if (!ec) {
            program.on_input();
            d.async_wait(ba::posix::descriptor::wait_type::wait_read, input_loop);
        }
    };

    // For fun, let's also update the time
    ba::high_resolution_timer tim(io);
    clock_loop = [&](error_code ec) {
        if (!ec) {
            program.on_clock();
            tim.expires_from_now(100ms);
            tim.async_wait(clock_loop);
        }
    };

    input_loop(error_code{});
    clock_loop(error_code{});
    io.run_for(10s);
}

这有效:

enter image description here

完整列表

#include <boost/asio.hpp>
#include <boost/asio/posix/descriptor.hpp>
#include <iostream>
#include "ncurses.h"

#define CTRL_R    18
#define CTRL_C    3
#define TAB       9
#define NEWLINE   10
#define RETURN    13
#define ESCAPE    27
#define BACKSPACE 127
#define UP        72
#define LEFT      75
#define RIGHT     77
#define DOWN      80

struct Program {
    Program() {
        initscr();
        ESCDELAY = 0;
        timeout(0);
        cbreak(); 

        noecho();
        keypad(stdscr, TRUE); // receive special keys

        clock   = newwin(2, 40, 0, 0);
        monitor = newwin(10, 40, 2, 0);

        syncok(clock, true);    // automatic updating
        syncok(monitor, true);

        scrollok(monitor, true); // scroll the input monitor window
    }
    ~Program() {
        delwin(monitor);
        delwin(clock);
        endwin();
    }

    void on_clock() {
        wclear(clock);

        char buf[32];
        time_t t = time(NULL);
        if (auto tmp = localtime(&t)) {
            if (strftime(buf, sizeof(buf), "%T", tmp) == 0) {
                strncpy(buf, "[error formatting time]", sizeof(buf));
            }
        } else {
            strncpy(buf, "[error getting time]", sizeof(buf));
        }

        wprintw(clock, "Async: %s", buf);
        wrefresh(clock);
    }

    void on_input() {
        for (auto ch = getch(); ch != ERR; ch = getch()) {
            wprintw(monitor, "received key %d ('%c')\n", ch, ch);
        }
        wrefresh(monitor);
    }

    WINDOW *monitor = nullptr;
    WINDOW *clock = nullptr;
};

int main() {
    Program program;

    namespace ba = boost::asio;
    using boost::system::error_code;
    using namespace std::literals;

    ba::io_service io;
    std::function<void(error_code)> input_loop, clock_loop;

    // Reading input when ready on stdin
    ba::posix::stream_descriptor d(io, 0);
    input_loop = [&](error_code ec) {
        if (!ec) {
            program.on_input();
            d.async_wait(ba::posix::descriptor::wait_type::wait_read, input_loop);
        }
    };

    // For fun, let's also update the time
    ba::high_resolution_timer tim(io);
    clock_loop = [&](error_code ec) {
        if (!ec) {
            program.on_clock();
            tim.expires_from_now(100ms);
            tim.async_wait(clock_loop);
        }
    };

    input_loop(error_code{});
    clock_loop(error_code{});
    io.run_for(10s);
}

关于select - 安排一个异步事件,当 stdin 在 boost::asio 中有等待数据时将完成该事件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59341959/

相关文章:

c++ - boost 线程池绑定(bind)错误

c++ - 如何暂停和恢复 Boost 截止时间计时器?

c++ - 为什么 boost::asio::ip::tcp::basic_stream_socket::available 运行时间长?

IN where 语句中的 mysql 用户变量

sql-server - SQL Server : Select entry based on frequency of entries in another table

c++ - 在 boost asio 中为异步操作的函数处理程序分配线程的困难

c++ - 我如何调整这个 C++ 测试用例,以便它继续与新的网络设备一起工作?

c++ - 使用 Boost.Log 和 Boost.ASIO 导致崩溃

javascript - 从 html select 中选择的总和

javascript - 我想更改 div 显示/隐藏脚本以使用选择菜单而不是单选按钮