web-dev-qa-db-fra.com

Comment faites-vous des E / S de console non bloquantes sur Linux en C?

Comment faites-vous la console non bloquante IO sur Linux/OS X en C?

32
nonpolynomial237

Ce n'est pas vraiment le cas. Le TTY (console) est un appareil assez limité, et vous ne faites pratiquement pas d'E/S non bloquantes. Ce que vous faites lorsque vous voyez quelque chose qui ressemble à des E/S non bloquantes, par exemple dans une application curses/ncurses, s'appelle E/S brutes . Dans les E/S brutes, il n'y a pas d'interprétation des caractères, pas de traitement d'effacement, etc. À la place, vous devez écrire votre propre code qui vérifie les données tout en faisant d'autres choses.

Dans les programmes C modernes, vous pouvez simplifier cela d'une autre manière, en plaçant les E/S de la console dans un thread ou un processus léger. Ensuite, les E/S peuvent continuer de la manière habituelle de blocage, mais les données peuvent être insérées dans une file d'attente pour être traitées sur un autre thread.

Mise à jour

Voici un tutoriel curses qui le couvre plus.

10
Charlie Martin

Comme Pete Kirkham , j'ai trouvé cc.byexamples.com , et cela a fonctionné pour moi. Allez-y pour une bonne explication du problème, ainsi que la version ncurses.

Mon code devait prendre une commande initiale à partir d'une entrée standard ou d'un fichier, puis rechercher une commande d'annulation pendant le traitement de la commande initiale. Mon code est C++, mais vous devriez pouvoir utiliser scanf () et le reste où j'utilise la fonction d'entrée C++ getline ().

La viande est une fonction qui vérifie s'il y a une entrée disponible:

#include <unistd.h>
#include <stdio.h>
#include <sys/select.h>

// cc.byexamples.com calls this int kbhit(), to mirror the Windows console
//  function of the same name.  Otherwise, the code is the same.
bool inputAvailable()  
{
  struct timeval tv;
  fd_set fds;
  tv.tv_sec = 0;
  tv.tv_usec = 0;
  FD_ZERO(&fds);
  FD_SET(STDIN_FILENO, &fds);
  select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);
  return (FD_ISSET(0, &fds));
}

Cela doit être appelé avant toute fonction d'entrée stdin. Lorsque j'ai utilisé std :: cin avant d'utiliser cette fonction, cela ne renvoyait jamais true. Par exemple, main () a une boucle qui ressemble à ceci:

int main(int argc, char* argv[])
{ 
   std::string initialCommand;
   if (argc > 1) {
      // Code to get the initial command from a file
   } else {
     while (!inputAvailable()) {
       std::cout << "Waiting for input (Ctrl-C to cancel)..." << std::endl;
       sleep(1);
     }
     std::getline(std::cin, initialCommand);
   }

   // Start a thread class instance 'jobThread' to run the command
   // Start a thread class instance 'inputThread' to look for further commands
   return 0;
}

Dans le thread d'entrée, de nouvelles commandes ont été ajoutées à une file d'attente, qui était périodiquement traitée par le jobThread. L'entréeThread ressemblait un peu à ceci:

THREAD_RETURN inputThread()
{
  while( !cancelled() ) {
    if (inputAvailable()) {
      std::string nextCommand;
      getline(std::cin, nextCommand);
      commandQueue.lock();
      commandQueue.add(nextCommand);
      commandQueue.unlock();
    } else {
        sleep(1);
    }
  }
  return 0;
}

Cette fonction aurait probablement pu être dans main (), mais je travaille avec une base de code existante, pas contre.

Pour mon système, il n'y avait aucune entrée disponible jusqu'à ce qu'une nouvelle ligne soit envoyée, ce qui était exactement ce que je voulais. Si vous voulez lire chaque caractère lors de la frappe, vous devez désactiver le "mode canonique" sur stdin. cc.byexamples.com a quelques suggestions que je n'ai pas essayées, mais le reste a fonctionné, donc ça devrait marcher.

19
jwhitlock

Je veux ajouter un exemple:

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

int main(int argc, char const *argv[])

{
    char buf[20];
    fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
    sleep(4);
    int numRead = read(0,buf,4);
    if(numRead > 0){
        printf("You said: %s", buf);
    }
}

Lorsque vous exécutez ce programme, vous avez 4 secondes pour fournir une entrée à l'entrée standard. Si aucune entrée n'est trouvée, elle ne se bloquera pas et reviendra simplement.

2 exemples d'exécutions:

Korays-MacBook-Pro:~ koraytugay$ ./a.out
fda 
You said: fda
Korays-MacBook-Pro:~ koraytugay$ ./a.out
Korays-MacBook-Pro:~ koraytugay$ 
14
Koray Tugay

J'ai mis en signet " Entrée utilisateur non bloquante en boucle sans ncurses " plus tôt ce mois-ci quand je pensais avoir besoin d'une entrée console non bloquante et non tamponnée, mais je ne l'ai pas, donc je ne peux pas me porter garant pour savoir si cela fonctionne ou non. Pour mon usage, je m'en fichais qu'il n'ait pas reçu d'entrée jusqu'à ce que l'utilisateur appuie sur Entrée, donc juste utilisé aio pour lire stdin.

6
Pete Kirkham
4
Aziz

utilisez ncurses

3
soulmerge

Une autre alternative à l'utilisation de ncurses ou de threads consiste à utiliser GNU Readline , en particulier la partie de celui-ci qui vous permet de enregistrer les fonctions de rappel . Le schéma est alors:

  1. Utilisez select () sur STDIN (parmi tout autre descripteur)
  2. Lorsque select () vous indique que STDIN est prêt à lire, appelez rl_callback_read_char () de readline
  3. Si l'utilisateur a entré une ligne complète, rl_callback_read_char appellera votre rappel. Sinon, il reviendra immédiatement et votre autre code pourra continuer.
2
Tyler McHenry

Vous ne savez pas exactement ce que vous entendez par "console IO" - lisez-vous depuis STDIN, ou s'agit-il d'une application console qui lit à partir d'une autre source?

Si vous lisez depuis STDIN, vous devrez ignorer fread () et utiliser read () et write (), avec poll () ou select () pour empêcher les appels de bloquer. Vous pouvez peut-être désactiver la mise en mémoire tampon d'entrée, ce qui devrait provoquer le retour d'un EOF par fread, avec setbuf (), mais je ne l'ai jamais essayé.

0
Don Werve