Development Tip

C 비 차단 키보드 입력

yourdevel 2020. 10. 19. 12:54
반응형

C 비 차단 키보드 입력


사용자가 키를 누를 때까지 반복되는 C (Linux) 프로그램을 작성하려고하지만 각 반복을 계속하기 위해 키를 누르지 않아도됩니다.

이를 수행하는 간단한 방법이 있습니까? 나는 그것을 할 수 있다고 생각 select()하지만 그것은 많은 일처럼 보입니다.

또는, 잡을 수있는 방법이있다 ctrl- c프로그램이 대신 비 차단 IO의 닫기 전에 정리를 할의 키는?


이미 언급했듯이 sigactionctrl-c select를 트랩하거나 표준 입력을 트랩하는 데 사용할 수 있습니다 .

그러나 후자의 방법을 사용하면 TTY를 설정하여 한 번에 한 줄씩 모드가 아닌 한 번에 한 문자로 설정해야합니다. 후자는 기본값입니다. 텍스트 줄을 입력하면 Enter 키를 누를 때까지 실행중인 프로그램의 표준 입력으로 전송되지 않습니다.

tcsetattr()기능 을 사용하여 ICANON 모드를 끄고 ECHO도 비활성화해야합니다. 메모리에서 프로그램이 종료되면 터미널을 다시 ICANON 모드로 설정해야합니다!

유닉스 TTY를 설정하고 DOS의 에뮬레이션 : 그냥 완성도, 여기에 내가 그냥 기절 한 일부 코드 (오류 검사! NB)의 <conio.h>기능 kbhit()getch():

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <termios.h>

struct termios orig_termios;

void reset_terminal_mode()
{
    tcsetattr(0, TCSANOW, &orig_termios);
}

void set_conio_terminal_mode()
{
    struct termios new_termios;

    /* take two copies - one for now, one for later */
    tcgetattr(0, &orig_termios);
    memcpy(&new_termios, &orig_termios, sizeof(new_termios));

    /* register cleanup handler, and set the new terminal mode */
    atexit(reset_terminal_mode);
    cfmakeraw(&new_termios);
    tcsetattr(0, TCSANOW, &new_termios);
}

int kbhit()
{
    struct timeval tv = { 0L, 0L };
    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(0, &fds);
    return select(1, &fds, NULL, NULL, &tv);
}

int getch()
{
    int r;
    unsigned char c;
    if ((r = read(0, &c, sizeof(c))) < 0) {
        return r;
    } else {
        return c;
    }
}

int main(int argc, char *argv[])
{
    set_conio_terminal_mode();

    while (!kbhit()) {
        /* do some work */
    }
    (void)getch(); /* consume the character */
}

select()편의상 너무 낮은 수준입니다. ncurses라이브러리를 사용하여 터미널을 cbreak 모드 및 지연 모드로 설정 한 다음을 호출 하면 문자가 준비되지 않은 경우 getch()반환 ERR됩니다.

WINDOW *w = initscr();
cbreak();
nodelay(w, TRUE);

이 시점에서 getch차단하지 않고 전화 걸 수 있습니다 .


UNIX 시스템에서는 sigactioncall을 사용 SIGINT하여 Control + C 키 시퀀스를 나타내는 신호에 대한 신호 처리기를 등록 할 수 있습니다 . 신호 처리기는 루프에서 확인 될 플래그를 설정하여 적절하게 중단되도록 할 수 있습니다.


당신은 아마 원합니다 kbhit();

//Example will loop until a key is pressed
#include <conio.h>
#include <iostream>

using namespace std;

int main()
{
    while(1)
    {
        if(kbhit())
        {
            break;
        }
    }
}

이것은 모든 환경에서 작동하지 않을 수 있습니다. 이식 가능한 방법은 모니터링 스레드를 만들고 플래그를 설정하는 것입니다.getch();


비 차단 키보드 입력을 얻는 또 다른 방법은 장치 파일을 열고 읽는 것입니다!

찾고있는 장치 파일, / dev / input / event * 중 하나를 알아야합니다. cat / proc / bus / input / devices를 실행하여 원하는 장치를 찾을 수 있습니다.

이 코드는 나를 위해 작동합니다 (관리자 권한으로 실행).

  #include <stdlib.h>
  #include <stdio.h>
  #include <unistd.h>
  #include <fcntl.h>
  #include <errno.h>
  #include <linux/input.h>

  int main(int argc, char** argv)
  {
      int fd, bytes;
      struct input_event data;

      const char *pDevice = "/dev/input/event2";

      // Open Keyboard
      fd = open(pDevice, O_RDONLY | O_NONBLOCK);
      if(fd == -1)
      {
          printf("ERROR Opening %s\n", pDevice);
          return -1;
      }

      while(1)
      {
          // Read Keyboard Data
          bytes = read(fd, &data, sizeof(data));
          if(bytes > 0)
          {
              printf("Keypress value=%x, type=%x, code=%x\n", data.value, data.type, data.code);
          }
          else
          {
              // Nothing read
              sleep(1);
          }
      }

      return 0;
   }

이 목적으로 curses 라이브러리를 사용할 수 있습니다. 물론 select()시그널 핸들러도 어느 정도 사용할 수 있습니다.


If you are happy just catching Control-C, it's a done deal. If you really want non-blocking I/O but you don't want the curses library, another alternative is to move lock, stock, and barrel to the AT&T sfio library. It's nice library patterned on C stdio but more flexible, thread-safe, and performs better. (sfio stands for safe, fast I/O.)


There is no portable way to do this, but select() might be a good way. See http://c-faq.com/osdep/readavail.html for more possible solutions.


Here's a function to do this for you. You need termios.h which comes with POSIX systems.

#include <termios.h>
void stdin_set(int cmd)
{
    struct termios t;
    tcgetattr(1,&t);
    switch (cmd) {
    case 1:
            t.c_lflag &= ~ICANON;
            break;
    default:
            t.c_lflag |= ICANON;
            break;
    }
    tcsetattr(1,0,&t);
}

Breaking this down: tcgetattr gets the current terminal information and stores it in t. If cmd is 1, the local input flag in t is set to non-blocking input. Otherwise it is reset. Then tcsetattr changes standard input to t.

If you don't reset standard input at the end of your program you will have problems in your shell.


You can do that using select as follow:

  int nfds = 0;
  fd_set readfds;
  FD_ZERO(&readfds);
  FD_SET(0, &readfds); /* set the stdin in the set of file descriptors to be selected */
  while(1)
  {
     /* Do what you want */
     int count = select(nfds, &readfds, NULL, NULL, NULL);
     if (count > 0) {
      if (FD_ISSET(0, &readfds)) {
          /* If a character was pressed then we get it and exit */
          getchar();
          break;
      }
     }
  }

Not too much work :D

참고URL : https://stackoverflow.com/questions/448944/c-non-blocking-keyboard-input

반응형