Linux下使用消息队实现 ATM 自动取款机功能

文章分五部分:需求分析、项目所需知识点、思路讲解、代码实现、功能演示
本文内容较长,建议是按照我自己的思路给大家讲解的,如果有其他问题,欢迎评论区讨论
文章中的代码是在linux下编译实现的,注意自己的环境。

(一)需求分析

我们的ATM终端要求实现下面的功能:

  • (1)开户
  • (2)存款
  • (3)取款
  • (4)查询
  • (5)转账
  • (6)退出
  • (7)销户

(二)项目相关知识点

  • 文件操作
  • 进程通信----消息队列
  • 模块编程
  • 数据库
    上面的内容不再讲解,根据代码可以查看相关函数的功能。

(三)思路讲解

1、实现功能用到进程间通信
(1)通信要求两个进程实现
            两个进程分别完成什么任务??
            站在用户的角度去考虑这个问题:
            例如:你去银行开户的场景
            你:我要开户
            银行职员:请出示你的相关信息
            你:交出身份证 银行职员:开始一系列的操作(录入信息,开账户等),紧接着要求你输入密码
            你:输入密码
            银行职员:请确认密码
            你:再次输入
            银行职员:银行卡余额不能为空,请存款
            你:交出100元
            银行职员:拿走你的钱,在你的账户输入100
            ............
            ............
            从这个角度来讲你和银行职员就是两个不同的进程,你们分别的任务有:
            银行职员根据你的提示信息为你开户、存款、取款、查询、转账、退出、销户操作,而你要做的就是给出相应的操作提示
(2)进程间通信的选取
消息队列在进程通信比较有代表性,我们在这里使用消息对列。

2、进程任务分解

可以将进程分为客户端进程和服务端进程,分别的任务有:
服务端进程

   1) 首先用msgget函数创建两个消息队列
        消息队列1:存放客户端进程发来的各种请求
        注意:根据操作的不同,会发送不同类型的消息,可以自己定义消息的类型,另外可以将功能函数作为一个模块,这样会使得程序逻辑比较清晰
            
        消息队列2:存放服务端进程回复给客户端进程的消息
  2)利用fork函数创建进程
            父进程阻塞就行,子进程通过exec函数来调用函数功能模块的可执行文件
  3)捕获用户发来的Ctrl+c的信号,结束进程 并删除消息队列
            通过exit函数和msgctl功能函数实现
            
        模块功能编写:
            1)开户操作
                首先需要一个文件来保存用户的信息,同时检测该文件是否已经存在---------------注意:银行账号不能重复
                成功创建文件后将客户端进程发来的用户信息写入文件
                返回成功的消息类型给客户端
                
            2)存款操作
                根据用户信息找到对应的文件,将文件内的关于金额的值加上相应的取款金额
            3)取款操作
                根据用户信息找到对应的文件,将文件内的关于金额的值减去相应的取款金额
            4)转账操作
                转账操作需要输入两个先关的用户信息,之后就是取款操作和存款操作的结合
            5)删除账户操作
                根据提示信息,找到相对应的文件,先要情况账户中的余额才能注销账户

客户端进程

可以先写一个界面,类似C语言的学员管理系统
根据界面的操作提示选择相对应的功能
操作有:
        (1)开户
        (2)存款
        (3)取款
        (4)查询
        (5)转账
        (6)退出
        (7)销户
实现:首先要获取消息队列
        1)开户操作
            开户的时候榕湖需要输入用户的相关信息(账户、密码、存款金额)
            为了操作简便可以通过结构体来封装用户信息
            输入信息操作后将消息发送到消息队列,并指定消息类型
        2)存款
            向指定的账户存入指定的金额
            先输入用户相关信息
            将信息发送到消息队列,并指定消息类型
        3)取款
            先输入用户相关信息和取款金额
            将信息发送到消息队列,并指定消息类型
            
        4)查询
            先输入用户相关信息
            将信息发送到消息队列,并指定消息类型
        5)转账
            先输入当前用户相关信息和要转账到的用户信息
            将信息发送到消息队列,并指定消息类型
        6)转账
            先输入销毁用户信息
            将信息发送到消息队列,并指定消息类型
以上操作只需要简单的录入信息和发送消息到消息队列,如同你现在按取款机上对应的功能键,具体操作在客户端调用的功能模块实现

(四)代码实现

由于代码量比较大这里贴出客户端和服务端的代码,其余的代码在链接资源里有需要的自行下载
client.c

/************************************************************************
*
* 文件名:cLient.c
*
* 文件描述:模拟银行终端客户端
*
* 创建人:Liu ming 2020/2/13
*
* 版本号:2.0
*
* 修改记录:
*
************************************************************************/
#define DEBUG  1 //调试定义宏
#include 
#include 
#include 
#include 
#include "msg_queue.h"
//#define   CLIENT     0
#ifdef  CLIENT
int show_flag = 0;//0--未登录界面  1--登录后界面
int sig_flag =0;//选择的功能id号
int msg_sendid =0,msg_recvid=0;//消息队列id
struct signal signal_t= {0,0}; //初始化进程通信信号

/*================================================================
*
* 函 数 名:Ctrl_c
*
* 参 数: 任意整形
*
* 功能描述:  强制结束函数时删除消息队列
*
* 返 回 值:无
*
* 作 者:Liu ming 2020/2/13
*
================================================================*/
void Ctrl_c(int singal_num)
{
    system("pkill server");
    msgctl(msg_sendid,IPC_RMID,NULL);
    msgctl(msg_recvid,IPC_RMID,NULL);
    exit(0);
}

/*================================================================
*
* 函 数 名:is_ok
*
* 参 数: 客户端请求码
*
* 功能描述:  如果用户输入1,则请求码被传递到消息队列结构体
                       并调用handle()进行发送接收处理;输入其他不做任何操作
*
* 返 回 值:  0 ------输入了确认
                     -1-------输入了取消
*
* 作 者:Liu ming 2020/2/13
*
================================================================*/
int is_ok(int flag)
{
    int num=0;
    //提示用户进行输入
    printf("输入 1 进行确认,输入 2 退出操作: \n");
    scanf("%d",&num);
    if(num !=1)
    {
        printf("请重新选择~\n");
        return -1;
    }
    sig_flag = flag ;
    handle();
    //退出登录账号 清除结构体
    if(sig_flag == 3 || sig_flag == 1)
    {
        InitMsg_t(&signal_t,1);//销户后清除客户端信息
    }
    return 0;
}
/*================================================================
*
* 函 数 名:handle
*
* 参 数: 无
*
* 功能描述:  发送请求功能码,接收服务端的数据,处理返回的数据
*
* 返 回 值:无
*
* 抛出异常:
*
* 作 者:Liu ming 2020/2/13
*
================================================================*/
void handle()
{
    signal_t.num = sig_flag;//复制请求码到消息队列
#ifdef DEBUG
    printf("send operation code : %d\n",signal_t.num);
#endif
    msgsnd(msg_sendid,(const void *)&signal_t,sizeof(signal_t)-4,0);//发送消息请求
    msgrcv(msg_recvid,(void *)&signal_t,sizeof(signal_t)-4,0,0);//阻塞接收消息
#ifdef DEBUG
    printf("receive operation code : %d\n",signal_t.num);
#endif
    //根据接收功能码处理不同的信息
    switch(signal_t.num)
    {
    case 30://开户成功
        printf("姓名:%s\n",signal_t.name);
        printf("身份证号:%s\n",signal_t.identyCard);
        printf("手机号码:%s\n",signal_t.phone);
        printf("分配的银行卡号:%d\n",signal_t.id);
        break;
    case 60://开户失败
        printf("请输入正确的身份信息\n");
        break;
    case 31://登录--账号密码正确
        show_flag =1;//进入登录界面
        break;
    case 61://登录--账号错误
         show_flag =0;//进入登录界面
        printf("账号或密码错误,请重试\n");
        break;
    case 62://登录--密码错误
         show_flag =0;//进入登录界面
        printf("账号或密码错误,请重试\n");
        break;

    case 32://销户--成功
        show_flag =0;//进入登录界面
        break;
    case 63://销户--失败
        printf("销户失败,请重试!\n");
        break;

    case 33://存款--成功
        show_flag =1;//
        printf("存款成功,当前余额为:%f\n",signal_t.money);
        break;
    case 64://存款--失败
        printf("存款失败,请重试!\n");
        break;

    case 34://取款--成功
        show_flag =1;//
        printf("取款成功,取出:%f\n",signal_t.money);
        break;
    case 65://取款--失败
        printf("取款失败,请重试!\n");
        break;

    case 35://转账--成功
        show_flag =1;//
        printf("转账成功,转出:%f\n",signal_t.money);
        break;
    case 66://余额不足
        printf("余额不足,请重试!\n");
        break;
    case 67://银行卡号不存在
        printf("银行卡号不存在,请重试!\n");
        break;

    case 36://余额查询--成功
        printf("余额:%f\n",signal_t.money);
        break;
    case 68://余额查询--失败
        printf("销户失败,请重试!\n");
        break;
    default:
        printf("传输信息出错!\n");
        break;
    }
}

/*================================================================
*
* 函 数 名:menu
*
* 参 数:无
*
* 功能描述:  提供显示和选择功能,并根据选择调用handle()函数
*                     进行数据的发送接收处理
* 返 回 值:无
*
* 作 者:Liu ming 2020/2/13
*
================================================================*/
void menu()
{
    int num=0;
    while(1)
    {
#ifdef DEBUG
        printf("showflag=%d\t signalflag=%d\n",show_flag,sig_flag);
#endif
        switch(show_flag)
        {
        case 0: //界面一 主界面
            printf("+-------------------------------------------------------------+\n");
            printf("|                                                             | \n");
            printf("|                 Welcome to bank system                      | \n");
            printf("|                                                             | \n");
            printf("|                      1.登录                                 | \n");
            printf("|                      2.开户                                 | \n");
            printf("|_____________________________________________________________| \n");
            printf("choose->");
            scanf("%d",&num);
            switch(num)
            {
            case 1:
            {
                show_flag =3;//进入登录页面
            }
            break;
            case 2:
            {
                show_flag =2;//进入开户界面
            }
            break;
            default :
                printf("请重新输入!\n");
            }
            break;
        case 1://界面二  登录进去后
            printf("+-------------------------------------------------------------+\n");
            printf("|                                                             | \n");
            printf("|                 Welcome to bank system                      | \n");
            printf("|                                                             | \n");
            printf("|                      0.退出                                 | \n");
            printf("|                      1.销户                                 | \n");
            printf("|                      2.存款                                 | \n");
            printf("|                      3.取款                                 | \n");
            printf("|                      4.转账                                 | \n");
            printf("|                      5.余额查询                             | \n");
            printf("|_____________________________________________________________| \n");
            printf("choose->");
            scanf("%d",&num);
            switch(num)
            {
            case 0://退出
                show_flag = 0;
                InitMsg_t(&signal_t,1);//初始化结构体
                break;
            case 1://销户
                is_ok(3);//3为销户请求码
                break;
            case 2://存款
                printf("请输入存款金额:");
                scanf("%f",&signal_t.money);
                is_ok(4);//4为存款请求码
                break;
            case 3://取款
                printf("请输入取款金额:");
                scanf("%f",&signal_t.money);
                //发送取款请求码5
                is_ok(5);
                break;
            case 4://转账
                printf("请输入转账账号:");
                scanf("%d",&signal_t.tmpId);
                printf("请输入转账金额:");
                scanf("%f",&signal_t.money);
                is_ok(6);//6为转账请求码
                break;
            case 5://余额查询
                sig_flag = 7;
                handle();
                break;
            default:
                printf("请重新输入!\n");
            }
            break;
        case 2://界面三  开户界面
            printf("+-------------------------------------------------------------+\n");
            printf("                    正在进行开户操作...                         \n");
            printf("请输入姓名:");
            scanf("%s",signal_t.name);
            printf("请输入身份证号:");
            scanf("%s",signal_t.identyCard);
            printf("请输入手机号码:");
            scanf("%s",signal_t.phone);
            printf("请输入密码:");
            scanf("%s",signal_t.passwd);
            is_ok(1);//1为开户请求码
            show_flag =0;
          //  if(! is_ok(1))sig_flag =0;
            break;
        case 3 :     //界面四  登录界面
            printf("+-------------------------------------------------------------+\n");
            printf("                  Welcome to bank system                       \n");
            printf("请输入账号:");
            scanf("%d",&signal_t.id);
            printf("请输入密码:");
            scanf("%s",signal_t.passwd);
            if(is_ok(2))
            {
                sig_flag =0;
                show_flag=0;
                InitMsg_t(&signal_t,1);//清除结构体
            }
            break;
        }
    }
}

int main(int argc, char *argv[])
{
    signal(SIGINT,Ctrl_c);//强制退出时删除开辟的资源
    msg_sendid= get_msg(123);//获得发送消息对列
    msg_recvid= get_msg(456);//获得接收消息对列
    InitMsg_t(&signal_t,1);//初始化消息类型1
    menu();
    return 0;
}
#endif

server.c

/************************************************************************
*
* 文件名:cLient.c
*
* 文件描述:模拟银行终端客户端
*
* 创建人:Liu ming 2020/2/13
*
* 版本号:2.0
*
*注释 :
---------------------------------------------------------------------------------------------------------
*     操作  客户端(client) 请求功能码       服务端(server)      *
 *    开户                 01                                      成功返回30  失败返回60
 *    登录                 02                                      成功返回31  卡号错误61,密码错误62
 *    销户                 03                                      成功返回32  失败返回63
 *    存款                 04                                      成功返回33  失败返回64
 *    取款                 05                                      成功返回34  失败返回65
 *    转账                 06                                      成功返回35  余额不足返回66 转账账号不存在67
 *    余额查询         07                                      成功返回36  失败返回68
 --------------------------------------------------------------------------------------------------------
* 修改记录:
*
************************************************************************/
#include 
#include 
#include 
#include 
#include "mysql.h"
#include "msg_queue.h"

#define DEBUG 1

#ifdef SERVER
int num =000;//分配的卡号
int sig_flag =0;//选择功能id号
char sql_buf[1024];//sql语句存放数组
struct signal signal_t= {0,0}; //进程通信信号
int msg_sendid=0,msg_recvid=0;//消息队列id
/*================================================================
*
* 函 数 名:give_account
*
* 参 数:无
*
* 功能描述: 查询数据库中最后一个id,把id+1作为新账户分配下去.
*
* 返 回 值:成功卡号,失败-1
*
* 抛出异常:
*
* 作 者:Liu ming 2020/2/13
*
================================================================*/
int give_account()
{
    memset(sql_buf,0,sizeof(sql_buf));//清除数组中的内容
    strcpy(sql_buf,"select cast(id as unsigned integer) from bank order by date desc ");
    errno = mysql_real_query(pmysql,sql_buf,strlen(sql_buf));
    if(errno)
    {
        printf("getid query error!\n");
        mysql_close(pmysql);
        exit(0);
    }
    res = mysql_store_result(pmysql);
    if(res ==NULL)
    {
        printf("没有获取到有效id\n");
        return -1;
    }
    row = mysql_fetch_row(res);
    num = 1+ atoi(row[0]);
    //返回生成的号码
    return num;

}

/*================================================================
*
* 函 数 名:Ctrl_c
*
* 参 数: 任意整形
*
* 功能描述:  强制结束函数时删除消息队列
*
* 返 回 值:无
*
* 作 者:Liu ming 2020/2/13
*
================================================================*/
void Ctrl_c(int singal_num)
{
    system("pkill client");
    msgctl(msg_sendid,IPC_RMID,NULL);
    msgctl(msg_recvid,IPC_RMID,NULL);
    exit(0);
}

/*================================================================
*
* 函 数 名:query_money
*
* 参 数: @id -- 银行卡卡号
*
* 功能描述: 根据输入的银行卡卡号查询余额
*
* 返 回 值:返回查询的余额
*
* 作 者:Liu ming 2020/2/13
*
================================================================*/
float query_money(int id)
{
    sprintf(sql_buf,"select money from bank where id=%d",id);
    errno = mysql_real_query(pmysql,sql_buf,strlen(sql_buf));
    memset(sql_buf,0,sizeof(sql_buf));
    if(errno)
    {
        printf("query money error!");
        signal_t.num = 67;//余额查询失败
        return -1;
    }
    res = mysql_store_result(pmysql);

    row = mysql_fetch_row(res);
    return atoi(row[0]);
}

/*================================================================
*
* 函 数 名:insert_money
*
* 参 数: @id -- 银行卡卡号
                 @money ---金额
*
* 功能描述: 根据输入的银行卡卡号修改用户余额
*
* 返 回 值:成功返回0,失败返回-1
*
* 作 者:Liu ming 2020/2/13
*
================================================================*/
int insert_money(int id,float money)
{
    sprintf(sql_buf,"update bank set money= %f where id = %d",money,id);
    errno = mysql_real_query(pmysql,sql_buf,strlen(sql_buf));
    memset(sql_buf,0,sizeof(sql_buf));
    if(errno)
    {
        printf("update money error!");
        return -1;
    }
    return 0;

}
/*================================================================
*
* 函 数 名:handle
*
* 参 数: 无
*
* 功能描述:  处理客户端发来的功能请求
*
* 返 回 值:无
*
* 作 者:Liu ming 2020/2/13
*
================================================================*/
void handle(void)
{
    float tmp_money=0;
    sig_flag = signal_t.num;
    printf("接收操作码:%d\n",sig_flag);
    switch(sig_flag)
    {
    case 1://开户操作
        printf("---------------------------------\n");
        printf("姓名:%s\n",signal_t.name);
        printf("身份证号:%s\n",signal_t.identyCard);
        printf("手机号码:%s\n",signal_t.phone);

        //生成卡号
        signal_t.id =give_account();
        if(signal_t.id <0)
        {
            perror("give_account()\n");
            return ;
        }
        printf("生成的id:%d\n",signal_t.id);
        //存储信息到数据库
        memset(sql_buf,0,sizeof(sql_buf));
        sprintf(sql_buf,"insert into bank values(%d,'%s',%s,%s,%s,%s,now())",\
                signal_t.id,signal_t.name,"1",signal_t.phone,signal_t.identyCard,signal_t.passwd);
        errno = mysql_real_query(pmysql,sql_buf,strlen(sql_buf));
        #ifdef DEBUG
        printf("%s\n",sql_buf);
        #endif // DEBUG
        if(errno)
        {
            printf("insert card info  error!");
            signal_t.num =60;//卡号输入错误
        }
        else
        {
            signal_t.num = 30;//分配卡号正确
        }
        break;
    case 2://登录操作
        memset(sql_buf,0,sizeof(sql_buf));
        sprintf(sql_buf,"select passwd from bank where id=%d",signal_t.id);
        errno = mysql_real_query(pmysql,sql_buf,strlen(sql_buf));
        if(errno)
        {
            printf("query passwd error!");
            return;
        }
        res = mysql_store_result(pmysql);
        row = mysql_fetch_row(res);
        if(row == 0)
        {
            signal_t.num =61;//查询信息出错无此人
        }
        else
        {
            if(strcmp(row[0],signal_t.passwd)==0)
            {
                printf("账号密码正确\n");
                signal_t.num = 31;//账号密码正确
            }
            else
            {
                signal_t.num = 62;//密码错误
                printf("密码错误");
            }
        }
        break;
    case 3://销户
        //删除数据库制定的信息
        memset(sql_buf,0,sizeof(sql_buf));
        sprintf(sql_buf,"delete from bank where id = %d",signal_t.id);
        #ifdef DEBUG
        printf("sql[]=%s\n",sql_buf);
        #endif // DEBUG
        errno = mysql_real_query(pmysql,sql_buf,strlen(sql_buf));
        if(errno)
        {
            printf("delete card info  error!");
            signal_t.num =63;//删除信息出错
        }
        else
        {
            signal_t.num = 32;//删除成功
        }
        break;
    case 4://存款
        //查询余额
        tmp_money=query_money(signal_t.id);
        //修改余额
        tmp_money += signal_t.money;
        //存入数据库
        if(insert_money(signal_t.id,tmp_money)==0)
        {
            signal_t.money = tmp_money;
            signal_t.num =33;
        }
        else
            signal_t.num=64;
        break;
    case 5://取款
        //查询余额
        tmp_money=query_money(signal_t.id);
        //修改余额
        if(tmp_money<=signal_t.money)
        {
            signal_t.money = tmp_money;
            tmp_money=0;
        }
        else
        {
            tmp_money -= signal_t.money;
        }
        //存入数据库
        if(insert_money(signal_t.id,tmp_money)==0)
        {
            signal_t.num =34;
        }
        else
            signal_t.num=65;
        break;
    case 6://转账
        sprintf(sql_buf,"select money from bank where id=%d",signal_t.tmpId);
        errno = mysql_real_query(pmysql,sql_buf,strlen(sql_buf));
        memset(sql_buf,0,sizeof(sql_buf));
        if(errno)
        {
            printf("query passwd error!");
            return;
        }
        res = mysql_store_result(pmysql);

        row = mysql_fetch_row(res);
        if(row == 0)
        {
            signal_t.num =67;// 银行卡卡号不存在
        }
        else
        {
            tmp_money=query_money(signal_t.id);
            //修改余额
            if(tmp_money<=signal_t.money)
            {
                signal_t.num = 66;//余额不足
            }
            else
            {
                signal_t.num = 35;//余额不足
                tmp_money -= signal_t.money;
                insert_money(signal_t.id,tmp_money);//扣除转账账户的余额
                tmp_money=query_money(signal_t.tmpId);
                tmp_money += signal_t.money;
                insert_money(signal_t.tmpId,tmp_money);//增加被转账账户的余额
            }
        }
        break;
    case 7:
        //查询余额
        tmp_money=query_money(signal_t.id);

        printf("your money : %f\n",tmp_money);
        signal_t.money = tmp_money;
        signal_t.num = 36;//余额查询成功
        break;
    default :
        break;
    }
}

int main(int argc, char *argv[])
{
    signal(SIGINT,Ctrl_c);//强制退出时删除消息对列

    //连接数据库
    pmysql = connect_mysql("test");

    //检测某表是否存在指定数据库中
    errno =table_isExist(pmysql,"bank");
    if(errno)
    {
        // talbe is not exist,
        //create table in hear !
        char buf[]="create table bank(id int not null auto_increment primary key , name varchar(20) not null, \
                money float default 0,phone varchar(11) not null,identyCard varchar(18) not null unique,\
                passwd varchar(20) not null,date DATETIME )";
        //执行创建表格MySQL语句
        errno = mysql_real_query(pmysql,buf,strlen(buf));
          //插入一条默认数据
        char test[]="insert into bank values(1,'admin',0,'null','admin','666666',now())";
        errno = mysql_real_query(pmysql,test,strlen(test));

        //设置可以存储中文
        memset(sql_buf,0,sizeof(sql_buf));
        sprintf(sql_buf,"alter table %s  CONVERT TO CHARACTER SET utf8","bank");
        errno = mysql_real_query(pmysql,sql_buf,strlen(sql_buf));
        #ifdef DEBUG
        printf("the table is not exist ,creating success!\n");
        #endif // DEBUG
    }
    else
    {
         #ifdef DEBUG
            printf("the table is exist \n");
        #endif // DEBUG
    }
    msg_recvid = get_msg(123);//获得接收消息队列
    msg_sendid = get_msg(456);//获得发送消息队列
    while(1)
    {
        printf("----------------------------------------\n");
        printf("等待接受信息:\n");
        msgrcv(msg_recvid,(void *)&signal_t,sizeof(signal_t)-4,0,0);
        #ifdef DEBUG
        printf("receive operation code : %d\nname=%s\niddentCar=%s\nphone=%s\npasswd=%s\nid=%d\nmoney=%f\n",
               signal_t.num,signal_t.name,signal_t.identyCard,signal_t.phone,signal_t.passwd,signal_t.id,signal_t.money);
        #endif // DEBUG
        handle();//处理接收的信息
        printf("send operation code : %d\n",signal_t.num);
        msgsnd(msg_sendid,(const void *)&signal_t,sizeof(signal_t)-4,0);//0队列满时阻塞
    }
    return 0;
}

(五)功能演示

开户功能演示:

开户.gif

存款功能演示:


存款

其他功能就不演示了,感兴趣可以下载代码自己尝试。


本文章仅供学习交流用禁止用作商业用途,文中内容来水枂编辑,如需转载请告知,谢谢合作

微信公众号:zhjj0729

微博:文艺to青年

你可能感兴趣的:(Linux下使用消息队实现 ATM 自动取款机功能)