密码输入在Linux上的实现
10 25, 2008 Linux
本例的demo还是接着那个链表测试,以下是在Linux上的实现。功能很简单,实现密码输入,输入密码时显示*,按backspace键则删除前一个输入字符,功能相对比较简单。以下是代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <termios.h>
#include <sys/types.h>
#define _NAME_MAX 32
#define _PASSWD_MAX 32
#define _INPUT_MIN 4
struct Jilu
{
char *pUserName;
char *PassWord;
char Flag; /* 如果仅是0,1,字符就可以处理了 */
struct Jilu *next;
};
void InputPassword(char *passwd, unsigned int nInputLimit);
int mygetch(void);
static FILE *fp;
int main(void)
{
struct Jilu *prev_userinfo, *curr_userinfo, *head, *temp;
char tty_path[256] = {0};
int i, user_toal;
char chTemp;
printf(”terminal is %s…\n”, ttyname(STDIN_FILENO));
if ( (fp = fopen(ttyname(STDIN_FILENO), “r+”)) == NULL)
return -1;
printf(”Please input user total:”);
scanf(”%d”, &user_toal);
for (i=0; i<user_toal; ++i)
{
curr_userinfo = malloc(sizeof(struct Jilu));
curr_userinfo->pUserName = NULL;
curr_userinfo->PassWord = NULL;
curr_userinfo->Flag = 0;
if (0 != i) /* 不是第一个节点 */
prev_userinfo->next = curr_userinfo;
else
head = curr_userinfo; /* 记下头节点 */
prev_userinfo = curr_userinfo; /* 保存当前指针 */
}
prev_userinfo->next = NULL; /* 最后一个节点 */
/* 为链表中字符串指针成员分配空间 */
curr_userinfo = head;
while (curr_userinfo != NULL)
{
curr_userinfo->pUserName = malloc( (_NAME_MAX+1)*sizeof(char));
curr_userinfo->PassWord = malloc( (_PASSWD_MAX+1)*sizeof(char));
memset(curr_userinfo->pUserName, 0, (_NAME_MAX+1)*sizeof(char));
memset(curr_userinfo->PassWord, 0, (_PASSWD_MAX+1)*sizeof(char));
curr_userinfo = curr_userinfo->next; /* 指向下一个节点 */
}
/* 向链表写数据 */
curr_userinfo = head;
while (curr_userinfo != NULL)
{
printf(”Please input name:”);
scanf(”%s”, curr_userinfo->pUserName);
chTemp = getchar();
printf(”Please input password:”);
InputPassword(curr_userinfo->PassWord, 32);
printf(”Please input flag:”);
scanf(”%d”, &curr_userinfo->Flag);
curr_userinfo = curr_userinfo->next;
}
/* 读数据 */
curr_userinfo = head;
while (curr_userinfo != NULL)
{
printf(”%s\n%s\n%d\n\n”, curr_userinfo->pUserName,
curr_userinfo->PassWord, curr_userinfo->Flag);
curr_userinfo = curr_userinfo->next;
}
/* 释放空间 */
for (curr_userinfo=head; curr_userinfo!=NULL; curr_userinfo=temp)
{
/* 首先释放成员占用堆内存 */
free(curr_userinfo->pUserName);
free(curr_userinfo->PassWord);
temp = curr_userinfo->next; /* 保存next域 */
/* 释放节点所占堆内存 */
free(curr_userinfo);
}
return 0;
}
void InputPassword(char *passwd, unsigned int nInputLimit)
{
struct termios old_termios, new_termios;
char chTemp;
int nInputlen = 0;
if (passwd == NULL)
return;
while (1)
{
chTemp = mygetch();
if (chTemp >= ‘0′ && chTemp <= ‘9′ && nInputlen < nInputLimit)
/* 只接受数字 */
{
printf(”*”);
fflush(stdout);
*passwd++ = chTemp;
nInputlen ++;
}
else if (chTemp == 0×7f) /* in Linux press backspace,the ASCII is 0×7f */
{
if (nInputlen > 0)
{
printf(”\b \b”);
fflush(stdout);
nInputlen –;
passwd –;
}
else
{
putchar(’\a’);
}
}
else if (chTemp == ‘\n’ || chTemp == ‘\r’)
{
if (nInputlen > _INPUT_MIN && nInputlen < nInputLimit)
{
*passwd = ‘\0′;
printf(”\n”);
break;
}
else
{
printf(”\nYour input is not correct,please input %d~%d numbers…\n”,
_INPUT_MIN, nInputLimit);
printf(”Please input password again:”);
passwd -= nInputlen;
nInputlen = 0;
}
}
else
{
printf(”\a”);
}
}
return ;
}
int mygetch(void)
{
int ch;
struct termios old_termios, new_termios;
/* fetch the current attribute… */
if (tcgetattr(fileno(fp), &old_termios) < 0)
return -1;
new_termios = old_termios; /* copy the attribute… */
new_termios.c_lflag &= ~ECHO; /* shut down echo */
/* let system deal with erase autolly… */
//new_termios.c_lflag &= ~(ECHOE|ICANON);
//new_termios.c_lflag |= (ECHOE|ICANON);
/* make terminal weork in noncanon mode… */
new_termios.c_lflag &= ~ICANON;
new_termios.c_cc[VMIN] = 1; /* 1 byte at a time, no timer… */
new_termios.c_cc[VTIME] = 0;
if (tcsetattr(fileno(fp), TCSANOW, &new_termios) < 0)
return -1;
ch = getchar();
/* recover the old term attribute… */
if (tcsetattr(fileno(fp), TCSANOW, &old_termios) < 0)
return -1;
return ch;
}
我们主要看mygetch函数,Linux系统没提供getch函数,该函数不回显输入字符。事实上Linux给我们编程者提供了一种更为灵活的方式来实现改函数,windows的控制台显然不能跟Linux的终端相提并论,Linux系统对终端的支持可谓既强大又灵活,给编程者带来很多乐趣。话不多说,下面阐述一下我几个小时的一点成果。new_termios.c_lflag &= ~ECHO; 这一语句是关闭回显,这是必须的,但是仅仅做到这个是不行的,经过测试就会知道当流涉及到终端时会典型的使用行缓存,虽然我们多次调用mygetch函数,但是终端驱动程序不会调用返回,因为终端使用行缓存,所以我们必须立刻想到:如何实现读一个字节就使终端驱动程序返回呢(这同时也是程序的需求),我试过下面这几个方法,调用read这个系统调用读1个字节,ch = getchar();改为read(STDIN_FILENO, &ch, 1);(同时把ch声明为char,这个其实没什么必要),可是结果还是一样,不会读一个字节终端驱动程序就返回;另外一个方法,我调用标准I/O库的修改缓存函数,使用setbuf跟setvbuf函数修改标准输入缓存,得到的结果还是一样,终端驱动程序并不领情,这几招都不奏效,说明这些行为并不能影响到终端驱动程序,那只好用修改终端属性的方法来实现。了解Linux终端的人都知道,终端I/O有两种不同的工作方式:(1) 规范方式输入处理。在这种方式中,终端输入以行为单位进行处理。对于每个读要求,终端驱动程序最多返回一行。(2) 非规范方式输入处理。输入字符不以行为单位进行装配。我们现在需要终端驱动程序读一个字节就返回,这样我们不能使用规范方式,必须使终端处于非规范方式下工作。通过屏蔽掉终端特性结构体termios(该结构体在bits/termios.h中有定义)c_lflag中的ICANON即可使终端工作在非规范方式下,new_termios.c_lflag &= ~ICANON;这条语句做到这点,好了,接下来的两条语句new_termios.c_cc[VMIN] =1; new_termios.c_cc[VTIME] = 0;使得终端阻塞直到读到一个字节为止,这样我们就完成了读一个字节就返回这一功能;接下来,如何处理输入backspace键就去掉前一个输入的字符呢,Linux终端同样提供对这个的支持,new_termios.c_lflag |= (ECHOE|ICANON); 这条语句使终端从显示中擦除当前行中的最后一个字符,终端驱动程序通过写三个字符序列:退格,空格,退格实现,注意到没有,要使该功能成立还必须使终端工作在规范方式下,这不就跟前面要处于非规范方式下冲突了吗?所以得自己实现,首先我们必须要知道按下backspace键对应的ASCII码值是多少,我以为是’\b’,在InputPassword函数中判断退格键的语句中写了’\b’,按退格键一点反映没有,到网上查了好像都讲是’\b’,后来想用delete键取代退格键实现删除前一字符功能,主要源于vi编辑器按del键可以删除前一个字符,查了下,对应ASCII是0×7f,把’\b’改成0×7f,可是按delete也没有反映,顺便按了一下退格,既然可以了?!原来退格键的ASCII码被Linux解释为0×7f!其实vi编辑程序也使用非规范方式,其原因是其命令是由不以新行符终止的一个或几个字符组成的,《UNIX环境高级编程》上的原话,呵呵,总算理解了。的确,学东西要触类旁通!!!
Tags: 终端,缓存,标准I/O



来说两句吧