无阻塞控制台输入C++
Non-blocking console input C++
我正在寻找一种(多平台)方法来为我的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系统。只要有可用的文件句柄。
那么它是如何工作的呢
- 使用
epoll_wait()
检测stdin上是否有可用数据。虽然控制台可以用各种方式进行配置,但通常情况下,它们是逐行操作的(也应该适用于ssh等) - 使用您喜欢的
getline()
函数从stdin读取该行。这会起作用,因为你知道,有数据,它不会阻塞(除非你的控制台没有默认为逐行处理) - 冲洗并重复
#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();
}
在线编译器
- 在C++程序中输入的文本文件将不起作用,除非文本被复制和粘贴
- 2D数组来自文本输入,中间有空格
- 如何使用 < 和 > 命令获取 c++ 中的输入和输出?
- 检查输入是否不是整数或数字
- 正在尝试了解输入验证循环
- 读取文件并输入到矢量中
- C++如何通过用户输入删除列表元素
- 用c++从输入文件中读取另一行
- 读取文件的最后一行并输入到链接列表时出错
- 创建一个函数以在输入为负数或零时输出字符串.第一次执行用户定义的函数
- 如何使用用户输入在C++中正确填充2D数组
- C++MySQL C api用户输入行
- 输入到文件并输出到另一个文件,并将流文件传递给函数
- 用户定义函数中的指针和输入
- 如何在C++中检查2D数组中负值的输入验证
- 如何只允许用户输入正整数
- 在while循环中输入带有std::cin的字符串后,控制台会输出大量胡言乱语
- 输入中的字符串数未知(以字母表示)
- cpp二进制搜索问题,计算给定数组中输入元素的出现次数
- 在一个模板函数中,若输入的类型是enum类,我该如何使用std::underlying_type