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 }
  • 相关阅读:
    python3 TypeError: a bytes-like object is required, not 'str'
    Centos 安装Python Scrapy PhantomJS
    Linux alias
    Vim vimrc配置
    Windows下 Python Selenium PhantomJS 抓取网页并截图
    Linux sort
    Linux RSync 搭建
    SSH隧道 访问内网机
    笔记《鸟哥的Linux私房菜》7 Linux档案与目录管理
    Tornado 错误 "Global name 'memoryview' is not defined"
  • 原文地址:https://www.cnblogs.com/ittinybird/p/4397315.html
Copyright © 2011-2022 走看看