用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

  

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

  bank.c

1 #include "bank.h"
2 
3 const key_t key1 = 0x20150405;
4 const key_t key2 = 0x20150406;

 

  account.h

 1 // 声明账户结构 和 消息结构 
 2 #ifndef _ACSTRUCT_H
 3 #define _ACSTRUCT_H
 4 
 5 typedef struct {
 6     int id;
 7     char name[10];
 8     char pwd[7];
 9     double money;
10 } Account;
11 
12 typedef struct {
13     long mtype;
14     Account data;
15 } MSG;
16 
17     
18 #endif

  二、服务端

  1.server.c

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

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

  

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

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

  我水平臭,写的比较绕。

 1 //服务端
 2 #include "bank.h"
 3 #include "server.h"
 4 #include <signal.h>
 5 #include <stdio.h>
 6 #include <unistd.h>
 7 #include <stdlib.h>
 8 
 9 extern const key_t key1;
10 extern const key_t key2;
11 
12 union sigval sigvalue;
13 
14 int msgid1,msgid2;
15 
16 int main()
17 {
18     printf(STAR);
19     printf(QUIT);
20     printf(STAR);
21 
22     //初始化服务器,两个传出参数,返回msgid
23     initserver(&msgid1,&msgid2);
24 
25     //信号绑定,用sigaction()
26     //不用signal因为要传递两个参数,msgid1,msgid2
27     Msg_id msg_id;
28     msg_id.msgid1 = msgid1;
29     msg_id.msgid2 = msgid2;
30     
31     sigvalue.sival_ptr = &msg_id;
32     struct sigaction act = {};
33     act.sa_handler = NULL;
34     act.sa_sigaction = endserver;
35     //使用 sa_sigaction 指针
36     act.sa_flags = SA_SIGINFO;
37 
38     signal(SIGINT,sigq_send);
39     sigaction(SIGUSR1,&act,NULL);
40 
41     // 接收客户端消息
42     RcvMsg();
43 }

  2.server_func.c

  1 #include "bank.h"
  2 #include "acstruct.h"
  3 #include "server.h"
  4 #include <stdio.h>
  5 #include <stdlib.h>
  6 #include <signal.h>
  7 #include <sys/ipc.h>
  8 #include <sys/msg.h>
  9 #include <unistd.h>
 10 #include <fcntl.h>
 11 #include <string.h>
 12 #include <stdbool.h>
 13 
 14 extern union sigval sigvalue;
 15 
 16 extern const key_t key1;
 17 extern const key_t key2;
 18 
 19 extern int msgid1,msgid2;
 20 
 21 //服务器初始化
 22 //1.新建两个消息队列,接受和发送消息
 23 void initserver(int* m1,int* m2) {
 24     printf("服务器启动,正在新建消息队列...
");
 25     sleep(1);//模拟等待 = =!
 26     int msgid1 =  msgget(key1,IPC_CREAT|0666);
 27     if(msgid1 == -1) {
 28         printf("消息队列1创建失败!
");
 29         exit(-1);
 30     }
 31     int msgid2 = msgget(key2,IPC_CREAT|0666);
 32     if(msgid2 == -1) {
 33         printf("消息队列2创建失败!
");
 34         exit(-1);
 35     }
 36     *m1 = msgid1;
 37     *m2 = msgid2;
 38     printf("发送和接受消息队列创建成功!
");
 39 }
 40 
 41 
 42 //关闭
 43 void end(int sig)
 44 {
 45     exit(0);
 46 }
 47 
 48 
 49 //接受消息
 50 void RcvMsg() {
 51     MSG msg1;
 52     printf("正在等待客户端请求!
");
 53     pid_t pid= vfork();
 54     if(pid == -1) {
 55         printf("vfork %m
");
 56         exit(-1);
 57     }
 58     esle if(pid == 0) {
 59         while(1) {
 60             signal(SIGUSR1,end);
 61             //type==-7 接受所有客户端发来的消息(1-7)
 62             int res = msgrcv(msgid1,&msg1,sizeof(msg1.data),-7,0);
 63             if(res == -1){
 64                 perror("接受客户端消息失败!");
 65                 exit(-1);
 66             }
 67             DealMsg(msg1);
 68         }
 69     }
 70     else {
 71         waitpid(pid,0,0);
 72         printf("处理结束!
");
 73     }
 74 }
 75 
 76 
 77 //处理受到的消息
 78 void DealMsg(MSG msg)
 79 {
 80     switch(msg.mtype) {
 81         case 1:OpenAct(msg);
 82                break;
 83         case 2:DelAct(msg);
 84                break;
 85         case 3:SaveMny(msg);
 86                break;
 87         case 4:DrawMny(msg);
 88                break;
 89         case 5:QuryMny(msg);
 90                break;
 91         case 7:CheckAct(msg);
 92                break;
 93     }        
 94 }
 95 
 96 
 97 //关闭服务器,删除消息队列
 98 void sigq_send(int sig) {
 99     sigqueue(getpid(),SIGUSR1,sigvalue);
100 }
101 
102 void endserver(int sig,siginfo_t* info,void* p)
103 {
104     int msgid1,msgid2;
105     msgid1 = ((Msg_id*)(info->si_ptr))->msgid1;
106     msgid2 = ((Msg_id*)(info->si_ptr))->msgid2;
107     printf("正在关闭服务器,删除msgqueue!
");
108     int m1 = msgctl(msgid1,IPC_RMID,NULL);
109     if(m1 == -1) {
110         perror("msgid1");
111         exit(-1);
112     }
113     int m2 = msgctl(msgid2,IPC_RMID,NULL);
114     if(m2 == -1) {
115         perror("msgid2");
116         exit(-1);
117     }
118     printf("删除消息队列成功!
");
119     printf("正常退出!
");
120     exit(0);
121 }
122 
123 
124 //创建ID对应的文件,写入账户信息
125 void OpenAct(MSG msg) {
126         //先读取可用的ID
127         int fd = open("Data/id.dat",O_RDWR);
128         if(fd == -1) {
129             printf("打开ID文件失败!
");
130             exit(-1);
131         }
132         int id,oldid;
133         read(fd,&id,4);
134         
135         //客户端查询时都是提交的id作为参数,不然会找不到文件
136         //用之前的没写这行创建的帐号找bug,找了好久好久
137         //吃一堑长一智 只能这样安慰自己 T_T
138         msg.data.id = id;
139         
140         oldid = id;//要传递给客户端
141         const char* path = FindPath(id);
142         //id+1 存入 id.dat
143         ++id;
144         lseek(fd,0,SEEK_SET);
145         write(fd,&id,4);
146         close(fd);        
147         fd = open(path,O_CREAT|O_RDWR,0666);
148         if(fd == -1) {
149             printf("创建ID文件失败!
");
150             exit(-1);
151         }
152         printf("创建id文件成功,正在写入账户信息...
");
153         sleep(1);
154         printf("%d,%s,%s,%lf",msg.data.id,msg.data.name,msg.data.pwd,msg.data.money);
155         int r = write(fd,&msg,sizeof(msg));
156         if(r==-1){
157             printf("写入账户信息到文件失败!
");
158             exit(-1);
159         }
160         close(fd);
161         msg.mtype = SV_SUCS;
162         msg.data.id = oldid;
163         SndBack(msg);
164         printf("客户端开户请求已经通过,成功返回给客户端!
");
165 }
166 
167 
168 
169 //返回处理结果给客户端
170 void SndBack(MSG msg)
171 {
172     int r = msgsnd(msgid2,&msg,sizeof(msg.data),0);
173     if(r == -1) {  
174         printf("返回消息给客户端时失败T_T!
");
175         exit(-1);
176     }
177 }
178 
179 //根据id找到,信息文件的路径
180 const char* FindPath(int id)
181 {
182     //这两行不能写成 
183     //static char path[15] ={"Data/"};
184     //否则 path会变长
185     static char path[15] ={};
186     strcpy(path,"Data/");
187     
188     char buf[8] = {"0000.id"};
189     ctos(buf,id);
190     strcat(path,buf);
191     return path;
192 }
193 
194 
195 
196 //检查客户端的登陆的id和密码
197 void CheckAct(MSG msg){
198     MSG msg1 = ReadMsg(msg.data.id);
199     if(!strncmp(msg.data.pwd,msg1.data.pwd,6)) {
200         msg.mtype = SV_SUCS;
201         SndBack(msg);
202         printf("客户端登陆密码正确,等待服务请求...
");
203     }
204     else {
205         msg.mtype = SV_FAIL; 
206         SndBack(msg);
207         printf("客户端登陆密码错误。
");
208     }
209 } 
210 
211 
212 
213 //删除账户
214 void DelAct(MSG msg) {
215     const char* path = FindPath(msg.data.id);
216     int res = remove(path);
217     if(res==-1) {
218         msg.mtype =SV_FAIL;
219         SndBack(msg);
220         exit(-1);
221     }
222     else {
223         msg.mtype =SV_SUCS;
224         SndBack(msg);
225         printf("%d的信息文件已经删除,账户已销户!
",msg.data.id);
226         //此时客户端收到SV_SUCS消息就会关闭。
227         printf("等待下一个客户端...
");
228     }
229 }
230 
231 
232 
233 void SaveMny(MSG msg){
234     MSG msg1 = ReadMsg(msg.data.id);
235     msg1.data.money += msg.data.money;
236     WriteMsg(msg1);
237     msg1.mtype =SV_SUCS;
238     SndBack(msg1);
239 }
240 
241 
242 void DrawMny(MSG msg) {
243     MSG msg1 = ReadMsg(msg.data.id);
244     //判断是否大于存款
245     if(msg1.data.money < msg.data.money) {
246         msg1.mtype =SV_FAIL;
247         SndBack(msg1);
248         printf("等待下一个客户端请求!
");
249     }
250     else {
251         msg1.data.money -= msg.data.money;
252         WriteMsg(msg1);
253         msg1.mtype =SV_SUCS;
254         SndBack(msg1);
255     }
256 }
257 
258 
259 void QuryMny(MSG msg) {
260     msg = ReadMsg(msg.data.id);
261     msg.mtype =SV_SUCS;
262     SndBack(msg);
263     printf("查询余额结果已经返回!
");
264 }
265 
266 //读取账户文件的信息
267 MSG ReadMsg(int id){
268     MSG msg = {};
269     msg.data.id =id;
270     const char* path = FindPath(id);
271     int fd = open(path,O_RDWR);
272     if(fd == -1) {
273         msg.mtype =SV_FAIL;
274         SndBack(msg);
275         exit(-1);
276     }
277     else{
278         read(fd,&msg,sizeof(msg));
279         close(fd);
280     }
281     return msg;
282 }
283 
284 
285 void WriteMsg(MSG msg) {
286     const char* path = FindPath(msg.data.id);
287     int fd = open(path,O_RDWR);
288     if(fd == -1) {
289         msg.mtype =SV_FAIL;
290         SndBack(msg);
291         exit(-1);
292     }
293     else {
294         write(fd,&msg,sizeof(msg));
295         close(fd);
296     }
297 }
298 
299 
300 
301 //数字id转成字符串,方便建立文件
302 void ctos(char* buf,int id) {
303     int i=0;
304     for(;i<4;++i) {
305         buf[3-i] = id%10 + '0';
306         id /= 10;
307     }
308 }

  三、客户端

  1.client

  

 1 //客户端
 2 #include "client.h"
 3 
 4 int msgid1,msgid2;
 5 
 6 int main()
 7 {
 8     //获取msgid
 9     Getmid(&msgid1,&msgid2);
10 
11     //显示 client login 界面
12     Login();
13 
14 }

  2.client_func.c

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

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4 #include <sys/ipc.h>
  5 #include <stdbool.h>
  6 #include <sys/msg.h>
  7 #include <unistd.h>
  8 #include <signal.h>
  9 #include <fcntl.h>
 10 #include <string.h>
 11 #include "bank.h"
 12 #include "acstruct.h"
 13 #include "client.h"
 14 
 15 extern int msgid1,msgid2;
 16 
 17 
 18 void Getmid(int* p1,int* p2) {
 19     int msgid1,msgid2;
 20     msgid1 = msgget(key1,IPC_CREAT|0666);
 21     if(msgid1 == -1) {
 22         printf("获取msgid1失败!
");
 23         exit(-1);
 24     }
 25     msgid2 = msgget(key2,IPC_CREAT|0666);
 26     if(msgid2 == -1) {
 27         printf("获取msgid2失败!
");
 28         exit(-1);
 29     }
 30     *p1 = msgid1;
 31     *p2 = msgid2;
 32 }
 33 
 34 void initclient(int id) {
 35     int choice;
 36     int iscontinue = 1;
 37     while(iscontinue){
 38         printf(LINE);
 39         printf(CHO1);
 40         printf(CHO2);
 41         printf(CHO3);
 42         printf(ENDL);
 43         printf(SELC);
 44         scanf("%d",&choice);
 45         switch(choice) {
 46             case 0:iscontinue=0;
 47                    break;
 48             case 1:DelAct(id);
 49                    break;
 50             case 2:SaveMny(id);
 51                    break;
 52             case 3:DrawMny(id);
 53                    break;
 54             case 4:QuryMny(id);
 55                    break;
 56             case 5:TrnsMny(id);
 57                    break;
 58             default:printf("没有这个选项!
");
 59                     break;
 60         }
 61     }
 62     printf("谢谢使用,再见!
");
 63 }
 64 
 65 void Login() {
 66     printf(LINE);
 67     printf(CHO5);
 68     printf(ENDL);
 69     printf("退出请按CRTL+C
");
 70     printf(SELC);
 71     int choice;
 72     scanf("%d",&choice);
 73     switch(choice) {
 74         case 0:CheckAct();
 75                break;
 76         case 1:CreatAct();
 77                break;
 78         default:printf("没有这个选项!
");
 79                 exit(-1);
 80     }
 81 }
 82 
 83 void CreatAct() {
 84     Account account = {};
 85     bool flag = false;
 86     bool isfst = true;
 87     char tmp[7];
 88     while(!flag) {
 89         printf("%s",isfst?"请输入姓名:":"两次密码不一致,请重新输入姓名:");
 90         scanf("%s",account.name);
 91         printf("请设置密码(前6位有效):");
 92         scanf("%s",account.pwd);
 93         printf("请确认密码:");
 94         scanf("%s",tmp);
 95         if(!strncmp(tmp,account.pwd,6))
 96             flag = true;
 97         isfst = false;
 98     }
 99     printf("输入有效,请输入存款金额:");
100     scanf("%lf",&account.money);
101     MSG msg={CT_OPEN,account};
102     printf("正在为您开户...
");
103     MSG bkres = SendMsg(&msg);
104     if(bkres.mtype) {
105         printf("开户成功,您的ID是%d,这是登陆凭据,请牢记!
",bkres.data.id);
106         printf("正在进入ATM服务系统...
");
107         initclient(bkres.data.id);
108     }
109     else {
110         printf("开户失败!
");
111         exit(-1);
112     }
113 }
114 
115 MSG SendMsg(MSG* pMsg) {
116     MSG bkres = {};//保存服务器处理结果
117     int r = msgsnd(msgid1,pMsg,sizeof(pMsg->data),0);
118     if(r == -1) {
119         printf("发送给服务器的请求失败了T_T!
");
120         exit(-1);
121     }
122     //mtype==-9 接受 消息 类型为 8-9的消息 
123     //头文件内只定义了这两个
124     r = msgrcv(msgid2,&bkres,sizeof(bkres),-9,0);
125     if(r== -1) {
126         printf("接受服务器处理结果失败!
");
127         exit(-1);
128     }
129     return bkres;
130 }
131 
132 void CheckAct(){
133     signal(SIGQUIT,CheckAct);
134     MSG msg;
135     msg.mtype = CT_OPEN;
136     int id;
137     char pwd[7]= {};
138     printf("
请输入的卡号:");
139     scanf("%d",&id);
140     printf("你输入的卡号是%d,",id);
141     printf("如需重新输入,请按CRTL+‘\’
");
142     printf("现在请输入密码:");
143     scanf("%s",pwd);
144     Account account = {};
145     account.id = id;
146     strcpy(account.pwd,pwd);
147     msg.mtype = CT_LOGN;
148     msg.data = account;
149     MSG bkres = SendMsg(&msg);
150     if(bkres.mtype == SV_SUCS) {
151         printf("密码正确登陆成功!
");
152         initclient(id);
153     }
154     else {
155         printf("密码有误,登陆失败!
");
156         exit(-1);
157     }
158 }
159 
160 void DelAct(int id) {
161     MSG msg;
162     msg.data.id = id;
163     msg.mtype = CT_DELT;
164     MSG bkres = SendMsg(&msg);
165     if(bkres.mtype == SV_SUCS) {
166         printf("账户已注销,您已被迫退出系统!
");
167         exit(0);
168     }
169     else {
170         printf("删除失败,请联系客服人员!
");
171         exit(-1);
172     }
173 }
174 
175 void SaveMny(int id) {
176     double save = 0;
177     while(save <= 0) {
178         printf("请输入存款金额:");
179         scanf("%lf",&save);
180     }
181     MSG msg;
182     msg.data.id = id;
183     msg.data.money = save;
184     msg.mtype = CT_SAVE;
185     printf("%d
",msg.data.id);
186     MSG bkres = SendMsg(&msg);
187     printf("%d
",bkres.mtype);
188     if(bkres.mtype == SV_SUCS)
189         printf("存款成功!
");
190     else {
191         printf("存款失败,请联系客服人员!
");
192         exit(-1);
193     }
194 }
195 
196 
197 void DrawMny(int id){
198     double draw = 0;
199     while(draw <= 0) {
200         printf("请输入取款金额:");
201         scanf("%lf",&draw);
202     }
203     MSG msg;
204     msg.data.id = id;
205     msg.data.money = draw;
206     msg.mtype = CT_DRAW;
207     MSG bkres = SendMsg(&msg);
208     if(bkres.mtype == SV_SUCS)
209         printf("取款成功!
");
210     else {
211         printf("没钱取你妹!
");
212         exit(-1);
213     }
214 }
215 
216 
217 double QuryMny(int id) {
218     double mny=0;
219     MSG msg;
220     msg.data.id = id;
221     msg.mtype = CT_QURY;
222     MSG bkres = SendMsg(&msg);
223     if(bkres.mtype == SV_SUCS) {
224         printf("你的余额:%lf
",bkres.data.money);
225         mny = bkres.data.money;
226     }
227     else {
228         printf("查询失败!
");
229         exit(-1);
230     }
231     return mny;
232 }
233 
234 void TrnsMny(int id) {
235     int destid=0;
236     printf("请输入转帐的ID:");
237     scanf("%d",&destid);
238     if(!ActExist(destid)) {
239         printf("不存在这个账户!
");
240         exit(-1);
241     }
242     double money =0 ;
243     while(money <= 0 ) {
244         printf("输入要转的金额:");
245         scanf("%lf",&money);
246     }
247     if(QuryMny(id) < money) {
248         printf("你自己的钱都不够呢!
");
249         exit(-1);
250     }
251     else {
252         Change(id,-money);
253         Change(destid,money);
254         printf("转帐成功!
");
255     }
256 }
257 
258 
259 void Change(int id,double money) {
260     MSG msg;
261     msg.data.id = id;
262     msg.data.money = money;
263     msg.mtype = CT_SAVE;
264     MSG bkres = SendMsg(&msg);
265 }
266 
267 //转帐时检测,目标账户是否存在
268 int ActExist(int id) {
269     MSG msg;
270     msg.data.id = id;
271     msg.mtype = CT_QURY;
272     MSG bkres = SendMsg(&msg);
273     if(bkres.mtype == SV_SUCS)
274         return 1;
275     else
276         return 0;
277 }
原文地址:https://www.cnblogs.com/ittinybird/p/4397315.html