密码输入在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环境高级编程》上的原话,呵呵,总算理解了。的确,学东西要触类旁通!!!

来说两句吧

在评论中,你可以使用以下标签: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>