用UNIX消息队列实现IPC(以ATM为例)

  清明假期三天没出寝室的门,先是把独立的博客折腾好了。域名备案还没好。域名是ilovecpp.com,意为“我爱C++”,好羞涩,掩面,逃:)。话说cnblogs.com的界面好丑 。其余大部分时间就是折腾这个小项目了,Unix 内核函数各种结构、flags即使查man手册还是看的头大。

  用消息而不是Socket,是因为最近看Unix C比较多,于是写le这个小项目(玩具),更好的了解UNIX的消息机制。

  

  1,测试时,server端异常退出时消息队列没有及时的删除,之后client端收到的消息总是有偏差,最后调试好久用ipcs才发现消息队列里边还有内容。

  2,由于调用函数比较频繁,调试时发现__LINE__宏挺好用的,可以很快的找到bug。 

  代码:

  一,公共部分包括定义的 宏和key,消息结构,账户结构。

  1.bank.h

  

 //定义消息类型的宏 ,声明产生message的key
//key的定义放在 bank.c 中
#ifndef _BANK_H
#define _BANK_H
#include "acstruct.h"
//key_t 头文件
#include <sys/ipc.h> extern const key_t key1;
extern const key_t key2; //客户端消息类型
#define CT_OPEN 1//开户
#define CT_DELT 2//消户
#define CT_SAVE 3//存钱
#define CT_DRAW 4//取钱
#define CT_QURY 5//查询
#define CT_TRNS 6//转帐
#define CT_LOGN 7//登陆 //服务端消息
#define SV_SUCS 8//操作成功
#define SV_FAIL 9//操作失败 // 保存退出时信号发送附加的数据
//两个 msgid
typedef struct Msg_id {
int msgid1;
int msgid2;
} Msg_id; #endif

  bank.c

 #include "bank.h"

 const key_t key1 = 0x20150405;
const key_t key2 = 0x20150406;

 

  account.h

 // 声明账户结构 和 消息结构
#ifndef _ACSTRUCT_H
#define _ACSTRUCT_H typedef struct {
int id;
char name[];
char pwd[];
double money;
} Account; typedef struct {
long mtype;
Account data;
} MSG; #endif

  二、服务端

  1.server.c

  signal(SIGINT,sigq_send);
  sigaction(SIGUSR1,&act,NULL);

  完全可以只用SIGINT就可以了,我是为了使用sigqueue发送数据,以及使用sigaction操作信号。

  用UNIX消息队列实现IPC(以ATM为例)

  收到SIGINT信号时会调用sigq_send,sigq_send调用sigqueue发送SIGUSR1,和数据sigvalue(结构),然后通过sigaction结构的sa_sigaction函数拿到

  info->si_ptr转换一下指针类型就拿到两个msgid了,就可以删除消息队列了。

  我水平臭,写的比较绕。

 //服务端
#include "bank.h"
#include "server.h"
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h> extern const key_t key1;
extern const key_t key2; union sigval sigvalue; int msgid1,msgid2; int main()
{
printf(STAR);
printf(QUIT);
printf(STAR); //初始化服务器,两个传出参数,返回msgid
initserver(&msgid1,&msgid2); //信号绑定,用sigaction()
//不用signal因为要传递两个参数,msgid1,msgid2
Msg_id msg_id;
msg_id.msgid1 = msgid1;
msg_id.msgid2 = msgid2; sigvalue.sival_ptr = &msg_id;
struct sigaction act = {};
act.sa_handler = NULL;
act.sa_sigaction = endserver;
//使用 sa_sigaction 指针
act.sa_flags = SA_SIGINFO; signal(SIGINT,sigq_send);
sigaction(SIGUSR1,&act,NULL); // 接收客户端消息
RcvMsg();
}

  2.server_func.c

 #include "bank.h"
#include "acstruct.h"
#include "server.h"
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdbool.h> extern union sigval sigvalue; extern const key_t key1;
extern const key_t key2; extern int msgid1,msgid2; //服务器初始化
//1.新建两个消息队列,接受和发送消息
void initserver(int* m1,int* m2) {
printf("服务器启动,正在新建消息队列...\n");
sleep();//模拟等待 = =!
int msgid1 = msgget(key1,IPC_CREAT|);
if(msgid1 == -) {
printf("消息队列1创建失败!\n");
exit(-);
}
int msgid2 = msgget(key2,IPC_CREAT|);
if(msgid2 == -) {
printf("消息队列2创建失败!\n");
exit(-);
}
*m1 = msgid1;
*m2 = msgid2;
printf("发送和接受消息队列创建成功!\n");
} //关闭
void end(int sig)
{
exit();
} //接受消息
void RcvMsg() {
MSG msg1;
printf("正在等待客户端请求!\n");
pid_t pid= vfork();
if(pid == -) {
printf("vfork %m\n");
exit(-);
}
esle if(pid == ) {
while() {
signal(SIGUSR1,end);
//type==-7 接受所有客户端发来的消息(1-7)
int res = msgrcv(msgid1,&msg1,sizeof(msg1.data),-,);
if(res == -){
perror("接受客户端消息失败!");
exit(-);
}
DealMsg(msg1);
}
}
else {
waitpid(pid,,);
printf("处理结束!\n");
}
} //处理受到的消息
void DealMsg(MSG msg)
{
switch(msg.mtype) {
case :OpenAct(msg);
break;
case :DelAct(msg);
break;
case :SaveMny(msg);
break;
case :DrawMny(msg);
break;
case :QuryMny(msg);
break;
case :CheckAct(msg);
break;
}
} //关闭服务器,删除消息队列
void sigq_send(int sig) {
sigqueue(getpid(),SIGUSR1,sigvalue);
} void endserver(int sig,siginfo_t* info,void* p)
{
int msgid1,msgid2;
msgid1 = ((Msg_id*)(info->si_ptr))->msgid1;
msgid2 = ((Msg_id*)(info->si_ptr))->msgid2;
printf("正在关闭服务器,删除msgqueue!\n");
int m1 = msgctl(msgid1,IPC_RMID,NULL);
if(m1 == -) {
perror("msgid1");
exit(-);
}
int m2 = msgctl(msgid2,IPC_RMID,NULL);
if(m2 == -) {
perror("msgid2");
exit(-);
}
printf("删除消息队列成功!\n");
printf("正常退出!\n");
exit();
} //创建ID对应的文件,写入账户信息
void OpenAct(MSG msg) {
//先读取可用的ID
int fd = open("Data/id.dat",O_RDWR);
if(fd == -) {
printf("打开ID文件失败!\n");
exit(-);
}
int id,oldid;
read(fd,&id,); //客户端查询时都是提交的id作为参数,不然会找不到文件
//用之前的没写这行创建的帐号找bug,找了好久好久
//吃一堑长一智 只能这样安慰自己 T_T
msg.data.id = id; oldid = id;//要传递给客户端
const char* path = FindPath(id);
//id+1 存入 id.dat
++id;
lseek(fd,,SEEK_SET);
write(fd,&id,);
close(fd);
fd = open(path,O_CREAT|O_RDWR,);
if(fd == -) {
printf("创建ID文件失败!\n");
exit(-);
}
printf("创建id文件成功,正在写入账户信息...\n");
sleep();
printf("%d,%s,%s,%lf",msg.data.id,msg.data.name,msg.data.pwd,msg.data.money);
int r = write(fd,&msg,sizeof(msg));
if(r==-){
printf("写入账户信息到文件失败!\n");
exit(-);
}
close(fd);
msg.mtype = SV_SUCS;
msg.data.id = oldid;
SndBack(msg);
printf("客户端开户请求已经通过,成功返回给客户端!\n");
} //返回处理结果给客户端
void SndBack(MSG msg)
{
int r = msgsnd(msgid2,&msg,sizeof(msg.data),);
if(r == -) {
printf("返回消息给客户端时失败T_T!\n");
exit(-);
}
} //根据id找到,信息文件的路径
const char* FindPath(int id)
{
//这两行不能写成
//static char path[15] ={"Data/"};
//否则 path会变长
static char path[] ={};
strcpy(path,"Data/"); char buf[] = {"0000.id"};
ctos(buf,id);
strcat(path,buf);
return path;
} //检查客户端的登陆的id和密码
void CheckAct(MSG msg){
MSG msg1 = ReadMsg(msg.data.id);
if(!strncmp(msg.data.pwd,msg1.data.pwd,)) {
msg.mtype = SV_SUCS;
SndBack(msg);
printf("客户端登陆密码正确,等待服务请求...\n");
}
else {
msg.mtype = SV_FAIL;
SndBack(msg);
printf("客户端登陆密码错误。\n");
}
} //删除账户
void DelAct(MSG msg) {
const char* path = FindPath(msg.data.id);
int res = remove(path);
if(res==-) {
msg.mtype =SV_FAIL;
SndBack(msg);
exit(-);
}
else {
msg.mtype =SV_SUCS;
SndBack(msg);
printf("%d的信息文件已经删除,账户已销户!\n",msg.data.id);
//此时客户端收到SV_SUCS消息就会关闭。
printf("等待下一个客户端...\n");
}
} void SaveMny(MSG msg){
MSG msg1 = ReadMsg(msg.data.id);
msg1.data.money += msg.data.money;
WriteMsg(msg1);
msg1.mtype =SV_SUCS;
SndBack(msg1);
} void DrawMny(MSG msg) {
MSG msg1 = ReadMsg(msg.data.id);
//判断是否大于存款
if(msg1.data.money < msg.data.money) {
msg1.mtype =SV_FAIL;
SndBack(msg1);
printf("等待下一个客户端请求!\n");
}
else {
msg1.data.money -= msg.data.money;
WriteMsg(msg1);
msg1.mtype =SV_SUCS;
SndBack(msg1);
}
} void QuryMny(MSG msg) {
msg = ReadMsg(msg.data.id);
msg.mtype =SV_SUCS;
SndBack(msg);
printf("查询余额结果已经返回!\n");
} //读取账户文件的信息
MSG ReadMsg(int id){
MSG msg = {};
msg.data.id =id;
const char* path = FindPath(id);
int fd = open(path,O_RDWR);
if(fd == -) {
msg.mtype =SV_FAIL;
SndBack(msg);
exit(-);
}
else{
read(fd,&msg,sizeof(msg));
close(fd);
}
return msg;
} void WriteMsg(MSG msg) {
const char* path = FindPath(msg.data.id);
int fd = open(path,O_RDWR);
if(fd == -) {
msg.mtype =SV_FAIL;
SndBack(msg);
exit(-);
}
else {
write(fd,&msg,sizeof(msg));
close(fd);
}
} //数字id转成字符串,方便建立文件
void ctos(char* buf,int id) {
int i=;
for(;i<;++i) {
buf[-i] = id% + '';
id /= ;
}
}

  三、客户端

  1.client

  

 //客户端
#include "client.h" int msgid1,msgid2; int main()
{
//获取msgid
Getmid(&msgid1,&msgid2); //显示 client login 界面
Login(); }

  2.client_func.c

Unix C给我的感觉就是头文件太多了。

 #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <stdbool.h>
#include <sys/msg.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <string.h>
#include "bank.h"
#include "acstruct.h"
#include "client.h" extern int msgid1,msgid2; void Getmid(int* p1,int* p2) {
int msgid1,msgid2;
msgid1 = msgget(key1,IPC_CREAT|);
if(msgid1 == -) {
printf("获取msgid1失败!\n");
exit(-);
}
msgid2 = msgget(key2,IPC_CREAT|);
if(msgid2 == -) {
printf("获取msgid2失败!\n");
exit(-);
}
*p1 = msgid1;
*p2 = msgid2;
} void initclient(int id) {
int choice;
int iscontinue = ;
while(iscontinue){
printf(LINE);
printf(CHO1);
printf(CHO2);
printf(CHO3);
printf(ENDL);
printf(SELC);
scanf("%d",&choice);
switch(choice) {
case :iscontinue=;
break;
case :DelAct(id);
break;
case :SaveMny(id);
break;
case :DrawMny(id);
break;
case :QuryMny(id);
break;
case :TrnsMny(id);
break;
default:printf("没有这个选项!\n");
break;
}
}
printf("谢谢使用,再见!\n");
} void Login() {
printf(LINE);
printf(CHO5);
printf(ENDL);
printf("退出请按CRTL+C\n");
printf(SELC);
int choice;
scanf("%d",&choice);
switch(choice) {
case :CheckAct();
break;
case :CreatAct();
break;
default:printf("没有这个选项!\n");
exit(-);
}
} void CreatAct() {
Account account = {};
bool flag = false;
bool isfst = true;
char tmp[];
while(!flag) {
printf("%s",isfst?"请输入姓名:":"两次密码不一致,请重新输入姓名:");
scanf("%s",account.name);
printf("请设置密码(前6位有效):");
scanf("%s",account.pwd);
printf("请确认密码:");
scanf("%s",tmp);
if(!strncmp(tmp,account.pwd,))
flag = true;
isfst = false;
}
printf("输入有效,请输入存款金额:");
scanf("%lf",&account.money);
MSG msg={CT_OPEN,account};
printf("正在为您开户...\n");
MSG bkres = SendMsg(&msg);
if(bkres.mtype) {
printf("开户成功,您的ID是%d,这是登陆凭据,请牢记!\n",bkres.data.id);
printf("正在进入ATM服务系统...\n");
initclient(bkres.data.id);
}
else {
printf("开户失败!\n");
exit(-);
}
} MSG SendMsg(MSG* pMsg) {
MSG bkres = {};//保存服务器处理结果
int r = msgsnd(msgid1,pMsg,sizeof(pMsg->data),);
if(r == -) {
printf("发送给服务器的请求失败了T_T!\n");
exit(-);
}
//mtype==-9 接受 消息 类型为 8-9的消息
//头文件内只定义了这两个
r = msgrcv(msgid2,&bkres,sizeof(bkres),-,);
if(r== -) {
printf("接受服务器处理结果失败!\n");
exit(-);
}
return bkres;
} void CheckAct(){
signal(SIGQUIT,CheckAct);
MSG msg;
msg.mtype = CT_OPEN;
int id;
char pwd[]= {};
printf("\n请输入的卡号:");
scanf("%d",&id);
printf("你输入的卡号是%d,",id);
printf("如需重新输入,请按CRTL+‘\\’\n");
printf("现在请输入密码:");
scanf("%s",pwd);
Account account = {};
account.id = id;
strcpy(account.pwd,pwd);
msg.mtype = CT_LOGN;
msg.data = account;
MSG bkres = SendMsg(&msg);
if(bkres.mtype == SV_SUCS) {
printf("密码正确登陆成功!\n");
initclient(id);
}
else {
printf("密码有误,登陆失败!\n");
exit(-);
}
} void DelAct(int id) {
MSG msg;
msg.data.id = id;
msg.mtype = CT_DELT;
MSG bkres = SendMsg(&msg);
if(bkres.mtype == SV_SUCS) {
printf("账户已注销,您已*退出系统!\n");
exit();
}
else {
printf("删除失败,请联系客服人员!\n");
exit(-);
}
} void SaveMny(int id) {
double save = ;
while(save <= ) {
printf("请输入存款金额:");
scanf("%lf",&save);
}
MSG msg;
msg.data.id = id;
msg.data.money = save;
msg.mtype = CT_SAVE;
printf("%d\n",msg.data.id);
MSG bkres = SendMsg(&msg);
printf("%d\n",bkres.mtype);
if(bkres.mtype == SV_SUCS)
printf("存款成功!\n");
else {
printf("存款失败,请联系客服人员!\n");
exit(-);
}
} void DrawMny(int id){
double draw = ;
while(draw <= ) {
printf("请输入取款金额:");
scanf("%lf",&draw);
}
MSG msg;
msg.data.id = id;
msg.data.money = draw;
msg.mtype = CT_DRAW;
MSG bkres = SendMsg(&msg);
if(bkres.mtype == SV_SUCS)
printf("取款成功!\n");
else {
printf("没钱取你妹!\n");
exit(-);
}
} double QuryMny(int id) {
double mny=;
MSG msg;
msg.data.id = id;
msg.mtype = CT_QURY;
MSG bkres = SendMsg(&msg);
if(bkres.mtype == SV_SUCS) {
printf("你的余额:%lf\n",bkres.data.money);
mny = bkres.data.money;
}
else {
printf("查询失败!\n");
exit(-);
}
return mny;
} void TrnsMny(int id) {
int destid=;
printf("请输入转帐的ID:");
scanf("%d",&destid);
if(!ActExist(destid)) {
printf("不存在这个账户!\n");
exit(-);
}
double money = ;
while(money <= ) {
printf("输入要转的金额:");
scanf("%lf",&money);
}
if(QuryMny(id) < money) {
printf("你自己的钱都不够呢!\n");
exit(-);
}
else {
Change(id,-money);
Change(destid,money);
printf("转帐成功!\n");
}
} void Change(int id,double money) {
MSG msg;
msg.data.id = id;
msg.data.money = money;
msg.mtype = CT_SAVE;
MSG bkres = SendMsg(&msg);
} //转帐时检测,目标账户是否存在
int ActExist(int id) {
MSG msg;
msg.data.id = id;
msg.mtype = CT_QURY;
MSG bkres = SendMsg(&msg);
if(bkres.mtype == SV_SUCS)
return ;
else
return ;
}
上一篇:Django 数据库查询集合(多对多)


下一篇:.Net下简单地实现MD5加密