无阻塞控制台输入C++

Non-blocking console input C++

本文关键字:输入 C++ 控制台      更新时间:2023-10-16

我正在寻找一种(多平台)方法来为我的C++程序进行非阻塞控制台输入,这样我就可以在程序持续运行的同时处理用户命令。该程序还将同时输出信息。

做这件事最好/最简单的方法是什么?我使用boost这样的外部库没有问题,只要它们使用许可证即可。

使用C++11的示例:

#include <iostream>
#include <future>
#include <thread>
#include <chrono>
static std::string getAnswer()
{    
    std::string answer;
    std::cin >> answer;
    return answer;
}
int main()
{
    std::chrono::seconds timeout(5);
    std::cout << "Do you even lift?" << std::endl << std::flush;
    std::string answer = "maybe"; //default to maybe
    std::future<std::string> future = std::async(getAnswer);
    if (future.wait_for(timeout) == std::future_status::ready)
        answer = future.get();
    std::cout << "the answer was: " << answer << std::endl;
    exit(0);
}

在线编译器:https://rextester.com/GLAZ31262

我将通过创建一个单独的线程来实现这一点,该线程调用正常的阻塞IO函数,并向它传递一个回调函数,它将在收到输入时调用该函数。你确定你需要按照你说的去做吗?

至于同时输出信息,如果用户正在输入一些输入,而您打印了一些内容,会发生什么?

我已经在QNX4.5上完成了这项工作,它不支持线程或使用select进行Boost。您基本上将select STDIN作为要使用的文件描述符传递,并在输入新行时返回select。我在下面添加了一个简化的示例循环。它是独立于平台的,至少对于类Unix系统来说是这样。但对Windows不太确定。

while (!g_quit)
{
   //we want to receive data from stdin so add these file
   //descriptors to the file descriptor set. These also have to be reset
   //within the loop since select modifies the sets.
   FD_ZERO(&read_fds);
   FD_SET(STDIN_FILENO, &read_fds);
   result = select(sfd + 1, &read_fds, NULL, NULL, NULL);
   if (result == -1 && errno != EINTR)
   {
      cerr << "Error in select: " << strerror(errno) << "n";
      break;
   }
   else if (result == -1 && errno == EINTR)
   {
      //we've received and interrupt - handle this
      ....
   }
   else
   {
      if (FD_ISSET(STDIN_FILENO, &read_fds))
      {
         process_cmd(sfd);
      }
   }
}

有一种简单的方法:

char buffer[512];
int point = 0;
...
while (_kbhit()) {
    char cur = _getch();
    if (point > 511) point = 511;
    std::cout << cur;
    if (cur != 13) buffer[point++] = cur;
    else{
        buffer[point] = '';
        point = 0;
        //Run(buffer);
    }
}

无阻塞,全在一个线程中。对我来说,这是有效的。

非阻塞控制台输入C++?

Ans:在后台线程上执行控制台IO,并提供线程之间的通信方式。

这里有一个完整的(但过于简单的)测试程序,它通过将io延迟到后台线程来实现异步io。

程序将等待您在控制台上输入字符串(用换行符终止),然后用该字符串执行10秒的操作。

您可以在操作进行时输入另一个字符串。

输入"退出"以使程序在下一个周期停止。

#include <iostream>
#include <memory>
#include <string>
#include <future>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <deque>
int main()
{
    std::mutex m;
    std::condition_variable cv;
    std::string new_string;
    bool error = false;
    auto io_thread = std::thread([&]{
        std::string s;
        while(!error && std::getline(std::cin, s, 'n'))
        {
            auto lock = std::unique_lock<std::mutex>(m);
            new_string = std::move(s);
            if (new_string == "quit") {
                error = true;
            }
            lock.unlock();
            cv.notify_all();
        }
        auto lock = std::unique_lock<std::mutex>(m);
        error = true;
        lock.unlock();
        cv.notify_all();
    });
    auto current_string = std::string();
    for ( ;; )
    {
        auto lock = std::unique_lock<std::mutex>(m);
        cv.wait(lock, [&] { return error || (current_string != new_string); });
        if (error)
        {
            break;
        }
        current_string = new_string;
        lock.unlock();
        // now use the string that arrived from our non-blocking stream
        std::cout << "new string: " << current_string;
        std::cout.flush();
        for (int i = 0 ; i < 10 ; ++i) {
            std::this_thread::sleep_for(std::chrono::seconds(1));
            std::cout << " " << i;
            std::cout.flush();
        }
        std::cout << ". done. next?n";
        std::cout.flush();
    }
    io_thread.join();
    return 0;
}

样品测试运行:

$ ./async.cpp
first
new string: first 0 1las 2t 3
 4 5 6 7 8 9. done. next?
new string: last 0 1 2 3 4 5 6 7 8quit 9. done. next?

ncurses可能是一个很好的候选者。

BSD授权的MUCLE网络库的StdinDataIO类支持在Windows、MacOS/X和Linux/Unix下从stdin进行非阻塞读取。。。如果您愿意,您可以使用它(或者只是检查代码作为如何完成它的示例)。

您可以使用tinycon库来完成此操作。只需在一个新线程中生成一个tinycon对象,就基本完成了。您可以定义触发器方法,以便在按下回车键时激发您想要的任何内容。

你可以在这里找到它:https://sourceforge.net/projects/tinycon/

此外,许可证是BSD,因此它将是最适合您需求的。

libuv是一个用于异步I/O的跨平台C库。它使用事件循环来执行诸如从标准输入读取之类的操作,而不阻塞线程。libuv为Node供电。JS等。

从某种意义上说,这个答案是不完整的。但是,我认为,即使对于有不同平台或环境的人来说,它也会很有用,让他们知道在自己的平台上寻找什么。

由于我刚刚在SDL2主事件循环中编写了一些脚本引擎集成(如果有要读取的行,则应该从stdin中读取行),以下是我如何做到的(在linux(debian bullseye 64位)上)。请参见下文。

但是,即使您不是在linux上,而是在其他posix系统上,您也可以使用平台的等效平台API。例如,您可以在FreeBSD上使用kqueue。或者,您可以考虑使用libevent作为一种更便携的方法(在Windows上仍然无法真正工作)。

如果你对相当新的ConPTY进行一些特殊的处理,这种方法可能也适用于Windows。在传统的windows控制台应用程序中,问题是stdin不是真正的文件句柄,因此,将其传递给libevent或在其上使用IOCP(IO完成端口)将无法按预期工作。

但是,如果有重定向,这种方法也应该适用于posix系统。只要有可用的文件句柄。

那么它是如何工作的呢

  1. 使用epoll_wait()检测stdin上是否有可用数据。虽然控制台可以用各种方式进行配置,但通常情况下,它们是逐行操作的(也应该适用于ssh等)
  2. 使用您喜欢的getline()函数从stdin读取该行。这会起作用,因为你知道,有数据,它不会阻塞(除非你的控制台没有默认为逐行处理)
  3. 冲洗并重复
#include <unistd.h>
#include <sys/epoll.h>
#include <iostream>
#include <string>
#include <array>
using EpollEvent_t = struct epoll_event;
int main(int argc, const char* argv[]) {
  //
  // create epoll instance
  //
  int epollfd = epoll_create1(0);
  if (epollfd < 0) {
    std::cout << "epoll_create1(0) failed!" << std::endl;
    return -1;
  }
  //
  // associate stdin with epoll
  //
  EpollEvent_t ev;
  ev.data.ptr = nullptr;
  ev.data.fd = STDIN_FILENO; // from unistd.h
  ev.data.u32 = UINT32_C(0);
  ev.data.u64 = UINT64_C(0);
  ev.events = EPOLLIN;
  if (epoll_ctl(epollfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev) < 0) {
    std::cout
      << "epoll_ctl(epollfd, EPOLL_CTL_ADD, fdin, &ev) failed."
      << std::endl;
    return -1;
  }
  //
  // do non-blocking line processing in your free running
  // main loop
  //
  std::array<EpollEvent_t,1> events;
  bool running = true;
  while (running) {
    int waitret = epoll_wait(epollfd,
                 events.data(),
                 events.size(),
                 0); // 0 is the "timeout" we want
    if (waitret < 0) {
      std::cout << "epoll_wait() failed." << std::endl;
      running = false;
    }
    if (0 < waitret) { // there is data on stdin!
      std::string line;
      std::getline(std::cin, line);
      std::cout
    << "line read: [" << line << "]" << std::endl;
      if (line == "quit")
    running = false;
    }
      // ... Do what you usually do in your main loop ...
  }
  //
  // cleanup of epoll etc.
  //
  close(epollfd);
    
    
  return 0;
}

你可以做:

#include <thread>
#include <chrono>
#include <string>
#include <iostream>

int main() {
    std::cout << "Type exit to quit." << std::endl;
    // initialize other std::thread handlers here 
    std::string input;
    
    while (input != "exit") {
        std::getline(std::cin, input);
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }
    std::cout << "Cleaning up and quitting" << std::endl;
    return 0;
};

一个简单的答案,使用线程/未来,每次读取一个字符(您可以根据需要用cin替换getchar

超时设置为零,每次完成上一次调用时都会创建一个新的未来。与cin一样,getchar要求用户点击RETURN键来结束函数调用。

#include <chrono>
#include <cstdio>
#include <future>
#include <iostream>
#include <thread>
static char get_usr_in()
{
  return std::getchar();
}
int main()
{
  std::chrono::seconds timeout(0);
  std::future<char> future = std::async(std::launch::async, get_usr_in);
  char ch = '!';
  while(ch!='q') {
    if(future.wait_for(timeout) == std::future_status::ready) {
      ch = future.get();
      if(ch!='q') {
        future = std::async(std::launch::async, get_usr_in);
      }
      if(ch >= '!' && ch <'~')
        std::cout << "ch:" << ch << std::endl;
    }
    std::cout << "." << std::endl;
  }
  exit(0);
}

为什么不使用promise?

#include <iostream>
#include <istream>
#include <thread>
#include <future>
#include <chrono>
void UIThread(std::chrono::duration<int> timeout) {
    std::promise<bool> p;
    std::thread uiWorker([&p]() {
        bool running = true;
        while(running) {
            std::string input;
            std::cin >> input;
            if(input == "quit") {
                p.set_value(true);
                running = false;
            }
        }
    });
    auto future = p.get_future();
    if (future.wait_for(timeout) != std::future_status::ready) {
        std::cout << "UI thread timed out" << std::endl;
        uiWorker.detach();
        return;
    }
    uiWorker.join();
}
int main()
{
    std::thread uiThread(UIThread, std::chrono::seconds(3));
    std::cout << "Waiting for UI thread to complete" << std::endl;
    uiThread.join();
}

在线编译器