曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 14926|回复: 14
打印 上一主题 下一主题

终端接口(Terminal interface)

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34399
跳转到指定楼层
楼主
发表于 2009-3-4 18:10:30 | 只看该作者 回帖奖励 |正序浏览 |阅读模式
通过调用一组称之为“通用终端接口”(General terminal interface,or GTI)的函数可以很好的对终端进行操作。这些函数的调用和对数据进行读写的函数调用是有区别的。这使得数据的(读/写)接口很干净,同时仍然允许对终端的行为进行更细微的调控。这话不是说终端的 I/O 接口上空无一物,相反的,它可以和各种千变万化的硬件设备打交道。

用 UNIX 的语言来说,控制接口设置了一个“行业帮规”(line discipline),这使得程序在定义终端驱动的操作行为方面给予了极大的灵活性(considerable flexibility)。

可以控制的主要功能:

行编辑(line editing):选择是否允许用 backspace 键进行编辑

缓冲(buffering): 选择是否立即读取字符还是在一个可配置的延迟后再读

回显(Echo):允许控制回显,如读取密码时

CR/LR:为输入/输出所做的映射:当打印一个换行符(\n, line feed character)时因该如何处理

线速度(line speed):在 PC 控制台上很少用到,这个线速度对于调制解调器和串行线上的终端就很重要了

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34399
15#
 楼主| 发表于 2009-3-8 17:09:45 | 只看该作者

菜单-4(终端控制示例)

/*程序功能:读取每一个字符*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <termios.h>

char *menu[] = {
        "a - add new record",
        "d - delete record",
        "q - quit",
        NULL,
};

int getchoice(char *greet, char *choices[], FILE *in, FILE *out);

int main()
{
        int choice = 0;
        FILE *input;
        FILE *output;
        struct termios initial_settings, new_settings;

        if(!isatty(fileno(stdout))) {
                fprintf(stderr, "You are not a terminal, OK.\\n");
        }

        input = fopen("/dev/tty", "r");
        output = fopen("/dev/tty", "w");
        if(!input || !output) {
                fprintf(stderr, "Unable to open /dev/tty\\n");
                exit(1);
        }

        /*进行终端各个参数的设置*/

        /*获取与标准输入相关联的终端控制属性*/
        tcgetattr(fileno(input), &initial_settings);

        new_settings = initial_settings; /*转存*/
        new_settings.c_lflag &= ~ICANON; /*非标准模式*/
        new_settings.c_lflag &= ~ECHO;  /*不回显*/
        new_settings.c_cc[VMIN] = 1;    /*读到1个字符时就返回*/
        new_settings.c_cc[VTIME] = 0;   /*不设置时间影响*/
        new_settings.c_lflag &= ~ISIG;  /*终端不响应信号*/

        if(tcsetattr(fileno(input), TCSANOW, &new_settings) != 0) {
                fprintf(stderr, "could not set attributes\\n");
        }

        do {
                choice = getchoice("Please select an action", menu, input, output);
                printf("You have chosen: %c\\n", choice);
        } while(choice != 'q');

        tcsetattr(fileno(input), TCSANOW, &initial_settings);
        exit(0);
}

int getchoice(char *greet, char *choices[], FILE *in, FILE *out)
{
        int chosen = 0;
        int selected;
        char **option;

        do {
                fprintf(out, "choice: %s\\n", greet);
                option = choices;
                while(*option) {                    /*打印menu菜单*/
                        fprintf(stdout, "%s\\n", *option);
                        option++;
                }

                do {
                        selected = fgetc(in); /*读取一个字符,遇到回车或换行则继续读取*/
                } while(selected == '\\n' || selected == '\\r');
                option = choices;
                while(*option) { /*查询输入的字符和menu中的首字符是否一样*/
                        if(selected == *option[0]) {
                                chosen = 1;
                                break;
                        }
                        option++;
                }
                if(!chosen) { /*错误选择*/
                        fprintf(out, "Incorrect choice, select again\\n");
                }
                } while(!chosen); /*错误选择则需重新输入*/

                return selected; /*输入正确返回选择结果*/
}
运行及输出:
[root@localhost C]# ./password-2.exe
choice: Please select an action
a - add new record
d - delete record
q - quit
You have chosen: a  #每次输入都会理解响应
choice: Please select an action
a - add new record
d - delete record
q - quit
You have chosen: d
choice: Please select an action #这里按下 Ctrl+C 测试,结果程序并不相应信号,这是因为程序中的 ~ISIG 的缘故
a - add new record
d - delete record
q - quit
Incorrect choice, select again
choice: Please select an action
a - add new record
d - delete record
q - quit
You have chosen: q

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34399
14#
 楼主| 发表于 2009-3-7 22:59:33 | 只看该作者

用 termios 来控制密码输入

测试程序:
#include <termios.h>
#include <stdio.h>
#include <stdlib.h>

#define PASSWORD_LEN 8

int main()
{
        struct termios initialrsettings, newrsettings; /*声明两个 termios 型数组*/
        char password[PASSWORD_LEN + 1]; /*密码存储数组*/

        tcgetattr(fileno(stdin), &initialrsettings); /*得到相应终端控制参数*/

        newrsettings = initialrsettings; /*initialrsettings为备份,newrsetting为需更改的参数*/

        newrsettings.c_lflag &= ~ECHO; /*屏蔽回显*/

        printf("Enter password: ");

        if(tcsetattr(fileno(stdin), TCSAFLUSH, &newrsettings) != 0) {
                fprintf(stderr, "Could not set attributes\\n");
        }
        else {
                fgets(password, PASSWORD_LEN, stdin);
                tcsetattr(fileno(stdin), TCSANOW, &initialrsettings); /*恢复*/
                fprintf(stdout, "\\nYou entered %s\\n", password);
        }

        exit(0);
}
运行及输出:
[root@localhost C]# ./password.exe
Enter password:         #这里输入密码不回显
You entered love

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34399
13#
 楼主| 发表于 2009-3-7 16:22:37 | 只看该作者

其它功能函数

在控制终端方面还有几个函数。这些函数直接对文件描述符进行操作,不需要读写 termios 结构,定义如下:
#include <termios.h>
int tcdrain(int fd);
int tcflow(int fd, int flowtype);
int tcflush(int fd, int in_out_selector);

函数有以下几点功能:

tcdrain 让调用程序等待直到队列输出全部发送完毕

tcflow 用来挂起或重启输出

tcflush 用来清空(flush)输入,输出或者两者

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34399
12#
 楼主| 发表于 2009-3-7 15:02:08 | 只看该作者

终端的速度(Terminal speed)

termios 提供的最后一个功能是对线速度进行操控。termios 结构体中没有为终端速度做定义的成员。它由函数调用来进行设置。输入和输出是分开处理的。

四个函数原型:
#include <termios.h>
speed_t cfgetispeed(const struct termios *);
speed_t cfgetospeed(const struct termios *);
int cfsetispeed(struct termios *, speed_t speed);
int cfsetospeed(struct termios *, speed_t speed);

注意:这些函数是对 termios 结构进行操作的,并不是直接作用于通讯端口。这意味着要想设置一个新的速度,就必须先用 tcgetattr() 函数读出当前的设置值,用上面四个函数中其中一个来设置速度,然后用 tcsetattr() 函数写回 termios 结构。只有在 tcsetattr() 函数结束之后,线速度才会改变。

上面4个函数可以为速度设置各种各样的值,但一下几个最为重要:
B0:挂起终端
B1200:1200 波特率
B2400:2400 波特率
B9600:9600 波特率
B19200:19200 波特率
B38400:38400 波特率

有关标准,没有为超过 38400 波特率的速度定义,也没有支持串行口工作大于此速度的标准方法。

一些系统,包括 linux,为了可以选择更快的速度,还定义了 B57600, B115200, B230400。如果现在正在使用的是早期版本的 linux,里面没有对这些函数进行定义,你可以使用 setserial 命令获得非标准的速度--57600 和 115200。在这种情况下,只有当选择 48400 时才能使用这些非标准速度。这些方法都不具备可移植性,所以在使用的时候要很小心。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34399
11#
 楼主| 发表于 2009-3-7 11:47:25 | 只看该作者

从命令提示符对终端进行设置

你也可以用 stty 命令从命令提示符那里直接设置终端模式.

设置一个终端工作模式,比如你想在你的脚本里可以执行“对单个字符的读取”,你需要关闭掉标准模式并设置 min 为 1 以及 time 为 0.
如下所示命令:
stty -icanon min 1 time 0

现在这个终端已经设置为对字符立即读的模式,可以试图运行一下 http://www.groad.net/bbs/read.php?tid=620 中的 menu-1.exe 这个程序.运行效果如下:
[root@localhost ~]# stty -icanon min 1 time 0
[root@localhost ~]# cd C
[root@localhost C]# ./menu-1.exe
Choice: Please select an cation
a - add new record
d - delete record
q - quit
aYou have chosen: a   #这里,在敲入 a 时,程序马上显示出 You have chosen: a 而不需要输入回车
Choice: Please select an cation
a - add new record
d - delete record
q - quit
dYou have chosen: d
Choice: Please select an cation
a - add new record
d - delete record
q - quit
注意的是,在桌面系统中,如用 ctrl+shift+T 新建立一个终端窗口,那么在这个新的终端窗口里再执行程序则还是以原来的配置状态(需要回车)执行,也就是说这些设置只对当前的窗口有效。

那再把终端模式恢复回来看看(之前已经用 stty save_stty 命令做过备份):
[root@localhost C]# stty $(cat ../save_stty)
[root@localhost C]# ./menu-1.exe
Choice: Please select an cation
a - add new record
d - delete record
q - quit
a     #这里,输入 a 后需要再输入回车才可以继续运行程序
You have chosen: a
Choice: Please select an cation
a - add new record
d - delete record
q - quit

又比如,在一个提示输入口令的程序,可以设置关闭掉输入口令回显,如执行以下命令:
stty -echo
使用完毕后,可以用下面的命令恢复之:
stty echo   #可见,有个 - 号就是关闭,没有 - 号就恢复

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34399
10#
 楼主| 发表于 2009-3-6 23:29:44 | 只看该作者

从 shell 里访问终端模式

如果你正在使用 shell 而你又希望看到 termios 的设置,可以使用如下命令:
[root@localhost ~]# stty -a
speed 38400 baud; rows 24; columns 80; line = 0;
intr = ^C; quit = ^\\; erase = ^?; kill = ^U; eof = ^D; eol = M-^?; eol2 = M-^?;
swtch = M-^?; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W;
lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 hupcl -cstopb cread -clocal -crtscts
-ignbrk brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff
-iuclc ixany imaxbel iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt
echoctl echoke

在上面,可以看到 EOF 字符对应着 Ctrl+D(eof = ^D) 并且使能了回显功能(echoe). 在做终端控制实验时,很容易把终端弄成一个非标准状态, 这就会造成使用起来很困难. 但是有几个办法可以使你摆脱(way out)这个困境:

第一个方法,如果你的 stty 版本支持的话,使用以下命令:
stty sane

如果你把回车键和换行符(用来结束命令行)之间的映射关系搞乱了, 你可以先键入 stty sane, 然后按入 Ctrl+J(这个组合键对应着换行符) , 而不是按下回车.

第二个方法是使用 stty -g 命令把当前的 stty 设置保存起来,在必要时再把原始值再读出来进行恢复.可以如下这么做:
stty -g > save_stty
..
<修改终端设置>
..
stty $(cat save_stty)
上面,最后一条 stty 命令还是需要用 Ctrl+J 而不是回车键.在 shell 脚本程序里可以使用同样的技巧:
save_stty = "$(stty -g)"
<alter stty setting>
stty $save_stty

如果以上的这些招数都不管用了(if you're really stuck),第三个办法是打开一个不同的终端,使用 ps 命令找到你那个你弄得无法使用的 shell, 然后使用 kill HUP <process id> 强制把这个 shell 终止掉.因为 stty 参数总是在系统给出登录提示符前被重置,所以你能够在 stty 为初始值的情况下再一次正常的登录上机.

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34399
9#
 楼主| 发表于 2009-3-6 18:08:07 | 只看该作者

TIME 和 MIN 值

TIME 和 MIN 的值仅用在非标准模式下并且两者结合起来控制输入的读取。此外,它们还控制当一个程序试图去读一个与一终端相关联的文件描述符时将会发生什么样的事情。

这里有四种情况:

1、MIN = 0 和 TIME = 0

在这种情况下, read 将总是立即返回。如果有等待处理的字符,它们就会被返回。如果没有可供处理的字符,read 将什么也读不到并返回 0 .

2、MIN = 0 和 TIME > 0

在这种情况下,只要有效字符一出现或经过 TIME 个十分之一秒,read 调用就会立刻返回。如果因为超时而没读到字符,read 调用就返回 0。否则,它就返回读到的字符个数。

3、MIN > 0 和 TIME = 0

在这种情况下,read 调用将等到读取到 MIN 个字符时才返回,并且返回的是读到的字符个数。到达文件末尾时返回 0。

4、MIN > 0 和 TIME > 0

这种情况比较复杂。当调用 read 时,它等待接收到一个字符。当接收到第一个字符以及后续各个字符时,会启动一个字符时间间隔计时器(如果计时器已经启动就会重启它)。read 调用在 MIN 个字符准备好或在超出 TIME 个十分之一秒的字符间隔时间时就会返回。这对于区分“单独按下 Escape 键”和“一个功能键转义序列(scape sequence)的开始”是很有用的。需要注意的是,网络通讯和高处理器负荷会把这种精细安排的时间信息清除掉。

通过设置非标准模式和使用 MIN 与 TIME 值,程序可以对输入的字符进行逐个的处理。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34399
8#
 楼主| 发表于 2009-3-5 23:25:50 | 只看该作者

本地模式(local modes)

这些模式控制终端的不同特性.通过设置 termios 结构体中的 c_lfalg 成员来指定本地模式.相应的宏意义如下:

ECHO : 使能输入字符本地回显

ECHOE : 在收到 ERASE 时执行后退(Backspace), 空格(Space), 以及后退空格的动作组合

ECHOK : 在接收到 KILL 字符时执行行删除动作

ECHONL : 回显换行符

ICANON : 使能标准输入处理

IEXTEN : 使能实现特定函数(implementation-specific)功能

ISIG : 使能信号

NOFLSH : 禁止清空队列动作

TOSTOP : 在试图进行写操作之前给后台进程发送一个信号

两个比较重要的标志是 ECHO 和 ICANON .

ECHO 允许强制回显用户敲入的字符;

ECANON 在两个明显不同的处理接收字符模式间对终端进行切换. 如果置位了 ICANON 标志, 就表示该输入行处于授权处理模式(canonical mode),否则处于非授权处理模式(non-canonical mode).

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34399
7#
 楼主| 发表于 2009-3-5 22:57:27 | 只看该作者

控制模式(control mode)

控制模式控制着终端的硬件特性。通过设置 termios 结构体中的 c_cflag 成员来指明控制模式,相关宏意义如下:

CLOCAL: 忽略任何调制解调器状态行

CREAD: 激活字符接收回执(receipt)功能

CS5: 在发送和接收的字符中使用 5 个二进制位

CS6: 在发送和接收的字符中使用 6 个二进制位

CS7: 在发送和接收的字符中使用 7 个二进制位

CS8: 在发送和接收的字符中使用 8 个二进制位

CSTOPB : 每个字符使用两个而不是一个停止位

HUPCL: 关闭操作时挂断调制解调器

PARENB : 激活奇偶校验(校验码)的检测和生成

PARODD : 使用奇校验而不是偶校验

注意: 如果 HUPCL 设置,当终端驱动检测到与终端对影的最后一个文件描述符关闭,它会设置调制解调器控制线挂断电话线路。

虽然控制模式可能用来与终端会话,但是其主要还是用在串行线和调制解调器连接的这种情况里。一般说来(normally),改变终端的配置比通过使用 termios 控制模式改变默认的线路连接行为会相对容易一些.
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2026-10-11 18:41 , Processed in 0.083319 second(s), 22 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

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