zoukankan      html  css  js  c++  java
  • 用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 }
  • 相关阅读:
    vue中Axios的封装和API接口的管理
    如何配置Webpack/Vue-CLI实现前端跨域(附跨域大全)
    前端面试几个重要知识点
    js常用函数
    JS中的枚举和不可枚举
    可配置性属性和不可配置性属性
    Object.create()和深拷贝
    JavaScript 中 call()、apply()、bind() 的用法
    从深入到通俗:Object.prototype.toString.call()
    js原生实现三级联动下拉菜单
  • 原文地址:https://www.cnblogs.com/ittinybird/p/4397315.html
Copyright © 2011-2022 走看看