文章分五部分:需求分析、项目所需知识点、思路讲解、代码实现、功能演示
本文内容较长,建议是按照我自己的思路给大家讲解的,如果有其他问题,欢迎评论区讨论
文章中的代码是在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;
}
(五)功能演示
开户功能演示:
存款功能演示:
其他功能就不演示了,感兴趣可以下载代码自己尝试。
本文章仅供学习交流用禁止用作商业用途,文中内容来水枂编辑,如需转载请告知,谢谢合作
微信公众号:zhjj0729
微博:文艺to青年