LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
12
返回列表 发新帖
楼主: romy

stdin 缓冲问题

[复制链接]
发表于 2004-5-18 08:55:49 | 显示全部楼层
正巧在国外论坛也看到了这个话题,供大家参考

unbuffered stdin

Hi, 29 April 2004

I've looked at an awful lot of posted question/answers, FAQs, and web sites, and have tried the methods they suggest, and I'm still stuck.

I'm programming in C++ on Red Hat Linux version 9.

I need to intercept individual characters from stdin as they are typed so that I can do custom line editing within a program. (In other words, I can not wait for the user to enter a newline before the program processes the keystrokes.) I have done this in the past using graphical programs (for instance glut and OpenGL), but I can't find a method that works for the normal shell window.

All the resources have said it is buffering in the terminal program that must be turned off, and that the method is system dependent. The various system calls seem to work without returning an error code, but do not yield a behavior change in the terminal.

I am amazed that this is so hard to find, because it isn't all that unusual of a need. For instance, "more", "top", and other programs display into the shell's teminal window and yet process individual keystrokes as commands. For that matter, so does any shell program. (I'd check the source of one of these programs, but the only way I seem able to get the source is to install the full Linux kernel source, which seems excessive.)

Does anyone have ideas or working code?

The following simple test program demonstrates the capability that I need.
When I run this program now, as I press keyboard keys, nothing is displayed until a newline is entered, and then all the output is written.
What I want:
If any keyboard key is pressed, then the character is immediately written twice.

int main() {
while (true) {
int val = getc(stdin);
cerr << char(val) << char(val);
}
return 0;
}

Thanks,
Mark

P.S. - I may not be able to respond quickly to any questions as I'm traveling for two weeks and unsure of Internet access.


--------------------------------------------------------------------------------
Posted by infamous41md on 04-30-2004 04:55 AM:

you need to mess with termios settings. check out the source code for chapter 11:
http://www.kohala.com/start/apue.html
u need to set it so that terminal driver only waits for a single character before returning. it's all in the source, i reaaaaaly don't feel like looking at it tho

__________________
HOW TO USE CODE TAGS
my site && wishlist
Three bugs every C* programmer should understand:
buffer overflows format strings signed/unsigned still hungry?



--------------------------------------------------------------------------------
Posted by ToniT on 04-30-2004 05:05 AM:

See the ncurses programming howto on how to code unbuffered keyhandler.

The sources of more, top can be obtained without installling the full linux source code.

If you are doing commandline editing, I suggest using readline library.
Not just to avoid reinventing the wheel, but that way your program is having a consistent
look&feel with other terminal programs (bash, tcsh, ncftp, lftp, octave, ahcd, parted, smbclient, mysql/postgres-client, R, devtodo, cle and abook just to mention few I use that use readline).


--------------------------------------------------------------------------------
Posted by mvt on Today 04:00 AM:

Thank you both for the help. 17 May 2004

infamous41md's suggestion led me to searching about
termios. A web page (quoted below) helped with
the needed syntax. Result: a solution.

In the interest of closure and to assist other readers,
attached below is my working C++ test program (it
consists mostly of comments).

Thanks,
Mark

/*
The following 3 commands compile this on my system.
/bin/rm -f stdin_test.o stdin_test
g++ -c stdin_test.cc
g++ stdin_test.o -lcurses -o stdin_test
*/

// program accepts charaters one at a time from stdin
// without waiting for a newline (unbuffered)
// based on: http://www.erlenstar.demon.co.uk/unix/faq_4.html
//
// Modify the values of vtime and vmin (at appoximately line 62, below)
// to try different modes. The current values are: vtime = 1 and vmin = 0
// which allows the main program to poll for characters. Using vtime=1
// intentionally slows the speed of the main loop, changing vtime to zero
// will greatly increase the speed of the main loop.
//
// On my system, the some keys are still intercepted:
// "F1" calls up the "Gnome Terminal manual"
// "F10" opens a menu
// "rint Screen" runs a screen capture program
// "Scroll Lock" is intercepted, unknown action.
// "ause" is intercepted, unknown action.
// "Num Lock" toggles status light (but not received values)
// keypad "home", "End", "gUp", and "gDn" scroll the terminal

#include <iostream>
#include <termios.h>

using namespace std;

// ---------------------------------------------------------------------------
void set_keypress(termios& stored_settings) {
// change the terminal settings to return each character as it is typed
// (disables line-oriented buffering)
// returns the original settings in the argument structure

// obtain the current settings flags
tcgetattr(0, &stored_settings);

// copy existing setting flags
termios new_settings = stored_settings;

// modify flags
// first, disable canonical mode
// (canonical mode is the typical line-oriented input method)
new_settings.c_lflag &= (~ICANON);
new_settings.c_lflag &= (~ECHO); // don't echo the character
new_settings.c_lflag &= (~ISIG); // don't automatically handle control-C

// vtime and vmin setting interactions are complex
// both > 0
// Blocks until it has first new character, then tries to get a total
// of vmin characters, but never waits more than vtime between characters.
// Returns when have vmin characters or the wait for next character is
// too long.
// vtime = 0, vmin > 0
// Blocks until vmin characters received (or a signal is received)
// vtime > 0, vmin = 0
// If a character is ready within vtime, it is returned immediately.
// If no character is avalable within vtime, zero is returned.
// both = 0
// Documentation somewhat unclear, but apparently returns immediately
// with all available characters up to the limit of the number
// requested by a read(). Returns -1 if no characters are available.
new_settings.c_cc[VTIME] = 1; // timeout (tenths of a second)
new_settings.c_cc[VMIN] = 0; // minimum number of characters

// apply the new settings
tcsetattr(0, TCSANOW, &new_settings);

// note:
// The return value from tcsetattr() is not tested because its value
// reflects "success" if any PART of the attributes is changed, not
// when all the values are changed as requested (stupidity!).
// Since the content of the termios structure may differ with
// implementation, as may the various constants such as ICANON, I see
// no elegant way to check if the desired actions were completed
// successfully. Comparing byte-by-byte shows the current state is
// NOT EQUAL to the requested state, and yet it runs, so the changes
// were apparently made. Can not check for success/failure.
}
// ---------------------------------------------------------------------------
void reset_keypress(const termios& stored_settings) {
// applies the terminal settings supplied as the argument
tcsetattr(0, TCSANOW, &stored_settings);
}
// ---------------------------------------------------------------------------
int main() {
// save the previous settings while modifying the current settings
termios stored_settings;
set_keypress(stored_settings);

cout << "ress keys... exit with a control-C." << endl;

// loop to poll for characters
while (true) {
int val = getc(stdin);
cerr << " " << val << endl;
if (val == 3) {
cout << "Saw control-c, exiting." << endl;
// return to original settings
reset_keypress(stored_settings);
exit(0);
}
}
return 0;
}
// ---------------------------------------------------------------------------


--------------------------------------------------------------------------------
Posted by ToniT on Today 05:53 AM:

Same with ncurses:


code:#include <ncurses.h>
int main(void) {
  int ch;
  initscr();
  cbreak();
  for(;;) {
    ch=getch();
    printw("%c",ch);
  }
  endwin();
}



Compile the program with flags: -lncurses
eg. gcc -Wall -lncurses wkeyc.c -o wkey


--------------------------------------------------------------------------------
Posted by Hko on Today 05:55 AM:

This thread may be of some help too:

http://www.linuxquestions.org/quest...;threadid=34027


--------------------------------------------------------------------------------
Posted by mvt on Today 08:23 AM:

In reply to ToniT's post (#5).
Thanks for the response.

I had tried ncurses when you first suggested it, and tried your code again just now. It works reasonably well except that initscr() creates a new display surface which is used for program output and which disappears upon program completion. I need to be able to see the output from previous comands so I can cut and paste text into my program... and after the program exits I need to be able to cut and paste text output by the program into other programs.

I could not figure out how to prevent getting a new display surface, although I suspect it is possible.

Other issues, perhaps also possible. (I've forgotten what I read in the ncurses docs, because I've looked at so many programs recently.)
I prefer to handle control-Z and control-C myself.
I prefer no automatic echo of characters.
The display no longer scrolls when text fills it.

Mark

发表于 2004-5-18 11:42:58 | 显示全部楼层
well,也是利用termios来改变终端特性。
其实这是Unix的老做法了,大家可以自己试试看。
我在此给出我自己的总结。
ps:我喜欢shell,所以特别留意终端编程,大家有空可以看看bash的源码,赏心悦目。:cool:


MIN以及TIME有四种取值可能,分别代表了四种非规范模式。
1.MIN和TIME都等于0
read总是立即返回。输入字符会实时出现进程的缓冲区,上下限由read的参数决定。
2.MIN大于0,TIME等于0
read在输入MIN个字符后返回,上下限都是MIN。适用于终端键盘的读取。
3.MIN等于0,TIME大于0
read在第一个字符输入后返回,超时后,返回0字符。适用于楼主的要求,也就是实时响应。
4.MIN和TIME都大于0
接收到第一个字符开始计时,如果在超时之前接收到MIN个字符,read调用返回;若超时,输入的当前字符被返回。

其实楼主也可以用1这种非规范模式来完成任务,试试看。
 楼主| 发表于 2004-5-18 11:52:35 | 显示全部楼层
MIN和TIME都等于0, 不等俺按键read就已经返回了。
只好用: echo y | a.out

嘿嘿。
不过意思已经明白了,多谢多谢。
发表于 2004-5-20 09:36:03 | 显示全部楼层
最初由 romy 发表
是的,因为这个程序退出时没有恢复TERM的设置,该终端在以后的运行中仍然保持实时响应特性。
另外,也可以通过命令行来达到同样的效果。
stty -icanon (具体记不大清楚,man stty 即可)

以前偶用过curses.h里头的一些函数做过类似的事情,不过时间长了,也不常用,忘光了:))

stty cbreak #开启实时响应模式
stty -cbreak #关闭实时响应模式
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表