清明假期三天没出寝室的门,先是把独立的博客折腾好了。域名备案还没好。域名是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("服务器启动,正在新建消息队列...\n"); 25 sleep(1);//模拟等待 = =! 26 int msgid1 = msgget(key1,IPC_CREAT|0666); 27 if(msgid1 == -1) { 28 printf("消息队列1创建失败!\n"); 29 exit(-1); 30 } 31 int msgid2 = msgget(key2,IPC_CREAT|0666); 32 if(msgid2 == -1) { 33 printf("消息队列2创建失败!\n"); 34 exit(-1); 35 } 36 *m1 = msgid1; 37 *m2 = msgid2; 38 printf("发送和接受消息队列创建成功!\n"); 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("正在等待客户端请求!\n"); 53 pid_t pid= vfork(); 54 if(pid == -1) { 55 printf("vfork %m\n"); 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("处理结束!\n"); 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!\n"); 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("删除消息队列成功!\n"); 119 printf("正常退出!\n"); 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文件失败!\n"); 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文件失败!\n"); 150 exit(-1); 151 } 152 printf("创建id文件成功,正在写入账户信息...\n"); 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("写入账户信息到文件失败!\n"); 158 exit(-1); 159 } 160 close(fd); 161 msg.mtype = SV_SUCS; 162 msg.data.id = oldid; 163 SndBack(msg); 164 printf("客户端开户请求已经通过,成功返回给客户端!\n"); 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!\n"); 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("客户端登陆密码正确,等待服务请求...\n"); 203 } 204 else { 205 msg.mtype = SV_FAIL; 206 SndBack(msg); 207 printf("客户端登陆密码错误。\n"); 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的信息文件已经删除,账户已销户!\n",msg.data.id); 226 //此时客户端收到SV_SUCS消息就会关闭。 227 printf("等待下一个客户端...\n"); 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("等待下一个客户端请求!\n"); 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("查询余额结果已经返回!\n"); 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失败!\n"); 23 exit(-1); 24 } 25 msgid2 = msgget(key2,IPC_CREAT|0666); 26 if(msgid2 == -1) { 27 printf("获取msgid2失败!\n"); 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("没有这个选项!\n"); 59 break; 60 } 61 } 62 printf("谢谢使用,再见!\n"); 63 } 64 65 void Login() { 66 printf(LINE); 67 printf(CHO5); 68 printf(ENDL); 69 printf("退出请按CRTL+C\n"); 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("没有这个选项!\n"); 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("正在为您开户...\n"); 103 MSG bkres = SendMsg(&msg); 104 if(bkres.mtype) { 105 printf("开户成功,您的ID是%d,这是登陆凭据,请牢记!\n",bkres.data.id); 106 printf("正在进入ATM服务系统...\n"); 107 initclient(bkres.data.id); 108 } 109 else { 110 printf("开户失败!\n"); 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!\n"); 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("接受服务器处理结果失败!\n"); 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("\n请输入的卡号:"); 139 scanf("%d",&id); 140 printf("你输入的卡号是%d,",id); 141 printf("如需重新输入,请按CRTL+‘\\’\n"); 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("密码正确登陆成功!\n"); 152 initclient(id); 153 } 154 else { 155 printf("密码有误,登陆失败!\n"); 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("账户已注销,您已被迫退出系统!\n"); 167 exit(0); 168 } 169 else { 170 printf("删除失败,请联系客服人员!\n"); 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\n",msg.data.id); 186 MSG bkres = SendMsg(&msg); 187 printf("%d\n",bkres.mtype); 188 if(bkres.mtype == SV_SUCS) 189 printf("存款成功!\n"); 190 else { 191 printf("存款失败,请联系客服人员!\n"); 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("取款成功!\n"); 210 else { 211 printf("没钱取你妹!\n"); 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\n",bkres.data.money); 225 mny = bkres.data.money; 226 } 227 else { 228 printf("查询失败!\n"); 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("不存在这个账户!\n"); 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("你自己的钱都不够呢!\n"); 249 exit(-1); 250 } 251 else { 252 Change(id,-money); 253 Change(destid,money); 254 printf("转帐成功!\n"); 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 }