C++ 中带超时的输入

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

我想要一个程序,让用户有 10 秒的时间输入密码。如果计时器超过 10 秒,程序会显示一条消息。我当前的代码是这样的:

 #include <iostream>
 #include <ctime>
 #include <string>

int main(){
std::string password;

int start_s=clock();

int stop_s=clock();
if(stop_s-start_s <= 0){

    std::cout << "TIME RAN OUT!";
}
std::cout << "Enter your password! \n";
std::cout << "Password: ";
std::cin >> password;
std::cout << "\n \n";

if (password == "password123"){

    std::cout << "Correct!";
} else {
    std::cout << "Wrong!";
}

}

这当然行不通...但我不知道下一步该做什么...有什么想法吗?

如果您需要更多详细信息,请在评论中询问。

编辑:

我刚刚意识到问题是什么......它需要一个时间戳,然后很快又制作另一个时间戳。当发现差异时,它低于0...

但我还是不知道下一步该做什么...

c++ windows winapi timing
4个回答
12
投票

您想要做的是从

stdin
进行非阻塞(异步)读取,超时时间为 10 秒。这并不太难,但根据您当前的水平可能会涉及许多新概念。

这里的关键概念是

cin >> password;
是一个阻塞调用,即,在完成之前,控制不会在此代码中进一步流动。所以我们需要以某种方式使其成为非阻塞,或者保持阻塞并在超时到期时打破它。

根据系统的设计要求和约束,有一些常见的实现方式。每个实现都因操作系统而异,但技术非常相似。

1。异步:带超时的 STDIN 这种方法通常用于网络编程,并且可以扩展到其他形式的输入,例如当前的情况。

  1. 将标准输入 (STDIN) 句柄 (handle = 0) 放入“监视列表”中。
  2. 在观察列表中设置超时。
  3. 只要 STDIN 发生变化,就对其进行处理。
  4. 超时后,检查我们处理的内容是否有效。

在 Linux(以及许多其他 Unix 版本)中,可以使用

FD_SET
select
系统调用来处理监视列表。在 Windows 中,您需要使用
WaitForMultipleEvents

我不确定我是否能够出于这个问题的目的准确地解释这些概念。作为参考,另一个问题有一些完全相同的代码指针是here

2。同步:带中断的多线程 这是一种常用技术,用于我们需要细粒度事件调度程序/计时器的情况。

  1. 创建两个线程,
    A
    B
  2. A
    将等待指定的超时时间。
  3. B
    将等待阻塞读取
  4. 如果
    A
    B
    完成之前终止(超时),
    A
    会向
    B
    发出信号,并且
    B
    决定下一步要做什么(终止、重复消息等)
  5. 如果
    B
    读取密码并且没有问题,
    B
    会向
    A
    发出信号并要求其死亡。

实现相同目的的另一种方法是使操作系统中断线程

B
,如其中一个评论中所述。

3.同步:轮询 这用于我们不需要对时间进行太多细粒度控制的情况。

  1. 使用非阻塞读取检查输入中是否有任何内容 (
    kbhit()
    )
  2. 如果没有,并且超时还有剩余时间,请等待更短的时间
    delta
    (说
    10ms
  3. 如果超时已过并且没有剩余时间,请执行所需的任何处理(向用户发送消息、退出等)

请注意,在这种情况下,根据

delta
,该方法可能会消耗大量 CPU 并且可能效率低下。例如,如果像上面那样
delta=10ms
,线程每秒将被唤醒100次,效率不高,尤其是当用户在键盘上输入字符的速度不那么快时。


0
投票

这段代码使用线程来完成。

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

int main()
{
   std::string password;
   std::cout << "Enter your password! \n";
   std::cout << "Password: ";

   //try to read a passsword in 10 seconds
   std::thread t1([&]() {
       std::cin >> password;
       });
   std::this_thread::sleep_for(std::chrono::seconds(10));
   t1.detach();

   //check if password was entered
   if (password.empty())
       std::cout << "TIME IS OUT\n";
   else
       if (password == "password123")
           std::cout << "Correct";
       else
           std::cout << "Worng";

   return 0;
}

但是如果你检查一下,你会发现即使用户及时输入了密码,它也会等到 10 秒结束。 所以你可以这样改进它:

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

int main()
{
    std::string password;
    std::cout << "Enter your password! \n";
    std::cout << "Password: ";


    time_t start = time(NULL);
    time_t waitTime = 10;

    //try to read a passsword in 10 seconds
    while (time(NULL) < start + waitTime && password.empty())
    {
        std::thread t1([&]() {
            std::cin >> password;
            });
        std::this_thread::sleep_for(std::chrono::milliseconds(20));
        t1.detach();
    }
    
    

    //check if password was entered
    if (password.empty())
        std::cout << "\nTIME IS OUT\n";
    else
        if (password == "password123")
            std::cout << "Correct";
        else
            std::cout << "Worng";

    return 0;
}

0
投票

我最近遇到了同样的问题。我从@user1952500 找到了答案,提供了有趣的方法。其中,第二个对我来说比较容易理解。

但是,我还没有找到好的代码解决方案。更一般地说,使用分离不会杀死线程,但它允许独立运行(“在主线程之外”)。这意味着线程将永远运行,直到整个代码终止,如果我们分离多个线程,情况会更糟。

要使用线程正确实现超时键盘输入,有必要从另一个线程终止该线程。问题是:仅使用 C++ 标准库是不可能做到的。

需要使用平台相关的功能。 这是我针对 Linux 的解决方案:

#include <thread>
#include <chrono>
#include <stdio.h>
#include <pthread.h>
#include <string>

std::chrono::milliseconds now_ms(){
    return std::chrono::duration_cast<std::chrono::milliseconds>(
        std::chrono::system_clock::now().time_since_epoch()
    );
}


int main(){
    // variable to check if the thread terminated by itself
    bool thread_terminated = false;
    // variable to store input 
    std::string answer;
    // create thread
    std::thread t1([&]() {
        std::cin >> answer;
        thread_terminated = true;
    });

    // get native handle
    auto t_handle = t1.native_handle();
    // start the thread
    auto start_ts = now_ms();
    t1.detach()

    // check for timeout or answer received
    while(this->now_ms_d() - start_ts < this->_cin_timeout && answer.empty()){
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    // if the thread didn't terminate, kill it
    if(! thread_terminated) pthread_cancel(t_handle);
    
    if (answer.empty()){
        std::cout << "no answer received" << std::endl;
    }
    else{
        /* do what you need to do with answer */
    }

    return 0;
}

针对不同平台,需要调用不同的函数,而不是

pthread_cancel(t_handle)

例如在 Windows 上,您可以使用 TerminateThread


-3
投票

规范的 C++ 解决方案应该如下所示:

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

int main() {
     std::string password;

     std::cout << "Enter your password! \n";
     std::cout << "Password: ";
     auto start_s = std::chrono::system_clock::now();
     std::cin >> password;

     auto stop_s = std::chrono::system_clock::now();
     if(stop_s - start_s >= std::chrono::seconds(10)) {
        std::cout << "TIME RAN OUT!";
     }
     else if (password == "password123") {
            std::cout << "Correct!";
     } else {
         std::cout << "Wrong!";
     }

}
© www.soinside.com 2019 - 2024. All rights reserved.