文章分五部分:需求分析、项目所需知识点、思路讲解、代码实现、功能演示
本文内容较长,建议是按照我自己的思路给大家讲解的,如果有其他问题,欢迎评论区讨论
文章中的代码是在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 <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#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 DEBUGprintf("send operation code : %d\n",signal_t.num);
#endifmsgsnd(msg_sendid,(const void *)&signal_t,sizeof(signal_t)-4,0);//发送消息请求msgrcv(msg_recvid,(void *)&signal_t,sizeof(signal_t)-4,0,0);//阻塞接收消息
#ifdef DEBUGprintf("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 DEBUGprintf("showflag=%d\t signalflag=%d\n",show_flag,sig_flag);
#endifswitch(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);//发送取款请求码5is_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);//初始化消息类型1menu();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 <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#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 DEBUGprintf("%s\n",sql_buf);#endif // DEBUGif(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 DEBUGprintf("sql[]=%s\n",sql_buf);#endif // DEBUGerrno = 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;}elsesignal_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;}elsesignal_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 DEBUGprintf("the table is not exist ,creating success!\n");#endif // DEBUG}else{#ifdef DEBUGprintf("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 DEBUGprintf("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 // DEBUGhandle();//处理接收的信息printf("send operation code : %d\n",signal_t.num);msgsnd(msg_sendid,(const void *)&signal_t,sizeof(signal_t)-4,0);//0队列满时阻塞}return 0;
}
(五)功能演示
开户功能演示:
存款功能演示:
其他功能就不演示了,感兴趣可以下载代码自己尝试。