发布时间:2025-12-09 18:06:55 浏览次数:4
在之前写的博客里面提到了上一个项目简易聊天室的问题,在这个银行管理系统上面得到了解决,通过服务器的链表将用户输入的账号密码储存起来,然后在服务器不退出的情况下,实现用户的查询、存钱、取钱、转账、改密码、销户等功能,并将其交易记录写入了Mysql的数据库中。服务器即使退出,丢失账号密码但是交易记录还在。其实账号密码也是应当存储在数据库中,这样账户密码也不会丢失,但是为了练习链表将其写在了链表中。
下面写的是安装MySQL的数据库的命令。
客户端我通过设置termios结构体中的c_lflag设置来关闭回显,保证密码安全,通过查找资料封装了一个函数来管理是否回显。
/*************************************************************************> 文件路径: client.c> 作者: Moliam> 邮箱: 2515826079@qq.com > 文件创建时间: 2019年03月20日 星期三 14时47分52秒************************************************************************/#include <stdio.h>#include <string.h>#include <stdlib.h>#include <netinet/in.h>#include <arpa/inet.h>#include <sys/socket.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <pthread.h>#include <termios.h>#include <errno.h>//由于我修改了vim的配置,所以会出现没有必要的头文件#define ECHOFLAGS (ECHO | ECHOE | ECHOK | ECHONL)//回显相关宏定义#define PORT 9709int sockfd;int text_fd;//char *IP = "192.168.1.190";char *IP = "192.168.15.2";//服务器IP地址typedef struct sockaddr AD;char acc[30]={0};//存放账户char cmd_buf[30]={0};//存放命令char dat_buf[100]={0};//存放数据int set_disp_mode(int fd,int option);//是否回显函数int getpasswd(char* passwd, int size);//获取终端输入函数void err(const char *buf,int line);//出错函数void Init(void);//初始化客户端void Service(void);//匹配帐号密码void menu(void);//菜单void enquiry(void);//查询余额void save(void);//存钱void withdraw(void);//取钱void modpw(void);//改密void trans(void);//转账void logout(void);//注销void __exit(void);int main(void){Init();Service();menu();//return 0;}void menu(void){//0查询 1存款 2取款 3转账 4改密 5销户 6退出int opt=0;while(1){printf("*********************************\n");printf(" 按0查询 \n");printf(" 按1存款 \n");printf(" 按2取款 \n");printf(" 按3转账 \n");printf(" 按4改密 \n");printf(" 按5销户 \n");printf(" 按6退出 \n");printf("*********************************\n");repeat:printf("请输入你需要进行的操作:\n");scanf("%d",&opt);if (opt > 6 && opt < 0){printf("输入错误,请重新输入!\n");goto repeat;}switch(opt){case 0:enquiry();break;case 1:save();break;case 2:withdraw();break;case 3:trans();break;case 4:modpw();break;case 5:logout();break;case 6:__exit();return ;}}}void enquiry(void){sprintf(cmd_buf,"0");text_fd = send(sockfd,cmd_buf,strlen(cmd_buf),0);//发送命令if(text_fd == -1)err("send",__LINE__);text_fd = recv(sockfd,dat_buf,sizeof(dat_buf),0);//接受余额if(text_fd == -1)err("recv",__LINE__);printf("当前余额为%s\n",dat_buf);memset(dat_buf,0,sizeof(dat_buf));}void save(void){double money = 0;sprintf(cmd_buf,"1");text_fd = send(sockfd,cmd_buf,strlen(cmd_buf),0);//发送命令if(text_fd == -1)err("send",__LINE__);printf("请输入你要存的数目:\n");scanf("%lf",&money);sprintf(dat_buf,"%.2f",money);text_fd = send(sockfd,dat_buf,strlen(dat_buf),0);//发送数目if(text_fd == -1)err("send",__LINE__);memset(dat_buf,0,sizeof(dat_buf));text_fd = recv(sockfd,dat_buf,sizeof(dat_buf),0);//接收是否成功if(text_fd == -1)err("recv",__LINE__);if(strcmp(dat_buf,"correct") != 0){printf("aha,failure!\n");}memset(dat_buf,0,sizeof(dat_buf));enquiry();}void withdraw(void){float money = 0;sprintf(cmd_buf,"2");text_fd = send(sockfd,cmd_buf,strlen(cmd_buf),0);//发送命令if(text_fd == -1)err("send",__LINE__);printf("请输入你要提现的数目:\n");scanf("%f",&money);sprintf(dat_buf,"%.2f",money);text_fd = send(sockfd,dat_buf,strlen(dat_buf),0);//发送数目if(text_fd == -1)err("send",__LINE__);memset(dat_buf,0,sizeof(dat_buf));text_fd = recv(sockfd,dat_buf,sizeof(dat_buf),0);//接收是否成功if(text_fd == -1)err("recv",__LINE__);if(strcmp(dat_buf,"correct") != 0){printf("aha,failure!\n");}memset(dat_buf,0,sizeof(dat_buf));enquiry();}void trans(void){float money = 0;char name[20];sprintf(cmd_buf,"3");text_fd = send(sockfd,cmd_buf,strlen(cmd_buf),0);//发送命令err:memset(name,0,sizeof(name));if(text_fd == -1)err("send",__LINE__);printf("请输入你要转账的账户(按quit退出):\n");scanf("%s",name);getchar();if(strcmp(name,"quit") == 0){text_fd = send(sockfd,name,strlen(name),0);return;}text_fd = send(sockfd,name,strlen(name),0);//发送名字if(text_fd == -1)err("send",__LINE__);memset(dat_buf,0,sizeof(dat_buf));text_fd = recv(sockfd,dat_buf,sizeof(dat_buf),0);//接收是否存在if(text_fd == -1)err("recv",__LINE__);if(strcmp(dat_buf,"correct") != 0){printf("aha,failure!Not such the person!\n");goto err;}memset(dat_buf,0,sizeof(dat_buf));scanf("%f",&money);sprintf(dat_buf,"%.2f",money);text_fd = send(sockfd,dat_buf,strlen(dat_buf),0);//发送数目if(text_fd == -1)err("send",__LINE__);text_fd = recv(sockfd,dat_buf,sizeof(dat_buf),0);//接收是否成功if(text_fd == -1)err("recv",__LINE__);if(strcmp(dat_buf,"correct") != 0){printf("aha,failure!You have no money!\n");}memset(dat_buf,0,sizeof(dat_buf));enquiry();}void modpw(void){char *p,passwd[20],com[20];sprintf(cmd_buf,"4");text_fd = send(sockfd,cmd_buf,strlen(cmd_buf),0);//发送命令if(text_fd == -1)err("send",__LINE__);set_disp_mode(STDIN_FILENO,0);//关闭回显getchar();getpasswd(passwd, sizeof(passwd));p=passwd;while(*p!='\n')p++;*p='\0';printf("\n");getpasswd(com, sizeof(com));p=com;while(*p!='\n')p++;*p='\0';printf("\n");set_disp_mode(STDIN_FILENO,1);//打开回显if(strcmp(passwd,com) != 0){printf("两次输入密码不一致!!\n");return ;}text_fd = send(sockfd,passwd,strlen(passwd),0);//发送密码if(text_fd == -1)err("send",__LINE__);text_fd = recv(sockfd,dat_buf,sizeof(dat_buf),0);//接收是否成功if(text_fd == -1)err("recv",__LINE__);if(strcmp(dat_buf,"correct") != 0){printf("aha,failure!\n");}memset(dat_buf,0,sizeof(dat_buf));}void logout(void){sprintf(cmd_buf,"5");text_fd = send(sockfd,cmd_buf,strlen(cmd_buf),0);//发送命令if(text_fd == -1)err("send",__LINE__);text_fd = recv(sockfd,dat_buf,sizeof(dat_buf),0);//接收是否成功if(text_fd == -1)err("recv",__LINE__);if(strcmp(dat_buf,"correct") != 0){printf("aha,failure!\n");}memset(dat_buf,0,sizeof(dat_buf));exit(-1);}void __exit(void){sprintf(cmd_buf,"6");text_fd = send(sockfd,cmd_buf,strlen(cmd_buf),0);//发送命令if(text_fd == -1)err("send",__LINE__);exit(-1);}void Service(void){char *p,passwd[20];err:printf("请输入你的帐号(无该帐号自动注册):");scanf("%s",acc);getchar();//读走回车if(strcmp(acc,"quit") == 0)//为了防止误操作进入转账页面{printf("创建失败,账户已存在\n");goto err;}set_disp_mode(STDIN_FILENO,0);getpasswd(passwd, sizeof(passwd));p=passwd;while(*p!='\n')p++;*p='\0';set_disp_mode(STDIN_FILENO,1);text_fd = send(sockfd,acc,strlen(acc),0);if(text_fd == -1)err("send",__LINE__);sleep(1);text_fd = send(sockfd,passwd,strlen(passwd),0);if(text_fd == -1)err("send",__LINE__);char mod_buf[20];memset(mod_buf,0,sizeof(mod_buf));text_fd = recv(sockfd,mod_buf,sizeof(mod_buf),0);if(text_fd == -1)err("recv",__LINE__);printf("%s\n",mod_buf);if(strcmp(mod_buf,"correct") != 0){printf("帐号密码输入错误,请重新输入!\n");goto err;}}void Init(void){sockfd = socket(PF_INET,SOCK_STREAM,0);if(sockfd == -1)err("socket",__LINE__);struct sockaddr_in addr;addr.sin_family = PF_INET;addr.sin_port = htons(PORT);addr.sin_addr.s_addr = inet_addr(IP);if(connect(sockfd,(AD*)&addr,sizeof(addr)) == -1)err("connect",__LINE__);printf("客户端初始化成功!\n");}void err(const char *buf,int line){fprintf(stderr,"出错行号是%d\n",line);perror(buf);exit(-1);}//函数set_disp_mode用于控制是否开启输入回显功能 //如果option为0,则关闭回显,为1则打开回显 int set_disp_mode(int fd,int option){ int err; struct termios term; if(tcgetattr(fd,&term)==-1){perror("不能获得终端属性!\n"); return 1; } if(option) term.c_lflag|=ECHOFLAGS; else term.c_lflag &=~ECHOFLAGS; err=tcsetattr(fd,TCSAFLUSH,&term); if(err==-1 && err==EINTR){ perror("不能设置终端属性!\n"); return 1; } return 0; }int getpasswd(char* passwd, int size)//获得密码函数{int c;int n = 0;printf("请输入密码:");do{c=getchar();if (c != '\n'|c!='\r'){passwd[n++] = c;}}while(c != '\n' && c !='\r' && n < (size - 1));//当读到回车,换行或者超出了passwd[n] = '\0';return n;}服务器通过链表来存储账号密码,然后将交易记录放在了数据库中,因为我建立的数据库名字时band_msg,数据库的建立顺序应先于服务器执行代码,不然会出现链接数据库错误的问题
/*************************************************************************> 文件路径: service.c> 作者: Moliam> 邮箱: 2515826079@qq.com > 文件创建时间: 2019年03月20日 星期三 14时47分43秒************************************************************************/#include <stdio.h>#include <string.h>#include <stdlib.h>#include <netinet/in.h>#include <arpa/inet.h>#include <sys/socket.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <pthread.h>#include <mysql/mysql.h>#include <errno.h>#define MAX_CLI 256#define PORT 9709int sockfd;int text_fd;//char *IP = "192.168.1.190";char *IP = "192.168.15.2";struct DAT//数据块,存放账户密码{char acc[30];char pwd[20];};typedef struct DAT date;struct client {date dat;struct client *next;};typedef struct sockaddr AD;//类型定义typedef struct client CLI;CLI *head = NULL;//链表头CLI *pnew = NULL;//指向下一个链表CLI *tmp = NULL;//临时指针int create_head(void);//创建链表头void Init(void);//初始化TCPvoid err(const char *buf,int line);//错误信息打印并退出void Service(void);//等待客户函数void *service_thread(void* p);//客户端服务函数float enquiry(int fd,char *acc_buf,MYSQL sql,int flag);//查询余额void save(int fd,char *acc_buf,MYSQL sql);//存钱void withdraw(int fd,char *acc_buf,MYSQL sql);//取钱void modpw(int fd,char *acc_buf,MYSQL sql);//改密void trans(int fd,char *acc_buf,MYSQL sql);//转账void logout(char *buf,MYSQL sql);//注销int main(void){create_head();Init();Service();return 0;}void Service(void){while(1){struct sockaddr_in fromaddr;int len = sizeof(fromaddr);int fd ;acc:fd = accept(sockfd,(AD*)&fromaddr,&len);if(fd == -1){//err("accept",__LINE__);printf("CONNECT ERROR!\n");goto acc;}pthread_t id;pthread_create(&id,0,service_thread,&fd);//有一个客户端即将建立连接时分配一个线程tmp = head->next;}}void *service_thread(void* p){int fd = *(int *)p;int option;char acc_buf[30],pwd_buf[20],dat_buf[30];MYSQL my_sql;//初始化结构体mysql_init(&my_sql);//连接mysql数据库band_msgif(mysql_real_connect(&my_sql,"localhost","root","1","band_msg",0,NULL,0)==NULL){perror("mysql_real_connect");return (void *)-1;}err:memset(acc_buf,0,sizeof(acc_buf));text_fd = recv(fd,acc_buf,sizeof(acc_buf),0);if(text_fd == -1)err("recv",__LINE__);memset(pwd_buf,0,sizeof(pwd_buf));text_fd = recv(fd,pwd_buf,sizeof(pwd_buf),0);if(text_fd == -1)err("recv",__LINE__);tmp = head->next;while(tmp != NULL)//若链表中能够找到该帐号{if (strcmp(acc_buf,tmp->dat.acc) == 0){if(strcmp(pwd_buf,tmp->dat.pwd) == 0){char *cor_buf = "correct";text_fd = send(fd,cor_buf,strlen(cor_buf),0);goto cor;//帐号密码输入正确}else{char *err_buf = "error";text_fd = send(fd,err_buf,strlen(err_buf),0);goto err;}}tmp = tmp -> next;}char *cor_buf = "correct";text_fd = send(fd,cor_buf,strlen(cor_buf),0);//没有找到,自动注册,在链表尾新建一个链表pnew = (CLI *)malloc(sizeof(CLI));pnew->next = NULL;strcpy(pnew->dat.acc,acc_buf);strcpy(pnew->dat.pwd,pwd_buf);tmp=head;//确保加在了链表尾while(tmp->next!=NULL){tmp=tmp->next;}tmp->next = pnew;//为该客户创建数据表(time change balance)//为新用户创建表char cli_buf[200];memset(cli_buf,0,sizeof(cli_buf));sprintf(cli_buf,"create table %s(time int(8),exchange decimal(10,2),balance decimal(12,2))",acc_buf);//这里尽量不要用float因为float会出现一些小数计算失误,虽然我并没有用数据库的计算,时间为time函数得出来的数值,也可以改变为varchar型,//然后用asctime函数得到时间字符串//这里的exchange设置的(10,2),也代表了一次交易最大99999999.99,需要该上限的话可以改一下,后面的余额也一样if((mysql_real_query(&my_sql,cli_buf,strlen(cli_buf)))!=0){perror("mysql_real_query");return (void *)-1;}//给新用户余额赋值为0sprintf(cli_buf,"insert into %s(time,exchange,balance)values(%ld,0,0)",acc_buf,time(NULL));if((mysql_real_query(&my_sql,cli_buf,strlen(cli_buf)))!=0){perror("mysql_real_query");return (void *)-1;}cor:option = 0;while(1){MYSQL my_sql;//初始化结构体mysql_init(&my_sql);//连接mysql数据库band_mesif(mysql_real_connect(&my_sql,"localhost","root","xxxxx","band_msg",0,NULL,0)==NULL)//链接数据库{perror("mysql_real_connect");return (void *)-1;}text_fd = recv(fd,dat_buf,sizeof(dat_buf),0);if(text_fd == -1)err("recv",__LINE__);option = atoi(dat_buf);//将接收到的命令转换为整型,因为传来的是字符串//0查询 1存款 2取款 3转账 4改密 5销户 6退出switch(option){case 0:enquiry(fd,acc_buf,my_sql,1);break;case 1:save(fd,acc_buf,my_sql);break;case 2:withdraw(fd,acc_buf,my_sql);break;case 3:trans(fd,acc_buf,my_sql);break;case 4:modpw(fd,acc_buf,my_sql);break;case 5:logout(acc_buf,my_sql);break;case 6:mysql_close(&my_sql);pthread_exit((void *)1);}}}float enquiry(int fd,char *acc_buf,MYSQL sql,int flag)//flag为标志位,表示给不给客户端发送余额,如果发送接收对应不齐会出现bug{//查询余额并发送//select balance from tablename order by time desc limit 1选取最新一次的数据的余额char buf[100],money[100];memset(buf,0,sizeof(buf));memset(money,0,sizeof(money));sprintf(buf,"select balance from %s order by time desc limit 1",acc_buf);//这里获得的结果集就是最后一次的交易余额,只有一行一列if((mysql_real_query(&sql,buf,strlen(buf))) != 0)err("mysql_real_query",__LINE__);MYSQL_RES *p=NULL;//获取到的数据库结果存放在了该结构体中p = mysql_store_result(&sql);if(p == NULL)err("mysql_store_result",__LINE__);int col = mysql_num_fields(p);int row = mysql_num_rows(p);MYSQL_ROW dat;unsigned int i;while((dat = mysql_fetch_row(p)))//在MySQL官网找的程序,可以循环得到结果集,我只选取了一行一列{unsigned long *lengths;lengths = mysql_fetch_lengths(p);for(i = 0;i< col;i++){sprintf(money,"%.*s",(int)lengths[i],dat[i]?dat[i]:"NULL");if(flag == 1)text_fd = send(fd,money,strlen(money),0);return atof(money);}}mysql_free_result(p);//使用完结果集之后必须释放掉其内存空间return -1;}void save(int fd,char *acc_buf,MYSQL sql){//接收存钱的数目//发送成功或者失败char buf[100],money[100];float saving,balance;memset(buf,0,sizeof(buf));memset(money,0,sizeof(money));balance = enquiry(fd,acc_buf,sql,0);text_fd = recv(fd,money,sizeof(money),0);if(text_fd == -1)err("recv",__LINE__);saving = atof(money);//字符型转换为浮点型sprintf(buf,"insert into %s(time,exchange,balance)values(%ld,%f,%f)",acc_buf,time(NULL),saving,balance+saving);if((mysql_real_query(&sql,buf,strlen(buf)))!=0){perror("mysql_real_query");return ;}text_fd = send(fd,"correct",strlen("correct"),0);return ;}void withdraw(int fd,char *acc_buf,MYSQL sql){//接收取款的数目//发送成功或者失败char buf[100],money[100];float wd,balance;memset(buf,0,sizeof(buf));memset(money,0,sizeof(money));balance = enquiry(fd,acc_buf,sql,0);text_fd = recv(fd,money,sizeof(money),0);if(text_fd == -1)err("recv",__LINE__);wd = atof(money);if(balance < wd){text_fd = send(fd,"error",strlen("error"),0);return;}sprintf(buf,"insert into %s(time,exchange,balance)values(%ld,%f,%f)",acc_buf,time(NULL),wd,balance-wd);if((mysql_real_query(&sql,buf,strlen(buf)))!=0){perror("mysql_real_query");return ;}text_fd = send(fd,"correct",strlen("correct"),0);}void trans(int fd,char *acc_buf,MYSQL sql){//接受名字//发送是否存在//接受数目//发送是否成功int exist=0;float tran,balance1,balance2;char name[20],money[100],dat_buf[130];err:memset(name,0,sizeof(name));memset(money,0,sizeof(money));memset(dat_buf,0,sizeof(dat_buf));text_fd = recv(fd,name,sizeof(name),0);if(text_fd == -1)err("recv",__LINE__);if(strcmp(name,"quit") == 0)return;tmp = head->next;while(tmp != NULL)//若链表中能够找到该帐号{if (strcmp(name,tmp->dat.acc) == 0)exist = 1; tmp = tmp -> next;}if(exist == 0){text_fd = send(fd,"error",strlen("error"),0);goto err;}elsetext_fd = send(fd,"correct",strlen("correct"),0);text_fd = recv(fd,money,sizeof(money),0);if(text_fd == -1)err("recv",__LINE__);tran = atof(money);balance1 = enquiry(fd,name,sql,0);//得到两人余额并进行计算balance2 = enquiry(fd,acc_buf,sql,0);sprintf(dat_buf,"insert into %s(time,exchange,balance)values(%ld,%f,%f)",name,time(NULL),tran,balance1+tran);if((mysql_real_query(&sql,dat_buf,strlen(dat_buf)))!=0){perror("mysql_real_query");return ;}memset(dat_buf,0,sizeof(dat_buf));sprintf(dat_buf,"insert into %s(time,exchange,balance)values(%ld,%f,%f)",acc_buf,time(NULL),tran,balance2-tran);if((mysql_real_query(&sql,dat_buf,strlen(dat_buf)))!=0){perror("mysql_real_query");return ;}text_fd = send(fd,"correct",strlen("correct"),0);}void modpw(int fd,char *acc_buf,MYSQL sql){//接受密码//发送是否成功char pw[100];memset(pw,0,sizeof(pw));text_fd = recv(fd,pw,sizeof(pw),0);if(text_fd == -1)err("recv",__LINE__);tmp = head->next;while(tmp != NULL)//若链表中能够找到该帐号{if (strcmp(acc_buf,tmp->dat.acc) == 0){strcpy(tmp->dat.pwd,pw);}tmp = tmp -> next;}text_fd = send(fd,"correct",strlen("correct"),0);}void logout(char *buf,MYSQL sql){CLI * tmp1 = NULL;CLI * tmp2 = NULL;tmp2=head;tmp1=head->next;while(tmp1 != NULL)//找到该帐号{if (strcmp(buf,tmp1->dat.acc) == 0){tmp2->next = tmp1->next;free(tmp1);return ;}tmp2 = tmp2->next;tmp1 = tmp1->next;}char drop_buf[150];sprintf(drop_buf,"drop table %s",buf);//删除其数据库if((mysql_real_query(&sql,drop_buf,strlen(drop_buf)))!=0){perror("mysql_real_query");return ;}}int create_head(void){head = (CLI *)malloc(sizeof(CLI));if(head == NULL)err("CREATE FAILURE!",__LINE__);else head->next = NULL;return 1;}//TCP协议前面的步骤初始化void Init(void){sockfd = socket(PF_INET,SOCK_STREAM,0);//IPV4if(sockfd == -1)err("socket",__LINE__);struct sockaddr_in addr;addr.sin_family = PF_INET;addr.sin_port = htons(PORT);//端口号addr.sin_addr.s_addr = inet_addr(IP);//服务器为本机if(bind(sockfd,(AD *)&addr,sizeof(addr)) == -1)err("bind",__LINE__);if(listen(sockfd,MAX_CLI) == -1)err("listen",__LINE__);printf("服务器初始化成功\n");}void err(const char *buf,int line){fprintf(stderr,"出错行号是%d\n",line);perror(buf);exit(-1);}我写的简易聊天室和银行管理系统使用了许多相同的函数,因为之前写过比较方便嘛,思路也大体一致,只不过银行管理系统的功能更加完备一些,使用到的知识也相对多一些。