如何在不阻塞输入的情况下使用 getline?

问题描述 投票:0回答:4

有没有什么方法可以调用

getline()
,如果没有给出输入,不阻塞和等待?

我有以下代码:

 while(true){
    if(recv(sd, tBuffer, sizeof(tBuffer), MSG_PEEK | MSG_DONTWAIT) > 0) break;
    getline(cin,send);
 }

我想等待输入,但如果我在

sd
套接字收到一些数据,我想停止等待数据并退出
while
。我现在的代码,只是停留在
getline()
的第一次迭代。我想评估
getline()
,如果没有可用的输入,请返回
if

这可能吗?

PS:我尝试使用

cin.peek()
,但这也会阻止输入。

c++ nonblocking getline
4个回答
11
投票

你应该能够通过在标准输入上设置非阻塞模式来做到这一点,文件描述符 0:

int flags = fcntl(0, F_GETFL, 0);
fcntl(0, F_SETFL, flags | O_NONBLOCK);

现在,如果没有可用的输入,底层的

read()
系统调用将返回0,
std::cin
会认为这是文件结束,并在
eof()
上设置
std::cin

当你想再次从标准输入读取时,只需

clear()
流的状态。

这里唯一复杂的因素是,这使得很难在

std::cin
上检测到真正的文件结束条件。当标准输入是交互式终端时问题不大;但如果标准输入可以是一个文件,这将是一个问题。

在那种情况下,你唯一现实的选择是完全放弃

std::cin
,将非阻塞模式放在文件描述符0上,
poll()
select()
它,以确定什么时候有东西要读,然后
read()
它。

尽管您也可以将

poll()
select()
std::cin
一起使用,但这会变得复杂,因为您将需要明确检查是否有任何内容已缓冲在
std::cin
streambuf
中,因为那样会,显然,抢占任何类型的
poll()
select()
检查;但是通过尝试从
std::cin
中读取某些内容,您仍然冒着读取缓冲数据的风险,然后尝试从现在处于非阻塞模式的底层文件描述符中进行
read()
,这会导致伪造的结束-文件条件。

总而言之:您需要花一些额外的时间阅读和理解文件流和流缓冲区的工作原理;以及文件描述符实际如何工作,以及非阻塞模式如何工作;为了弄清楚您需要使用的正确逻辑。

哦,如果你坚持使用

std::cin
getline()
的非阻塞路线,你将没有简单的方法来确定
getline()
返回的字符串是否结束,因为
getline()
实际上从标准输入,或者它达到了过早的假文件结束条件并且实际上没有读取整行输入。

所以,使用非阻塞模式和

std::cin
,你将不得不使用
read()
,而不是
getline()


1
投票

istream::getsome() 方法可用于执行非阻塞读取。您可以使用它来构建 std::getline.

的非阻塞等效项
bool getline_async(std::istream& is, std::string& str, char delim = '\n') {

    static std::string lineSoFar;
    char inChar;
    int charsRead = 0;
    bool lineRead = false;
    str = "";

    do {
        charsRead = is.readsome(&inChar, 1);
        if (charsRead == 1) {
            // if the delimiter is read then return the string so far
            if (inChar == delim) {
                str = lineSoFar;
                lineSoFar = "";
                lineRead = true;
            } else {  // otherwise add it to the string so far
                lineSoFar.append(1, inChar);
            }
        }
    } while (charsRead != 0 && !lineRead);

    return lineRead;
}

此函数与原始

std::getline()
函数的工作方式相同,只是它始终立即返回。我把它放在my gists上,因为它偶尔会派上用场。


0
投票

我使用 select() 来检索标准输入文件描述符的状态。这适用于 Ubuntu 和嵌入式 Linux 开发板。如果 stdin 仍未收到用户的回车键,select 将等待一段时间并报告 stdin 未准备好。 stopReading 可以停止监视标准输入并继续其他内容。您可以根据需要对其进行编辑。它可能不适用于特殊输入 tty。

#include <sys/select.h>

static constexpr int STD_INPUT = 0;
static constexpr __suseconds_t WAIT_BETWEEN_SELECT_US = 250000L;

// Private variable in my class, but define as needed in your project
std::atomic<bool> stopReading;

...

std::string userInput = "";
while (false == stopReading)
{
    struct timeval tv = { 0L, WAIT_BETWEEN_SELECT_US };
    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(STD_INPUT, &fds);
    int ready = select(STD_INPUT + 1, &fds, NULL, NULL, &tv);

    if (ready > 0)
    {
        std::getline(std::cin, userInput);
        break;
    }
}

0
投票

我试图在我自己的项目中使用@PeteBlackerThe3rd 的答案。不幸的是,我只能在之前运行

std::ios_base::sync_with_stdio(false)
时才能让它工作。所以我尝试了我自己的版本,它也是线程安全的,以防多个线程从多个源读取。

// Returns 1 on success, 0 when not done, and -1 on failure (check errno)
// str is initially expected to be an empty string and should only altered by this function.
int getline_async_thread_safe(const int& fd, std::string& str, char delim = '\n') {
    int chars_read;
    do {
        char buf[2] = { 0 };
        chars_read = (int) read(fd, buf, 1);
        if (chars_read == 1) {
            if (*buf == delim) {
                return 1;
            }
            str.append(buf);
        } else {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                chars_read = 0;
                break;
            }
        }
    } while (chars_read > 0);

    return chars_read;
}

被调用的函数示例:

int flags = fcntl(0, F_GETFL, 0);
fcntl(0, F_SETFL, flags | O_NONBLOCK);

std::string output;
int retval;
while(some_condition) {
    retval = getline_async_thread_safe(0, output);
    if (retval == 0) {
        // Process std::string output 
        // Make sure to reset string if continuing through loop
    } else if (retval == -1) {
        // Check errno
    }
    // Do other things while waiting for the newline.
}
if (retval < 0) {
    // Handle error with errno.
} else {
    std::cout << "Output: " << output << std::endl;
}
© www.soinside.com 2019 - 2024. All rights reserved.