简体   繁体   English

非阻塞控制台输入 C++

[英]Non-blocking console input C++

I'm looking for a (multiplatform) way to do non-blocking console input for my C++ program, so I can handle user commands while the program continually runs.我正在寻找一种(多平台)方法来为我的 C++ 程序进行非阻塞控制台输入,这样我就可以在程序持续运行时处理用户命令。 The program will also be outputting information at the same time.该程序还将同时输出信息。

What's the best/easiest way to do this?最好/最简单的方法是什么? I have no problem using external libraries like boost, as long as they use a permissive license.只要它们使用许可许可证,我就可以使用诸如 boost 之类的外部库。

I would do this by creating separate a thread which calls normal blocking IO functions and pass it a callback function which it would call when it got input.我将通过创建单独的线程来实现此目的,该线程调用正常阻塞 IO 函数并将其传递给回调 function ,它将在收到输入时调用。 Are you sure you need to do what you said you want to do?你确定你需要做你说你想做的事吗?

As for outputting information at the same time, what would happen if the user was in the middle of typing some input and you printed something?至于同时输出信息,如果用户正在输入一些输入并且你打印了一些东西会发生什么?

Example using C++11:使用 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);
}

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

There is one easy way:有一种简单的方法:

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] = '\0';
        point = 0;
        //Run(buffer);
    }
}

No block, all in 1 thread.没有阻塞,全部在 1 个线程中。 As for me, this works.至于我,这行得通。

I've done this on QNX4.5 that doesn't support threads or Boost by using select .我已经在不支持线程或 Boost 的 QNX4.5 上使用select完成了此操作。 You basically pass select STDIN as the file descriptor to use and select will return when a new line is entered.您基本上将select STDIN 作为要使用的文件描述符传递,并且 select 将在输入新行时返回。 I've added a simplified example loop below.我在下面添加了一个简化的示例循环。 It's platform independent, at least for Unix like systems.它与平台无关,至少对于 Unix 之类的系统而言。 Not sure about Windows though.虽然不确定 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);
      }
   }
}

Non-blocking console input C++?非阻塞控制台输入C++?

Ans: do console IO on a background thread and provide a means of communicating between threads. Ans:在后台线程上做控制台 IO 并提供线程之间通信的方法。

Here's a complete (but simplistic) test program that implements async io by deferring the io to a background thread.这是一个完整(但很简单)的测试程序,它通过将 io 推迟到后台线程来实现异步 io。

the program will wait for you to enter strings (terminate with newline) on the console and then perform a 10-second operation with that string.程序将等待您在控制台上输入字符串(以换行符结尾),然后对该字符串执行 10 秒的操作。

you can enter another string while the operation is in progress.您可以在操作进行时输入另一个字符串。

enter 'quit' to get the program to stop on the next cycle.输入“退出”以使程序在下一个周期停止。

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

sample test run:样品测试运行:

$ ./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 can be a good candidate. ncurses可能是一个不错的选择。

You can use the tinycon library to do this.您可以使用 tinycon 库来执行此操作。 Just spawn a tinycon object in a new thread, and you are pretty much done.只需在一个新线程中生成一个 tinycon object,就完成了。 You can define the trigger method to fire off whatever you'd like when enter is pressed.您可以定义触发方法以在按下回车时触发您想要的任何内容。

You can find it here: https://sourceforge.net/projects/tinycon/你可以在这里找到它: https://sourceforge.net/projects/tinycon/

Also, the license is BSD, so it will be the most permissive for your needs.此外,许可证是 BSD,因此它将最适合您的需求。

libuv is a cross-platform C library for asynchronous I/O. libuv是一个用于异步 I/O 的跨平台 C 库。 It uses an event loop to do things like read from standard input without blocking the thread.它使用事件循环来执行诸如从标准输入读取而不阻塞线程之类的事情。 libuv is what powers Node.JS and others. libuv 是 Node.JS 和其他软件的动力。

The StdinDataIO class of the BSD-licensed MUSCLE networking library supports non-blocking reads from stdin under Windows, MacOS/X, and Linux/Unix... you could use that (or just examine the code as an example of how it can be done) if you want. BSD 许可的MUSCLE 网络库StdinDataIO class 支持在 Windows、MacOS/X 和 Linux/Unix 下从标准输入进行非阻塞读取......您可以使用它(或者只是检查代码作为示例完成)如果你愿意。

In a sense, this answer is incomplete.从某种意义上说,这个答案是不完整的。 But yet, I think it can be useful even for people who have different platforms or circumstances, giving the idea, what to look for in their platform.但是,我认为即使对于拥有不同平台或环境的人来说,它也会很有用,给出想法,在他们的平台上寻找什么。

As I just wrote some scripting engine integration into an SDL2 main event loop (which is supposed to read lines from stdin if there are lines to be read), here is how I did it (on linux (debian bullseye 64 bit)).由于我刚刚将一些脚本引擎集成到 SDL2 主事件循环中(如果有要读取的行,则应该从stdin读取行),这就是我的做法(在 linux(debian Bullseye 64 位)上)。 See below.见下文。

But even if you are not on linux, but on some other posix system, you can use the equivalent platform APIs of your platform.但是,即使您不在 linux 上,而是在其他一些 posix 系统上,您也可以使用您平台的等效平台 API。 For example, you can use kqueue on FreeBSD.例如,您可以在 FreeBSD 上使用kqueue Or you can consider using libevent for a bit more portable approach (still will not really work on Windows).或者您可以考虑将libevent用于更便携的方法(在 Windows 上仍然无法真正工作)。

This approach might also work on Windows if you do some special fiddling with the rather new-ish ConPTY .如果您对相当新的 ConPTY 进行一些特殊的摆弄,这种方法也可能适用于Windows In traditional windows console applications, the problem is, that stdin is not a real file handle and as such, passing it to libevent or using IOCP (IO completion ports) on it will not work as expected.在传统的 windows 控制台应用程序中,问题在于,stdin 不是真正的文件句柄,因此,将其传递给libevent或在其上使用 IOCP(IO 完成端口)将无法按预期工作。

But, this approach should also work on posix systems, if there is redirection at play.但是,如果存在重定向,这种方法也应该适用于 posix 系统。 As long as there is a file handle available.只要有可用的文件句柄。

So how does it work?那么它是怎样工作的?

  1. Use epoll_wait() to detect if there is data available on stdin.使用epoll_wait()检测标准输入是否有可用数据。 While consoles can be configured in all sorts of ways, typically, they operate on a line by line basis (should also apply for ssh etc.).虽然控制台可以通过各种方式进行配置,但通常它们是逐行运行的(也应适用于 ssh 等)。
  2. Use your favorite getline() function to read the line from stdin.使用您最喜欢的getline() function 从标准输入读取该行。 Which will work, because you know, there is data and it will not block (unless your console is not defaulting to line by line handling).这会起作用,因为你知道,有数据并且它不会阻塞(除非你的控制台没有默认逐行处理)。
  3. Rince and repeat.冲洗并重复。
#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;
}

You could do:你可以这样做:

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



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

A simple answer with thread/future and reading a single char at a time (you can replace getchar with cin as required)线程/未来的简单答案,一次读取一个字符(您可以根据需要将getchar替换为cin

Timeout is set to zero and a new future is created every time the previous call is completed.超时设置为零,每次上一次调用完成时都会创建一个新的未来。 Like cin , getchar requires that the user hits the RETURN key to end the function call.cin一样, getchar要求用户按RETURN键来结束 function 调用。

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

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

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