stdin으로부터 키 입력 체크하기
글쓴이: 송지석 / 작성시간: 목, 2004/06/03 - 11:19오전
cygwin환경에서 테스트 프로그램을 짜는 중인데요.
제가 하고 싶은 건
while 루프 안에서 키 입력을 체크하는 루틴을 두고, 키 입력이 있으면 거기에 따른 명령을 실행하는 겁니다.
그런데 키 입력을 받으려고 하니 getc fgetc등은 입력시 엔터를 받을 때 까지 기다리기 때문에 while 루프 안에서 한동안 멈춰있습니다.
키 입력이 있는 지 체크하고 없으면 NULL을 리턴하는 함수 없을까요? 아니면 뭔가 좋은 방법이 있을까요?
이전에 리눅스에서는 while 루프에선 fgets로 입력만 체크하고 thread로 worker를 돌렸었는데 지금은 그럴 필요까지는 없을 것 같고 cygwin에선 될 지 안될 지도 모르고..
뭔가 방법이 없을까요? 간단한 테스트 때문에 두시간 째 삽질중이네요..
Forums:
C 스트림의 fd를 얻을 수 있나요? 그리고 그 콘솔 fd 도 selec
C 스트림의 fd를 얻을 수 있나요? 그리고 그 콘솔 fd 도 select 할 수 있나요? 실험해주세요~~~ 궁금합니다... 스티븐 아저씨 책에도 콘솔 입력과 파일 처리를 동시에 select 하는 예제가 있었던 걸로 기억합니다...
DOS 쪽은 kbhit, 윈도우는 Wait* 시리즈중에 Input 하고 같이 하는 것이 있어서 어떻게 될 듯한데요. *nix 환경에서는 어떤지 모르겠습니다.
[code:1]...fd = fileno(stdin);
대략 위와같은 형태로 나오겠습니다.
Stevens책에 예제도 있습니다. 정확한 예제는 Unix Network programming
1st edition(현재 3rd까지 나왔지요?) 에서 IO Multiplexing이 나오는 Chapter입니다. 2nd와 3rd Edition에서는 어디쯤 나오는지 가물가물하네요.
------------------ P.S. --------------
지식은 오픈해서 검증받아야 산지식이된다고 동네 아저씨가 그러더라.
궁금한게 하나 더 있는데요. select 후에 read 를 써야 하나요,
궁금한게 하나 더 있는데요. select 후에 read 를 써야 하나요, fread 를 써야 하나요? 둘중 하나만 일관되게 쓰면 문제가 없나요? mach 님의 경험담을 좀... 책을 찾기가 어려운 상황이라서요... -- 귀찮아서가 아닙니다. -_-;
아... 지금 생각하니까.. 입력이 라인 모드라면? 이라는 생각이 드는군요... 모드를 바꾸는 방법이 있나요? 리모트로 외부에서 라인모드로 접속해서 해당 어플리케이션을 실행한다면 문제가 생기지 않을까요?
linux에서 작성할 때 termio 구조체랑 tcsetattr 함수
linux에서 작성할 때
termio 구조체랑 tcsetattr 함수를 사용해서
stdin에 변화가 있는지를 조사하구 stdin에 변화가 있으면 값을 읽어 오구
아니면 0 혹은 기타 값을 리턴하게 하면 while안에서도 사용이
가능하더군요..
굳이 select함수를 사용하지 않더라도..
[quote="bugiii"]궁금한게 하나 더 있는데요. select 후
버퍼링 vs. formatting 을 누가 하게 할것이냐?라는 전략이 필요한데요.
저는 버퍼링은 제가하는 버릇이 있습니다. 그래서 read를 즐겨씁니다.
때로, fread등의 f*시리즈를 사용하기위해 setbuf()시리즈(setbuf,
setbuffer, setlinebuf, setvbuf)를 써서 버퍼를 조절하고 쓰기도 하지만,
저는 개인적으로는, 가물에 콩나듯합니다. (참, 제가 아는 분중에 후자를
선호하시는 분이 계십니다. )
달리 말씀드리자면 자기자신에게 맞는 방법이 최선책이라고 생각합니다.
저도 책 찾기 어려운 상황에서 살고 있습니다. 회사에는 책... 없고, 집에도
책꽂이 비숫한(?) 공간에는 "주부생활", "패션잡지"등 이런 것만 꽂을 수 있습니다.
이상하고! 재미없는! 전산관련 책들은 집 창고, 박스속에...... :cry:
단지 최근 몇달간 구입한 책 몇권만 책상위에 있군요.
Line Discipline (raw, canonical..)관련 말씀을 하시는건지.....
....
어차피 버퍼관리 부터 코딩하자면, 서버가 라인모드를 지원하게 코딩하면 되겠네요.
------------------ P.S. --------------
지식은 오픈해서 검증받아야 산지식이된다고 동네 아저씨가 그러더라.
select를 사용해봤는데cygwin에서는 타임아웃으로 하면 입력체크
select를 사용해봤는데
cygwin에서는 타임아웃으로 하면 입력체크가 안되는 것 같습니다.
timeout을 NULL로 하면 입력 체크가 되지만, 이것은 그냥 fgets 하는 것이나 마찬가지고..
왜그런지 모르겠습니다. -_-;;
rommance.net
소스를 만들어 봤는데...
제대로 작동 안하네요.
원래 의도와는 다르게, 엔터를 치지 않으면, 키를 눌러도 값을 출력 안합니다.
소스에서는 딱히 에러를 못찾겟는데요... -_-;;
누구 에러 보이시는 분.... 지적 부탁드립니다. -_-;;;
행복하시길...
-----------------
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <termios.h>
#include <string.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
int kbhit1( int stdfd );
int main()
{
char c;
int stdfd;
struct termios oldtio, newtio;
stdfd = fileno(stdin);
printf("----\n");
while(1)
{
printf( "%x", c = kbhit1( stdfd ) );
//printf( "%x", c = getchar() );
}
return 0;
}
int kbhit1( int stdfd )
{
fd_set readfs;
struct timeval Timeout;
unsigned char rv;
int res;
while(1)
{
printf(" loop working \n");
FD_SET(stdfd, &readfs);
Timeout.tv_sec = 3;
printf( "select:%d", res = select(stdfd + 1, &readfs, NULL, NULL, &Timeout));
if ( res < 0 )
{ perror(" Error on the select");
return -1; }
else if ( res > 0 )
{ if ( FD_ISSET(stdfd,&readfs) )
{ if ( read(stdfd,&rv,1) < 0 )
{ perror( "Error on read" );
return -1; }
return rv; }
else
return 0;
}
}
}
stdin 의 fd 값을 얻어서 select 를 하더라도,stdin
stdin 의 fd 값을 얻어서 select 를 하더라도,
stdin 자체가 buffered input 이기 때문에 Enter 키가 입력되기 전에는
select 에서 반응을 보이지 않습니다.
(원래 표준이 이런지, 아니면 구현을 이렇게 해 놓은것인지는 모르겠네요)
DOS 시절 conio.h 에 있던(맞던가..6-.-)
kbhit() 를 원하신다면 이런 코드가 있죠.
(이 코드가 아마 여기(KLDP BBS)에서 본 코드였던걸로 기억합니다 -.-;;)
헤더는 fcntl.h, sys/ioctl.h, termios.h 입니다.
kslee80 님이 말씀하신대로 minicom 과 같은 프로그램에서도 로
kslee80 님이 말씀하신대로 minicom 과 같은 프로그램에서도 로우 모드로 변경해서 사용하는것 같습니다.
-- 아쉬운 하루 되세요 --
터미널 제어에 대해서
말씀하시는 걸 유닉스 상에서 구현하려면...
유닉스의 기본 프로그램 철학을 약간 벗어 나는 프로그램이 될 것 같습니다. (예를 들어서 다른 프로그램으로 연결한다든 등)
(원격의)터미널에서 보낸 데이터는 커널의 터미널 드라이브가
버퍼링을 하기때문에 엔터를 받기 전에는 어플리케이션까지
데이터를 보내주지않는군요.
-> 즉 아무리 stdin 파일에 nonblocking걸거나 select해봐야
엔터치기 전까지는 커널이 보내주지 않는군요.
해결하려면 데이터직접 터미널에서 데이터를 읽는 방법밖에 없군요.
가장 간단한 방법은
이런 방법일 듯한데 시스템에 따라서 tty파일명이 달라지니....
절대 사용해선 안돼는 방법입니다.
위의 예보다는 Unix 의 표준 터미널 제어 함수를 사용해서
non-canonical input mode로 변경하는 방법이 옳은 방법일 것 같네요.
윈도우 방식의 입력을 에뮬하는 라이브러리가 여기 있군요...
http://snap.nlc.dcccd.edu/learn/fuller3/chap5/chap5.html
Detecting Keystrokes로 검색하시면 아마 나올 겁니다.
(-.-;; 확실하지 않은 정보지만... ) 도움이 되시길...
[quote="kslee80"]...stdin 자체가 buffer
이 말씀도 좋습니다만, 터미널을 언급할때는,
cannonical mode라고 하는것이 더 어울립니다.
상대적으로 unbuffered보다 raw모드가 어울립니다.
여러 가지를 시험해 봤습니다만...
결국 다 잘 안되더군요. -_-;
우선, 문제점 - 엔터를 치기 전에는 /dev/tty로 입력값을 전달 안하는 - 에 대해서는 이해했습니다. (친절한 설명 감사합니다. ㅎㅎㅎ)
에...그래서,
FILE *inputFile;
inputFile = fopen("/dev/tty", "r"); /* 에러처리 생략 */
input = fgetc(inputFile);
를 비롯한 예제의 방법을 모두 시험해 봤습니다만, 안되더군요.
조금 이상한게, [결국 터미널에서 키보드 입력하면, 이 값이 엔터를 누르기 전까진 버퍼에 전달되지 않는다....] 라는 걸 이해하고,
[그렇다면, NonBlocking 모드로 바꾸어보자]라고 생각해서, 답글에 달린 예제 및, 알고 있는 예제를 동원해 봤는데도, 결국 엔터를 누르기 전까지는 값이 안 나가더군요.
------------------------------
-> 즉 아무리 stdin 파일에 nonblocking걸거나 select해봐야
엔터치기 전까지는 커널이 보내주지 않는군요.
해결하려면 직접 터미널에서 데이터를 읽는 방법밖에 없군요.
-------------------------------
이 말대로... -_-;;;;
근데요. 여기 설명에 써 있는 [직접 터미널에서 데이터를 읽는 방법]이 뭐죠?
---------------------------------
가장 간단한 방법은
코드:
FILE *inputFile;
inputFile = fopen("/dev/tty", "r"); /* 에러처리 생략 */
input = fgetc(inputFile);
---------------------------------------
fgetc 와 read는 읽는 버퍼가 서로 다른가요?
즉, read해도 안 되는 이유가 데이터 자체가 버퍼로 전송이 안 되서 그런걸 텐데, 그걸 fgetc로 하면 해결이 된다...라는게 이해가 안 가더라구요.
(사실 테스트 해서 안되는 거 보고 추측한 겁니다. ^^*)
음... 질문이 장황하게 되었네요. 드릴 말씀이...
참고로, 아래가 테스트 한 예제입니다.
/*헤더 파일 및 선언부 생략*/
int main()
{
printf(" %d %d " , sizeof(char), sizeof( char *) );
char c;
int stdfd;
struct termios oldtio, newtio;
stdfd = fileno(stdin);
TTYNonBlockSetting( stdfd , &oldtio, &newtio );
while(1)
{ if ( read( stdfd, &c, 1 ) > 0) printf(" %x\n", c);
// fflush(stdout);
if (c=='z') break;
}
printf(" finish fgetc \n");
return 0;
}
int TTYNonBlockSetting( int stdfd , struct termios *oldtio, struct termios *newtio )
{
unsigned char rv;
tcgetattr( stdfd, oldtio );
*newtio = *oldtio;
newtio->c_lflag = ~ICANON;
newtio->c_lflag = ~ECHO;
newtio->c_cc[VMIN] = 0;
newtio->c_cc[VTIME] = 0;
fcntl( stdfd, F_SETFL, FNDELAY );
tcsetattr( stdfd, TCSANOW, newtio );
// read(stdfd,&rv,1);
// tcsetattr( stdfd, TCSANOW, &oldtio);
return 1;
}
그리고요. 또 하나더 이상한게,
이건 제가 개념이 없는 건지 몰라도....
처음에는
newtio->c_lflag = ~ICANON;
newtio->c_cc[VMIN] = 0;
newtio->c_cc[VTIME] = 0;
이 세줄만으로 Non-Cannonic mode가 적용될 줄 알았는데...
fcntl( stdfd, F_SETFL, FNDELAY );
이걸 코드에 추가 안 해주면, 안 되더라구요?
제가 뭘 잘못 알고 있는건가요?
답글 주신 모든 분 감사드리구요. 행복한 하루 되시기 바랍니다.
stdin 응답
curses 라이브러리를 쓰시는것도 고려해보세요
(리눅스에서는 ncurses)
wtimeout 함수로 timeout시간을 정해놓고.
밑단에서 wgetch 하시면 됩니다.
키 입력이 바로바로 처리됩니다.
음 오래전 얘기가 리바이벌되었군요.그때 아예 예제도 하나 올릴것을 그
음 오래전 얘기가 리바이벌되었군요.
그때 아예 예제도 하나 올릴것을 그랬나 봅니다.
터미널제어문자열 처리에 대해 조금 공부하시고, telnet등의 터미널제어문자들에 대해 공부해보세요.
그리고, cannonical mode에서 터미널에서 입력된것은 일단 서버컴퓨터로 전송이 됩니다. 서버컴퓨터의 터미널제어루틴에서 유한상태오토마타(FSM)로 코드가 수행됩니다. 버퍼에 넣고, 제어가 끝나기를 기다리게됩니다. 위에 약간 이상한 표현이 있어서 참고삼아 씁니다.
일단, 소스나 올리겠습니다. 질문자님의 소스를 조금 손봐서 올립니다.
프로그램의 종료는 '스페이스바' 즉, 공백문자 되겠습니다.
Raw mode로 진입하게 되어, Ctrl+C도 안먹지요... 당연...
============================
댓글 달기