【C语言】网络编程之网上银行(socket、tcp)

此项目为linux 下多进程编程银行小系统(消息队列版本)改进版
通过socket套接字以及tcp协议是客户端和服务端可以通过本地网络交互数据

之前的消息队列版本:传送门
网上银行版本:码云传送门github传送门

总体思路:

依旧分客户端(client)和服务端(server)两个端口,将需要传递数据的账户结构体放在bank.h中,tools.c中放一些常用的工具函数。
客户端发送数据,包括操作数据类型和实际的账户数据,服务端则根据接收的不同的数据类型,执行不同的操作等,再将结果存入buf字符串发送给客户端接收,客户端接收返回结果并显示。

项目要求

主要分为两人大模块:

客户端
	1、进入时的功能开户、销户、登录、解锁
		开户:输入姓名、身份证号、设置密码,如果开户成功,则服务器上保存一个账号信号(一个账号存一个文件,文件名建议是账号)。
		销户:输入帐号、密码,服务器询问是否确认销户,如果确认则服务器删除帐号文件,并记录帐号。
		登录:输入账号、密码,三次错误账号锁定。
		解锁:输入账号、身份证号解锁。
	2、登录成功:存钱、取钱、转账、查询、修改密码
		存钱:输入存钱金额
		取钱:输入取钱金额
		转账:目标帐号和要转的金额
		查询:不需要输入数据
		修改密码:原密码和新密码
服务器
	如果识别功能:根据消息的类型识别客户端请求的功能。
	开启服务各项功能的子进程
		各进程按照消息类型接收消息
	
头文件

#define key 123456

帐号结构体
{
	帐号
	身份证号
	密码
	金额
}

消息结构体
{
	消息类型
	帐号结构体
}

客户端代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "tools.h"
#include "bank.h"

#define BUF_SIZE (4096)

int cli_fd;
Acc acc = {1,"1","1","1",1,1};

void show_acc(Acc* acc)
{
	printf("%d %s %s %s %f %d\n",acc->type,acc->bank,acc->card,acc->pass,acc->balance,acc->islock);
}

void acc_to_str(Acc* acc,char* buf)
{
	sprintf(buf,"%d %s %s %s %f %d",acc->type,acc->bank,acc->card,acc->pass,acc->balance,acc->islock);
}

// 主菜单
void main_menu(void)
{
	puts("******欢迎使用指针银行******");
	puts("1、开户		2、登陆");
	puts("3、销户		4、解锁");
	puts("0、退出");
}

// 子菜单
void sub_menu(void)
{
	puts("******恭喜您登陆成功******");
	puts("1、存款		2、取款");
	puts("3、查询		4、转账");
	puts("5、改密		6、退出");
}

// 存款
void save(void)
{
	acc.type = M_SAVE;
	printf("请输入存款金额:");
	scanf("%f",&acc.balance);
}

// 取款
void take(void)
{
	acc.type = M_TAKE;
	printf("请输入取款金额:");
	scanf("%f",&acc.balance);
}

// 查询
void query(void)
{
	acc.type = M_QUERY;
}

// 转账
void transfer(void)
{
	acc.type = M_TRANSFER;
	printf("请输入目标银行卡号:");
	get_str(acc.card,20);
	printf("请输入转账金额:");
	scanf("%f",&acc.balance);
}

// 改密
void repass(void)
{
	char pass1[20] = {} , pass2[20] = {};
	printf("请输入新密码:");
	get_pass(pass1,20,true);
	printf("请再次输入新密码:");
	get_pass(pass2,20,true);
	if(strcmp(pass1,pass2))
	{
		puts("两次输入的密码不相同,修改失败!");
	}
	else
	{
		acc.type = M_REPASS;
		strcpy(acc.pass,pass1);
	}
}

// 开户
void open_acc(void)
{
	acc.type = M_OPEN;
	printf("请输入身份证号:");
	get_str(acc.card,20);
	printf("请输入开户金额:");
	scanf("%f",&acc.balance);
}

// 登陆
void login(void)
{
	acc.type = M_LOGIN;
	printf("请输入银行卡号:");
	get_str(acc.bank,20);
	printf("请输入密码:");
	//get_str(acc.pass,20);
	get_pass(acc.pass,20,true);
	
	// 向服务端发送消息
	char buf[BUF_SIZE];
	sprintf(buf,"%d %s %s %s %f %d",acc.type,acc.bank,acc.card,acc.pass,acc.balance,acc.islock);
	int send_size = write(cli_fd,buf,strlen(buf)+1);
		
	// 从服务端接收消息
	int recv_size = read(cli_fd,buf,sizeof(buf));

	// 显服务端执行的结果
	puts(buf);
	anykey_continue();
	if('Y' != buf[0])
	{
		return;
	}
	
	for(;;)
	{
		sub_menu();
		switch(get_cmd('1','6')-'0'+4)
		{
		case M_SAVE: 	save();		break;	// 存款
		case M_TAKE: 	take();		break;	// 取款
		case M_QUERY: 	query();	   break;	// 查询
		case M_TRANSFER:transfer();	break;	// 转账
		case M_REPASS:	repass();	   break;	// 改密
		default: return ;break;// 退出
		}
		
		// 向服务端发送消息
		char msg[BUF_SIZE];
		sprintf(msg,"%d %s %s %s %f %d",acc.type,acc.bank,acc.card,acc.pass,acc.balance,acc.islock);
		write(cli_fd,msg,strlen(msg)+1);
		
		// 从服务端接收消息
		read(cli_fd,msg,sizeof(msg));
		
		// 显服务端执行的结果
		puts(msg);
		anykey_continue();
	}

}

// 销户
void destory(void)
{
	acc.type = M_DESTORY;
	printf("请输入银行卡号:");
	get_str(acc.bank,20);
	printf("请输入身份证号:");
	get_str(acc.card,20);
	printf("请输入密码:");
	get_pass(acc.pass,20,true);
}

// 解锁
void unlock(void)
{
	acc.type = M_UNLOCK;
	printf("请输入银行卡号:");
	get_str(acc.bank,20);
	printf("请输入身份证号:");
	get_str(acc.card,20);
	printf("请输入密码:");
	get_pass(acc.pass,20,true);
}

void exit_bank(void)
{
	char buf[BUF_SIZE];
	sprintf(buf,"quit");
	write(cli_fd,buf,strlen(buf)+1);
	close(cli_fd);
	exit(0);
}

int main()
{
	//创建socket对象
	cli_fd = socket(AF_INET,SOCK_STREAM,0);
	if(0 > cli_fd)
	{
		perror("socket");
		return -1;
	}

	//准备通信地址
	struct sockaddr_in addr = {};
	addr.sin_family = AF_INET;
	addr.sin_port = htons(6789);
	addr.sin_addr.s_addr = inet_addr("10.0.2.15");
	socklen_t addrlen = sizeof(addr);

	//链接服务器
	if(connect(cli_fd,(struct sockaddr*)&addr,addrlen))
	{
		perror("connect");
		return -1;
	}

	for(;;)
	{
		main_menu();
		switch(get_cmd('0','4')-'0')
		{
		case M_EXIT:	exit_bank();	break;	// 退出
		case M_OPEN:	open_acc();	break;	// 开户
		case M_LOGIN: 	login();	continue;	// 登陆
		case M_DESTORY: destory();	break;	// 销户
		case M_UNLOCK:	unlock();	break; 	// 解锁
		}
		
		char buf[BUF_SIZE];
		//sprintf(buf,"%d %s %s %s %f %d",acc.type,acc.bank,acc.card,acc.pass,acc.balance,acc.islock);
		acc_to_str(&acc,buf);
		
		// 向服务端发送消息
		int send_size = write(cli_fd,buf,strlen(buf)+1);
		
		// 从服务端接收消息
		int recv_size = read(cli_fd,buf,sizeof(buf));
		
		// 显服务端执行的结果
		puts(buf);
		anykey_continue();
	}
}

服务端代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>
#include "bank.h"
#include "tools.h"

#define BUF_SIZE (4096)

//服务端socket对象
int svr_fd;
//银行账户后7位id
int id = 1234001;

//解析字符串到结构体中
void str_to_acc(Acc* acc,char* buf)
{
	sscanf(buf,"%d %s %s %s %f %d",&acc->type,acc->bank,acc->card,acc->pass,&acc->balance,&acc->islock);
}

//显示数据
void show_acc(Acc* acc)
{
	printf("%d %s %s %s %f %d\n",acc->type,acc->bank,acc->card,acc->pass,acc->balance,acc->islock);
}

//开户
void open_acc(Acc* acc,char* buf)
{
	printf("%s\n",__func__);
	// 获取银行卡号
	sprintf(acc->bank,"19992000%d",id++);
	// 设置初始密码
	sprintf(acc->pass,"123456");
	// 设置锁定状态
	acc->islock = 0;
	
	char path[256] = {};
	sprintf(path,"%s%s",ACC_PATH,acc->bank);

	int fd = open(path,O_WRONLY|O_CREAT|O_TRUNC|O_EXCL,0644);
	if(0 > fd)
	{
		sprintf(buf,"开户失败,服务器正在升级,请稍候!");
	}
	else
	{
		sprintf(buf,"开户成功,您的账号为:%s",acc->bank);
		show_acc(acc);
		write(fd,acc,sizeof(Acc));
		close(fd);
	}
}

//登陆
void login(Acc* acc,char* buf)
{
	printf("%s\n",__func__);
	
	char path[256] = {};
	sprintf(path,"%s%s",ACC_PATH,acc->bank);
	printf("%s\n",path);
	// 判断银行卡号是否正确
	if(0 != access(path,F_OK))
	{
		sprintf(buf,"N:卡号不存在,请检查!");
		return;
	}
	
	int fd = open(path,O_RDWR);
	if(0 > fd)
	{
		perror("open");
		sprintf(buf,"N:服务器正在升级,登陆失败!");
		puts("登陆失败");
		return;
	}
	
	Acc local_acc = {};
	read(fd,&local_acc,sizeof(Acc));
	show_acc(&local_acc);
	if(3 <= local_acc.islock)
	{
		sprintf(buf,"N:登陆失败,帐号已经锁住!");
		puts("登陆失败,帐号已经锁住!");
	}
	else if(0 != strcmp(local_acc.pass,acc->pass))
	{	
		local_acc.islock++;
		sprintf(buf,"N:登陆失败,帐号或者密码输错(您还有%d次机会)!",3-local_acc.islock);
		printf("%s\n",acc->pass);
		puts("登陆失败,密码输错!");
	}
	else
	{
		local_acc.islock = 0;
		sprintf(buf,"Y:登陆成功");
		puts("登陆成功");
	}
	fd = open(path,O_RDWR|O_TRUNC,0644);
	write(fd,&local_acc,sizeof(Acc));
	close(fd);
}

//销户
void destory(Acc* acc,char* buf)
{
	printf("%s\n",__func__);
	
	char path[256] = {};
	sprintf(path,"%s%s",ACC_PATH,acc->bank);
	// 判断银行卡号是否正确
	if(0 != access(path,F_OK))
	{
		sprintf(buf,"N:卡号不存在,请检查!");
		return;
	}
	
	int fd = open(path,O_RDWR);
	if(0 > fd)
	{
		perror("open");
		sprintf(buf,"N:服务器正在升级,销户失败");
		return;
	}
	
	Acc local_acc = {};
	read(fd,&local_acc,sizeof(Acc));
	show_acc(&local_acc);
	close(fd);
	
	if(strcmp(acc->card,local_acc.card))
	{
		sprintf(buf,"N:身份证号不正确,销户失败!");
		return;
	}
	
	if(strcmp(acc->pass,local_acc.pass))
	{
		sprintf(buf,"N:密码不正确,销户失败!");
		return;
	}
	
	if(remove(path))
	{
		perror("remove");
		sprintf(buf,"N:服务器正在升级,销户失败!");
		return;
	}
	
	sprintf(buf,"Y:销户成功,期待下次与你相遇!");
}

//解锁
void unlock(Acc* acc,char* buf)
{
	printf("%s\n",__func__);
	
	char path[256] = {};
	sprintf(path,"%s%s",ACC_PATH,acc->bank);
	// 判断银行卡号是否正确
	if(0 != access(path,F_OK))
	{
		sprintf(buf,"N:卡号不存在,请检查!");
		return;
	}
	
	int fd = open(path,O_RDWR);
	if(0 > fd)
	{
		perror("open");
		sprintf(buf,"N:服务器正在升级,解锁失败");
		return;
	}
	
	Acc local_acc = {};
	read(fd,&local_acc,sizeof(Acc));
	show_acc(&local_acc);
	close(fd);
	
	if(strcmp(acc->card,local_acc.card))
	{
		sprintf(buf,"N:身份证号不正确,解锁失败!");
		return;
	}
	
	if(strcmp(acc->pass,local_acc.pass))
	{
		sprintf(buf,"N:密码不正确,解锁失败!");
		return;
	}
	
	local_acc.islock = 0;
	//lseek(fd,0,SEEK_SET);
	fd = open(path,O_RDWR|O_TRUNC,0644);
	write(fd,&local_acc,sizeof(Acc));
	close(fd);
	
	sprintf(buf,"Y:解锁成功!");
}

//存钱
void save(Acc* acc,char* buf)
{
	printf("%s\n",__func__);
	
	char path[256] = {};
	sprintf(path,"%s%s",ACC_PATH,acc->bank);
	
	int fd = open(path,O_RDWR);
	if(0 > fd)
	{
		perror("open");
		sprintf(buf,"N:服务器正在升级,存款失败!");
		return;
	}
	
	Acc local_acc = {};
	read(fd,&local_acc,sizeof(Acc));

	local_acc.balance += acc->balance;
	//lseek(fd,0,SEEK_SET);
	fd = open(path,O_RDWR|O_TRUNC,0644);
	write(fd,&local_acc,sizeof(Acc));
	close(fd);
	
	sprintf(buf,"Y:存款成功,当前余额为:%.2f!",local_acc.balance);
}

//取钱
void take(Acc* acc,char* buf)
{
	printf("%s\n",__func__);
	
	char path[256] = {};
	sprintf(path,"%s%s",ACC_PATH,acc->bank);
	
	int fd = open(path,O_RDWR);
	if(0 > fd)
	{
		perror("open");
		sprintf(buf,"N:服务器正在升级,取款失败!");
		return;
	}
	
	Acc local_acc = {};
	read(fd,&local_acc,sizeof(Acc));

	if(local_acc.balance < acc->balance)
	{
		sprintf(buf,"N:余额不足,取款失败。当前余额为:%.2f!",local_acc.balance);
		close(fd);
		return;
	}
	
	local_acc.balance -= acc->balance;
	//lseek(fd,0,SEEK_SET);
	fd = open(path,O_RDWR|O_TRUNC,0644);
	write(fd,&local_acc,sizeof(Acc));
	close(fd);
	
	sprintf(buf,"Y:取款成功,当前余额为:%.2f!",local_acc.balance);
}

//查询
void query(Acc* acc,char* buf)
{
	printf("%s\n",__func__);
	
	char path[256] = {};
	sprintf(path,"%s%s",ACC_PATH,acc->bank);
	
	int fd = open(path,O_RDWR);
	if(0 > fd)
	{
		perror("open");
		sprintf(buf,"N:服务器正在升级,查询失败!");
		return;
	}
	
	Acc local_acc = {};
	read(fd,&local_acc,sizeof(Acc));
	close(fd);
	
	sprintf(buf,"Y:查询成功,当前余额为:%.2f!",local_acc.balance);
}

//转帐
void transfer(Acc* acc,char* buf)
{
	printf("%s\n",__func__);
	
	char src_path[256] = {} , dest_path[256];
	
	sprintf(src_path,"%s%s",ACC_PATH,acc->bank);
	sprintf(dest_path,"%s%s",ACC_PATH,acc->card);
	
	// 判断银行卡号是否正确
	if(0 != access(dest_path,F_OK))
	{
		sprintf(buf,"N:目标卡号不存在,请检查!");
		return;
	}
	
	int src_fd = open(src_path,O_RDWR);
	int dest_fd = open(dest_path,O_RDWR);
	if(0 > src_fd || 0 > dest_fd)
	{
		error("open");
		sprintf(buf,"N:服务器正在升级,转账失败");
		return;
	}
	
	Acc src_acc = {} , dest_acc = {};
	read(src_fd,&src_acc,sizeof(Acc));
	read(dest_fd,&dest_acc,sizeof(Acc));

	if(src_acc.balance <  acc->balance)
	{
		sprintf(buf,"N:余额不足,转账失败。当前余额为:%.2f!",src_acc.balance);
		return;
	}

	src_acc.balance -= acc->balance;
	dest_acc.balance += acc->balance;
	
	
	lseek(src_fd,0,SEEK_SET);
	lseek(dest_fd,0,SEEK_SET);
	write(src_fd,&src_acc,sizeof(Acc));
	write(dest_fd,&dest_acc,sizeof(Acc));
	close(src_fd);
	close(dest_fd);
	
	sprintf(buf,"Y:转账成功,当前余额为:%.2f!",src_acc.balance);
}

//修改密码
void repass(Acc* acc,char* buf)
{
	printf("%s\n",__func__);
	
	char path[256] = {};
	sprintf(path,"%s%s",ACC_PATH,acc->bank);
	
	int fd = open(path,O_WRONLY);
	if(0 > fd)
	{
		error("open");
		sprintf(buf,"N:服务器正在升级,查询失败!");
		return;
	}
	
	Acc local_acc = {};
	read(fd,&local_acc,sizeof(Acc));
	strcpy(local_acc.pass,acc->pass);
	write(fd,&local_acc,sizeof(Acc));
	
	sprintf(buf,"Y:修改密码成功!");
}

//客户端线程
void* server(void* arg)
{
	int cli_fd = *(int*)arg;
	Acc* acc = malloc(sizeof(Acc));
	for(;;)
	{
		char buf[BUF_SIZE];
		int recv_size = read(cli_fd,buf,sizeof(buf));
		if(0 >= recv_size || 0 == strcmp(buf,"quit"))
		{
			printf("客户端%d退出!\n",cli_fd);
			free(acc);
			close(cli_fd);
			pthread_exit(NULL);
		}
		//解析字符串
		str_to_acc(acc,buf);
		//show_acc(acc);
		
		//根据字符串type操作
		switch(acc->type)
		{
			case M_OPEN: open_acc(acc,buf); break;		//开户
			case M_LOGIN: login(acc,buf); break;		//登陆
			case M_DESTORY: destory(acc,buf); break;	//销户
			case M_UNLOCK: unlock(acc,buf); break;		//解锁
			case M_SAVE: save(acc,buf); break;			//存钱
			case M_TAKE: take(acc,buf); break;			//取钱
			case M_QUERY: query(acc,buf); break;		//查询
			case M_TRANSFER: transfer(acc,buf); break;//转帐
			case M_REPASS: repass(acc,buf); break;		//改密码
		}
		write(cli_fd,buf,strlen(buf)+1);
	}
}

//服务器退出
void sigint(int signum)
{
	close(svr_fd);
	printf("服务器关闭\n");
	exit(0);
}

int main()
{
	signal(SIGINT,sigint);
	//创建socket对象
	svr_fd = socket(AF_INET,SOCK_STREAM,0);
	if(0 > svr_fd)
	{
		perror("socket");
		return -1;
	}

	//准备通信地址
	struct sockaddr_in addr = {};
	addr.sin_family = AF_INET;
	addr.sin_port = htons(6789);
	addr.sin_addr.s_addr = inet_addr("10.0.2.15");
	socklen_t addrlen = sizeof(addr);

	//绑定socket对象与地址
	if(bind(svr_fd,(struct sockaddr*)&addr,addrlen))
	{
		perror("bind");
		return -1;
	}

	//设置监听
	if(listen(svr_fd,10))
	{
		perror("listen");
		return -1;
	}

	//等待链接
	for(;;)
	{
		int cli_fd = accept(svr_fd,(struct sockaddr*)&addr,&addrlen);

		if(0 > cli_fd)
		{
			printf("accept");
			return -1;
		}

		pthread_t tid;
		pthread_create(&tid,NULL,server,&cli_fd);
	}
}
上一篇:C++ 关于日期时间(三)cout/printf/sprintf区别


下一篇:C语言-sprintf的用法---@颜麓