小区物业停车管理系统(实现多用户登录)

距离博客 数据结构课设(停车场管理系统的设计与实现)的发布,已经有三个月了,一想到当时是因为不认真听课,怕挂科,为了在课设这块多拿点分而被迫去参加答辩,就觉得很荒谬~ ,当时绞尽脑汁的想了一个星期,才勉强想到了停车场系统的一点设计思路,但是,在老师验收的前一刻却发现自己的程序少了一个功能没实现,结果又花了几天的时间修改,好在最后也是成功的改好了程序,做好了PPT,拿到了自己应得的那一份成绩。这几天在学网络编程,在《Linux系统编程》这本书上也看到了一个有关停车场的项目,刚开始觉得也没怎么样,但一细看发现写得是真的好,两个头文件上定义了大量的宏,代码的总体结构、封装的信息结构体、传输数据与接收数据的处理上让我惊讶。想到自己写的停车场课设,终归是自己的视野太窄,明明能用一两百行的代码就能解决的问题,简单易懂,自己却用了将近八百行左右才能实现,且结构混乱,代码冗余。下面将介绍该书中的一个项目——小区物业停车管理系统,其论述及代码均来源于该书中。

文章目录

      • 通过该项目我们能学到什么?
      • 首先是环境使用的说明,如下表:
    • 项目需求分析
    • 系统软件设计
    • 服务器功能实现
    • 客户端功能实现
    • 系统展示

通过该项目我们能学到什么?

  • 熟练应用系统编程接口
  • 掌握多任务机制的问题处理方法
  • 掌握项目功能模块的设计方法

首先是环境使用的说明,如下表:

名称 系统配置条件
操作系统 Linux操作系统(ubuntu 10.04)要求不高
开发语言 C语言
开发工具 Wmware 10
使用环境 网络连接环境

项目需求分析

小区物业停车管理系统是基于Linux系统编程,通过对操作文件实现的。文件作为Linux系统编程的一大重要课题,在实际开发是经常结合数据库实现信息的管理。同时,该系统利用TCP来实现支持多用户信息管理(循环服务器)。它具有功能直观易理解、操作方便、人性化等特点。
系统设置为两种类型用户使用:物业管理人员与小区业主。不同类型用户系统的使用功能也不同。普通业主的功能为查询信息,修改登录密码;而物业管理人员的功能则拥有最高权限,其权限可以查询任意、更新、添加和删除任意业主信息。系统设计思想如下流程所示:

小区物业停车管理系统(实现多用户登录)_第1张图片

系统软件设计

本系统有客户端与服务器构成,服务器通过对业主信息的处理实现与客户端的信息交互,客户端可以运行在多个不同的主机上连接服务器,实现多用户登录,完成物业管理人员或业主与登录界面的交互,工作模式如下:
小区物业停车管理系统(实现多用户登录)_第2张图片
本系统服务器的功能分为两个部分,一部分与客户端通信,另一部分为数据处理。其流程为打开数据文件并对网络进行监听。服务器接收数据则先判断登录结构体是否有变化,如果有变化,表示有新用户登录,则创建一个线程;如果没有,则表示无用户登录或者收到为已登录用户的数据。如果是用户登录,则判断是否为管理员账户。通过接收客户端操作数据文件,完成后将结果发给客户端,并返回等待下次的数据到来,如果发现错误,则发送错误信息给客户端。
小区物业停车管理系统(实现多用户登录)_第3张图片

(1)用户登录部分的功能。当客户端连接上服务器之后进入登录界面,提示用户输入用户名和密码。如果都正确就登录成功,进入相应界面;否则,返回登录界面。
(2)用户权限选择部分的功能。用户登录成功后,经过服务器端判断觉得用户进入对于的权限界面。
(3)用户信息的操作请求部分的功能。如果进入物业管理界面则可进行对用户的增删改查,若进入普通业主则只能查询和修改个人信息。
(4)退出程序部分的功能。用户操作结束后退出程序,也可返回上一层界面。

服务器功能实现

#define N 64
#define READ 1 //查询业主信息
#define CHANGE 2 //修改业主信息
#define DELETE 3 //删除业主信息
#define ADD 4 //添加业主信息
#define LOAD 5 //业主申请登陆
#define QUIT 6 //业主退出时发给服务器通知消息
#define SUCCESS 7 //服务器操作成功
#define FAILED 8 //服务器操作失败

/*用户级别宏*/
#define STAFF 10 //小区业主
#define ADM 11 //物业管理人员

typedef struct{
	int type;//判断是否为物业管理员
	char name[N];//名字
	char passwd[N];//登陆密码
	int no;//业主登记编号
}USER;

typedef struct{
	char name[N];//名字
	char addr[N];//业主小区详细住址
	char time_start[N];//停车费年卡办理时间
	char time_end[N];//年卡有效截止日期
	int location;//车库位置编号
	int no;//编号
	int card;//车库年卡费用
	char phone[N];//手机号
	int type;//用户级别
	char car_num[N];//车牌号
}INFO;

typedef struct{
	int sign;//标志符判断操作是否成功
	int type;//判断操作类型
	char name[N];//发送消息的用户
	INFO info;//住户信息结构体
	char passwd[N];//业主密码在第一次登陆使用
	char data[N];// 操作失败或成功的消息
}MSG;

/*用于登录时判断用户是否存在*/
void FindUser(MSG *);
/*用于物业管理查询业主信息*/
void FindMsg(MSG *);
/*用于添加业主登陆信息,实现后续业主登陆功能*/
void AddUser(MSG *);
/*用于物业管理添加业主详细信息,实现后续查询业主信息*/
void AddMsg(MSG *);
/*用于删除业主登陆信息*/
void DelUser(MSG *);
/*用于删除业主详细信息*/
void DelMsg(MSG *); 

代码分析如下:
上述代码在自定义的服务器程序使用的头文件server.h,分为三部分

  • 1 ~ 13行为操作标志宏定义,即服务器根据接收的标志来完成相应的动作
  • 14 ~ 42行为封装的信息结构体。
    • USER:存储用户基本登录信息,在登录时使用
    • INFO:存储业主信息,是完成添加、删除、修改操作的核心结构体
    • MSG:客户端与服务器建立通信联系,用来完成两种间的数据传输
  • 43 ~ 55行操作服务器端的操作声明,实现核心操作
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "server.h"

typedef struct sockaddr SA;

int Info_rmark = 0;//文件info.dat互斥操作的读
int Info_wmark = 0;//文件info.dat互斥操作的写
int User_rmark = 0;//文件User.dat互斥操作的读
int User_wmark = 0;//文件User.dat互斥操作的写

/******************************************************
 * 线程处理函数,每登陆一个用户,则创建一个线程用来处理
 *****************************************************/
void *handler(void *arg){
	int confd = *((int *)arg);
	MSG msg;
	int n;

	while(1){
		/*接收客户端发送信息结构体*/
		n = recv(confd, &msg, sizeof(MSG), 0);
		printf("get message from %s type:%d sign:%d\n",\
				msg.name, msg.type, msg.sign);

		/*如果收到退出宏,则线程退出,程序自动回到接收请求处*/
		if(msg.type == QUIT){
			printf("user %s quit\n", msg.name);
			pthread_exit(NULL);
			close(confd);
		}
		
		if(n == -1){
			break;
		}

		/*执行核心函数,分析客户端请求,执行相应操作*/
		GetMsg(&msg);
		printf("send message to %s type:%d sign:%d\n\n", 
				msg.name, msg.type, msg.sign);
		/*发送执行结果给客户端*/
		send(confd, &msg, sizeof(MSG), 0);
	}

	close(confd);
	pthread_exit(NULL);

}
/*服务器端主函数*/
int main(int argc, const char *argv[])
{
	/*************************************************
	 *将管理员的登陆信息先写入到文件user.dat中
	 *后续登陆时将读取文件进行判断,保证管理员登陆成功
	 ************************************************/
#if 1
	USER user;
	strcpy(user.name, "admin");
	strcpy(user.passwd, "123");

	user.type = 11;
	user.no = 111111;

	FILE *fp = fopen("./user.dat", "wb");
	fwrite(&(user), sizeof(USER), 1, fp);

	fclose(fp);
#endif

	int listenfd, confd;

	struct sockaddr_in serveraddr, clientaddr;

	/*命令行传参判断,是否符合传参规则*/
	if(argc != 3){
		printf("please input %s  \n", argv[0]);
		return -1;
	}

	/*创建流式套接字*/
	if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
		perror("socket error");
		return -1;
	}

	/*填充网络信息结构体*/
	bzero(&serveraddr, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	serveraddr.sin_port = htons(atoi(argv[2]));

	/*绑定套接字*/
	if(bind(listenfd, (SA *)&serveraddr, sizeof(serveraddr)) == -1){
		perror("bind error");
		return -1;
	}

	/*监听客户端连接*/
	listen(listenfd, 5);

	bzero(&clientaddr, sizeof(clientaddr));
	socklen_t len = sizeof(clientaddr);
	printf("listenfd = %d\n", listenfd);

	while(1){
		/*等待客户端连接请求*/
		if((confd = accept(listenfd, (SA *)&clientaddr, &len)) == -1){
			perror("accept error");
			return -1;
		}

		/*输出客户端地址以及端口信息*/
		printf("connect with ip: %s, port: %d\n",\ 
				inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));

		/*服务器收到客户端请求之后,创建线程与客户端进行通信*/
		pthread_t thread;

		if(pthread_create(&thread, NULL, handler, (void *)&confd) < 0){
			perror("pthread_create error");
			return -1;
		}
	}

	close(listenfd);
	return 0;
}

代码分析如下:
上述代码为服务器主函数操作内容。服务器供操作两个文件,user.datinfo.dat,前者用来存储各个用户的登录信息,即写入的是USER结构体,后者用于保存用户的详细信息,即写入的是INFO结构体

  • 17 ~ 20行 为文件读写标志,分别对应两个文件设置标志位,目的是为了实现文件操作时的互斥,避免出现竞态,导致文件存储出现问题。如果多用户同时登录,并进行执行操作请求,那么服务器则需要创建多个线程对文件进行操作,并发的读取文件,并不会对文件造成影响,但并发的写文件,则带来了不确定性问题。通过标志位的判断,来确定当前对共享资源(文件)的操作是否需要执行,这一点类似与多线程的同步机制信号量,采用标志位也可以达到这样的效果。
  • 21 ~ 26行 为线程处理函数,使用循环处理,可一直响应客户端的请求,线程处理函数的核心操作为 GetMsg() 函数,其功能为读取客户端发送的 MSG结构体,并进行相应的操作,后面将会介绍
  • 60 ~ 136行 为主函数操作内容,建立了网络连接,使用了循环服务器的思想,结合多线程,用来接收客户端的连接请求。

int GetMsg(MSG *msg){
	switch(msg->type){
		case READ:
			FindMsg(msg);
			break;
		case CHANGE:
			DelUser(msg);
			DelMsg(msg);
			AddUser(msg);
			AddMsg(msg);
			break;
		case ADD:
			AddUser(msg);
			AddMsg(msg);
			break;
		case DELETE:
			DelUser(msg);
			DelMsg(msg);
			break;
		case LOAD:
			FindUser(msg);
			break;
		default:
			break;
	}
	return 0;
}

代码分析如下:
上述代码为GetMsg()的操作内容,通过获取客户端回复的操作类型,执行相应的分支操作。



void FindUser(MSG *msg){
	FILE *fp;
	int flag = 0;

	/**************************************************
	 *实现对文件读写的互斥,如果写user.dat文件的写标志位大于0,
	 *表示此时有线程在对文件进行写操作,此时执行循环判断,直到
	 *标志位为0,则结束循环。
	 *************************************************/
	while(User_wmark > 0){
		usleep(100000);
	}

	/********************************************************
	 * 在执行读操作之前,将对文件user.dat的读标志位进行自加,
	 *其他任务在操作之前则判断此标志位,如果大于0,则不允许操作
	 ********************************************************/
	User_rmark++;

	/*打开存放用户登陆信息的文件*/
	if((fp = fopen("./user.dat", "rb")) == NULL){
		printf("User %s request:no file user.dat\n", msg->name);
		msg->sign = FAILED;
		strcpy(msg->data, "no file");
		return;
	}

	USER user_temp;

	/*读取文件中的信息,将存放信息的结构体USER依次取出*/
	while(fread(&user_temp, sizeof(USER), 1, fp) != 0){
		/*将文件中读取出的结构体信息中的名字信息与客户端请求的
		 *结构体中的名字进行对比,判断是否有一致的名字*/
		if(strcmp(user_temp.name, msg->name) == 0){
			/*如果名字一致,则继续判断登陆密码*/
			if(strcmp(user_temp.passwd, msg->passwd) == 0){
				/*满足以上条件,则判断登陆成功,设置对应的标志位*/
				flag = 1;
				msg->sign = SUCCESS;
				msg->info.type = user_temp.type;
				strcpy(msg->data, "all is right");
				return;
			}
		}
	}
	/*如果flag没有变化,说明未找到匹配信息,设置对应的标志位*/
	if(flag == 0){
		msg->sign = FAILED;
		strcpy(msg->data, "find user failed\n");
		return;
	}

	fclose(fp);
	/*操作完成,标志位恢复*/
	User_rmark--;
}

代码分析如下:
上述代码为用户登录时,客户端发送请求之后,服务器执行的核心处理,其核心的操作为第 186 ~ 210 行,即打开存储用户登录信息的文件 user,dat,将存储信息的结构体与从客户端接收的结束信息 MSG 进行成员匹配,一致则判断登录成功,反之失败并返回判断结果。


/***********************************************
 *向保存用户登陆信息的文件中,添加新用户的信息
 *其目的是保证后续该用户可以登陆系统
 ***********************************************/
void AddUser(MSG *msg){
	FILE *fp;
	USER user;
	/*读取客户端发送的结构体MSG,保存的是新用户的信息
	 *并将其复制到结构体USER中,之后写入文件中*/
	strcpy(user.name, msg->info.name);
	strcpy(user.passwd, msg->passwd);

	user.type = STAFF;
	user.no = msg->info.no;

	/********************************************
	 *判断对user.dat文件的读写标志位是否为0
	 *如果大于0,表示当前有线程在操作此文件
	 *******************************************/
	while((User_wmark > 0) && (User_rmark > 0)){
		usleep(100000);
	}

	/*在将新用户信息写入文件之前,操作标志位自加对其他线程执行互斥*/
	User_wmark++;

	/*打开存放用户登陆信息的文件*/
	if((fp = fopen("./user.dat", "ab")) == NULL){
		printf("User %s request: open user.dat failed\n", msg->name);
		msg->sign = FAILED;
		strcpy(msg->data, "no file");
		return;
	}

	/*写入新用户的登陆信息*/
	fwrite(&(user), sizeof(USER), 1, fp);
	printf("add user for %s ok\n", msg->name);

	/*写入成功,发送写入成功标志*/
	msg->sign = SUCCESS;
	strcpy(msg->data, "add user ok\n");

	fclose(fp);
	/*恢复标志位*/
	User_wmark--;
}

代码分析如下:
上述代码为物业管理人员添加一个新的业主是,服务器操作的一部分,此部分实现的功能相当于注册新用户,后续如果该用户进行登录时,则执行 FindUser() 可以找到对应用户的信息,确保用户登录成功。


/*添加用户的详细信息*/
void AddMsg(MSG *msg){
	FILE *fp;

	/**************************************************
	 *判断info.dat文件的读写标志位,如果标志位大于0
	 *表示此时文件正在被其他线程操作,则执行循环等待
	 **************************************************/
	while((Info_wmark > 0) && (Info_rmark > 0)){
		usleep(100000);
	}

	/**************************************************
	 *在将新用户的详细信息写入文件之前,
	 *对info.dat文件的读写标志位进行自加,
	 *对其他线程实现互斥
	 *************************************************/
	Info_wmark++;

	/*打开用于存储用户详细信息的文件*/
	if((fp = fopen("./info.dat", "ab")) == NULL){
		printf("User %s request:open info.dat failed\n", msg->name);
		msg->sign = FAILED;
		strcpy(msg->data, "no file");
		return;
	}

	/*将新用户的详细信息写入到文件中保存*/
	fwrite(&(msg->info), sizeof(INFO), 1, fp);

	/*写入成功,设置对应的标志位,之后发送给客户端*/
	printf("add info for %s ok\n", msg->name);
	msg->sign = SUCCESS;
	strcpy(msg->data, "write info ok\n");

	fclose(fp);
	/*恢复标志位*/
	Info_wmark--;
}

代码分析如下:
上述代码同样为管理人员添加一个新的物主,是服务器操作的一部分,核心代码为 第288 ~ 296行,其操作为将从客户端获取的保存新用户信息的结构体 INFO ,写入文件中。


void DelUser(MSG *msg){
	FILE *fp;
	int i = 0;
	USER user_temp[N];

	/*************************************************
	 *检测文件user.dat文件的写标志位,由于读操作不会
	 *对文件中的内容产生影响,因此不需要关注读标志位
	 *如果标志位大于0,则执行循环等待
	 *************************************************/
	while(User_wmark > 0){
		usleep(100000);
	}
	
	/*对文件user.dat的读标志位进行自加,实现互斥*/
	User_rmark++;

	/*打开存放用户登陆信息的文件*/
	if((fp = fopen("./user.dat", "rb")) == NULL){
		printf("User %s request:open user.dat failed\n", msg->name);
		msg->sign = FAILED;
		strcpy(msg->data, "no file");
		return;
	}

	/*将文件中所有的业主信息全部取出,保存到结构体数组中*/
	while(fread(&(user_temp[i++]), sizeof(USER), 1, fp) != 0){
		;
	}

	fclose(fp);
 	User_rmark--;

	/*判断读写标志位,原理同上*/
	while((User_rmark > 0) && (User_wmark > 0)){
		usleep(100000);
	}

	User_wmark++;

	/*重新打开文件,并清空文件中原有的数据*/
	if((fp = fopen("./user.dat", "wb")) == NULL){
		printf("User %s request:open user.dat failed\n", msg->name);
		msg->sign = FAILED;
		strcpy(msg->data, "no file");
		return;
	}

	/********************************************************
	 *判断需要删除的用户的编号与文件中所有的用户的编号进行对比
	 *由于编号设定时是唯一的,因此将判断符合条件的结构体从数组中
	 *移除,并将移除之后的数组中的结构体依次重新写入文件,即实现删除
	 ********************************************************/
	while(i--){
		if(msg->info.no != user_temp[i].no){
			fwrite(&(user_temp[i]), sizeof(USER), 1, fp);
		}
	}

	/*操作完成后,设置对应的标志位*/
	msg->sign = SUCCESS;

	printf("delete user for %s ok\n", msg->name);
	strcpy(msg->data, "delete user ok\n");

	fclose(fp);
	User_wmark--;
}

代码分析如下:
上述代码为其核心操作为 第325~364行 ,删除用户的第一步,即删除用户的登录信息,之后用户将无法登录系统,实现的方式为将存储用户登录信息的结构体从文件中删除,删除的设计思想为将所有存储用户登录信息的结构体从文件中依次读出,并保存在结构体数组中,然后,再将数组中的结构体重新写入文件时则需要进行判断,使需要删除的结构体跳过写入操作,将其他结构体依次写入,即实现完成删除操作。


/*删除业主的详细信息,其原理与删除登录信息时一致*/
void DelMsg(MSG *msg){
	FILE *fp;
	int i = 0;
	INFO info_temp[N];

	/*实现对文件读写的互斥*/
	while(Info_wmark > 0){
		usleep(100000);
	}

	Info_rmark ++;

	/*打开用于存储用户详细信息的文件*/
	if((fp = fopen("./info.dat", "rb")) == NULL){
		printf("User %s request:open info.dat failed\n", msg->name);
		msg->sign = FAILED;
		strcpy(msg->data, "no file");
		return;
	}

	/*将存储用户详细信息的结构体依次从文件中读出,并保存在结构体数组中*/
	while(fread(&(info_temp[i++]), sizeof(INFO), 1, fp) != 0){
		;
	}

	fclose(fp);
	Info_rmark--;

	while((Info_rmark > 0) && (Info_wmark > 0)){
		usleep(100000);
	}

	Info_wmark++;

	/*重新打开文件*/
	if((fp = fopen("./info.dat", "wb")) == NULL){
		printf("User %s request:open info.dat failed\n", msg->name);
		msg->sign = FAILED;
		strcpy(msg->data, "no file");
		return;
	}

	/*******************************************************
	 *将结构体数组中的保存用户详细信息的结构体重新写入文件
	 *写入时,将符合删除条件的结构体跳过写入操作,实现删除 
	 ******************************************************/
	while(i--){
		if(msg->info.no == info_temp[i].no)
			continue;
		fwrite(&(info_temp[i]), sizeof(INFO), 1, fp);
	}

	printf("delete info for %s ok\n", msg->name);
	msg->sign = SUCCESS;
	strcpy(msg->data, "change info ok\n");

	fclose(fp);
	Info_wmark--;
}

代码分析如下:
上述代码为将业主的信息从系统中删除,其实现的原理与上一段代码一致,核心的代码为第388~426行,完成此初操作的同时,业主信息将彻底从系统中移除,用户无法登录的同时,物业管理也无法查询。


/*实现查询业主信息*/
void FindMsg(MSG *msg){
	INFO info_temp;
	int flag = 0;
	FILE *fp;

	while(Info_wmark >0){
		usleep(100000);
	}

	Info_rmark++;

	/*打开存储业主详细信息的文件*/
	if((fp = fopen("./info.dat", "rb")) == NULL){
		printf("User %s request:no file info.dat\n", msg->name);
		msg->sign = FAILED;
		strcpy(msg->data, "no file");
		return;
	}

	if(strcmp(msg->info.name, "NULL") != 0){
		/*从文件中依次读取描述业主信息的结构体*/
		while(fread(&info_temp, sizeof(INFO), 1, fp) != 0){
			/*判断客户端需要查询的业主姓名与文件中读取的是否一致*/
			if(strcmp(info_temp.name, msg->info.name) == 0){
				/*如果名字相同,则再次判断其编号,避免业主名字相同,查询出错*/
				if((msg->info.no != 0) && (msg->info.no == info_temp.no)){
					/*判断符合条件则将该结构体之后发送给客户端*/
					msg->info = info_temp;
					msg->sign = SUCCESS;
					strcpy(msg->data, "find it2");
					flag = 1;
					return;
				}
				else{
					continue;
				}
			}
		}
		if(flag == 0){
			msg->sign = FAILED;
			strcpy(msg->data, "not find");
			return;
		}
	}

	fclose(fp);
	Info_rmark--;
}

代码分析如下:
上述代码为执行查询请求时,服务端的核心操作。核心的代码为第448~473行,实现的内容为从存储业主详细信息的文件中,将需要读取的业主信息所对应的结构体发送给客户端,由于业主编号在整个管理系统中具有唯一性,因此将其作为判断用户的标记,避免执行读取操作时读到信息重叠的其他用户,导致读取信息不正确的情况。


客户端功能实现

代码如下:

#define N 64
#define READ 1 //查询业主信息
#define CHANGE 2 //修改业主信息
#define DELETE 3 //删除业主信息
#define ADD 4 //添加业主信息
#define LOAD 5 //业主申请登陆
#define QUIT 6 //业主退出时发给服务器通知消息
#define SUCCESS 7 //服务器操作成功
#define FAILED 8 //服务器操作失败
/*用户级别宏*/
#define STAFF 10 //小区业主
#define ADM 11 //物业管理人员

typedef struct{
	int type;//判断是否为物业管理员
	char name[N];//名字
	char passwd[N];//登陆密码
	int no;//业主登记编号
}USER;

typedef struct{
	char name[N];//名字
	char addr[N];//业主小区详细住址
	char time_start[N];//停车费年卡办理时间
	char time_end[N];//年卡有效截止日期
	int location;//车库位置编号
	int no;//身份证号
	int card;//车库年卡费用
	char phone[N];//手机号
	int type;//用户级别
	char car_num[N];//车牌号
}INFO;

typedef struct{
	int sign;//标志符判断操作是否成功
	int type;//判断操作类型
	char name[N];//发送消息的用户
	INFO info;//住户信息结构体
	char passwd[N];//业主密码在第一次登陆使用
	char data[N];//操作失败或成功的消息
}MSG;

/*客户端实现添加新用户信息请求的函数接口*/
int do_adduser(int sockfd, MSG *msg);
/*客户端实现删除用户信息请求的函数接口*/
int do_deluser(int sockfd, MSG *msg);
/*客户端实现修改用户信息请求的函数接口*/
int do_modifyuser(int sockfd, MSG *msg);
/*客户端实现查询用户信息请求的函数接口*/
int do_selectuser(int sockfd, MSG *msg);

代码分析如下:
上述代码为客户端程序需要的头文件。第2 ~ 12行代码为操作的宏定义,用来与服务器进行信息传递时指定请求的类型。服务器根据该定义完成对应的操作,第21 ~ 32行 代码为结构体 INFO,用来保存业主详细信息的结构体。第34 ~ 41行代码,为客户端与服务器进行信息传递的载体,结构体 INFO 被包含于其中,第43 ~ 50行 代码为客户端设置的,关于请求操作的函数接口。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "client.h"

int main(int argc, const char *argv[])
{
	int sockfd;
	struct sockaddr_in serveraddr;

	MSG msg;

	/*创建流式套接字*/
	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
		perror("socket error");
		return -1;
	}

	/*填充网络信息结构体*/
	bzero(&serveraddr, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	serveraddr.sin_port = htons(atoi(argv[2]));

	/*连接服务器*/
	if(connect(sockfd, (struct sockaddr *)&serveraddr,\ 
				sizeof(serveraddr)) < 0){
		perror("connect error");
		return -1;
	}

	/*登录界面,使用循环,保证操作错误时可以再次返回该界面*/
	while(1){
		puts("==============================================");
		puts("+++++++++++++++++++Login++++++++++++++++++++++");
		puts("==============================================");

		/*输入登录信息 名字+密码*/
		printf("Please input your name >");
		fgets(msg.name, N, stdin);
		msg.name[strlen(msg.name) -1] = '\0';

		printf("Please input your password >");
		fgets(msg.passwd, N, stdin);
		msg.passwd[strlen(msg.passwd) - 1] = '\0';
		msg.type = LOAD;

		/*发送消息给服务器,进行登陆验证*/
		send(sockfd, &msg, sizeof(MSG), 0);
		printf("---load type %d\n", msg.type);
		/*接收服务器的反馈消息*/
		recv(sockfd, &msg, sizeof(MSG), 0);
		
		/*根据服务器端返回的标志进行判断*/
		/*登陆成功*/
		if(msg.sign == SUCCESS){
			/*进入业主登陆界面*/
			if(msg.info.type == STAFF){
				goto User;
			}
			/*进入物业管理界面*/
			else if(msg.info.type == ADM){
				goto Admin;
			}
		}
		/*登陆失败*/
		if(msg.sign == FAILED){
			printf("%s\n", msg.data);
			continue;
		}
	}

代码分析如下:
上述代码为客户端与服务器建立网络连接之后实现进入登录界面的,操作登录时根据用户名与登录密码实现登录,将登录请求发送给客户端服务器,判断保存用户登录信息的文件中,是否有匹配用户,并将结果返回给客户端 第57~75行 代码,为客户端对服务器返回的结果的解析选择需要跳转的界面,登录成功后进入物业管理或业主界面,失败时重新返回登录界面。

/*跳转到物业管理界面*/
Admin:
 	while(1){
		/*管理员权限*/
		puts("===========================================================");
		puts("1:add user 2:delete user 3:modify info 4:select info 5:exit");
		puts("===========================================================");
		printf("please input your command > ");//输入对应的操作数字

		/*输入错误命令情况处理*/
		int result;
		int command;
		char clear[N];

		if(scanf("%d", &command) == 0){
			fgets(clear, N, stdin);
			continue;
		}

		switch(command){
			case 1:
				/*添加业主信息*/
				result = do_adduser(sockfd, &msg);
				if(result == SUCCESS){
					puts("注册业主信息成功");
				}
				else if(result == FAILED){
					printf("%s\n", msg.data);
					continue;
				}
				break;
			case 2:
				/*删除业主信息*/
				result = do_deluser(sockfd, &msg);
				if(result == SUCCESS){
					puts("删除业主信息成功");
				}
				else if(result == FAILED){
					printf("%s\n", msg.data);
					puts("删除业主信息失败");
					continue;
				}
				break;
			case 3:
				/*修改业主信息*/
				result = do_modifyuser(sockfd, &msg);
				if(result == SUCCESS){
					puts("修改业主信息成功");
				}
				else if(result == FAILED){
					printf("%s\n", msg.data);
					puts("修改业主信息失败");
					continue;
				}
				break;
			case 4:
				/*查询业主信息*/
				result = do_selectuser(sockfd, &msg);
				if(result == SUCCESS){
					printf("姓名:%s\n", msg.info.name);
					printf("业主详细地址:%s\n", msg.info.addr);
					printf("停车费年卡办理时间:%s\n", msg.info.time_start);
					printf("停车卡有效截止日期:%s\n", msg.info.time_end);
					printf("车位编号:%d\n", msg.info.location);
					printf("业主编号:%d\n", msg.info.no);
					printf("年卡费用:%d\n", msg.info.card);
					printf("手机号:%s\n", msg.info.phone);
					printf("用户类型:%d\n", msg.info.type);
					printf("车牌号:%s\n", msg.info.car_num);
				}
				else if(result == FAILED){
					printf("%s\n", msg.data);
					puts("业主信息不存在");
					continue;
				}
				break;
			case 5:
				msg.type = QUIT;
				send(sockfd, &msg, sizeof(MSG), 0);
				goto Exit;
		}
	}

代码分析如下:
上述代码为跳转到物业管理界面后,选择不同的选项,则执行不同的请求,分支语句保证程序可以跳转到不同的函数,执行不同的请求,也包括新的业主信息,删除业主信息,查询业主信息,修改业主信息以及系统退出。

/*跳转到业主界面*/
User:
	while(1){
		/*普通业主权限*/
		puts("==============================================");
		puts("+++++1:select info 2:modify passwd 3:exit+++++");
		puts("==============================================");
		printf("please input your command > ");

		/*处理输入错误命令的情况*/
		int command;
		char clear[N];
		/****************************************************
		 * 如果终端输入的内容未被成功读取,则返回值为零;
		 * 说明本次输入的选择,不合规则,无法读取;
		 * continue跳过本次循环,重新让业主选择
		 ***************************************************/
		if(scanf("%d", &command) == 0){
			fgets(clear, N, stdin);
			continue;
		}
		
		switch(command){
			case 1:
				msg.type = READ;
				strcpy(msg.info.name, msg.name);
				printf("请输入编号 > ");
				input_no:
				if(scanf("%d", &(msg.info.no)) == 0){
					printf("input type error, exp 1001\n");
					fgets(clear, N, stdin);
					goto input_no;
				}
				/*发送查询消息*/
				send(sockfd, &msg, sizeof(MSG), 0);
				/*接收服务器的反馈消息*/
				recv(sockfd, &msg, sizeof(MSG), 0);
				/*打印用户自身消息*/
				printf("姓名:%s\n", msg.info.name);
				printf("业主详细地址:%s\n", msg.info.addr);
				printf("办停车卡时间:%s\n", msg.info.time_start);
				printf("停车卡有效期:%s\n", msg.info.time_end);
				printf("车位编号:%d\n", msg.info.location);
				printf("业主编号:%d\n", msg.info.no);
				printf("年卡费用:%d\n", msg.info.card);
				printf("手机号:%s\n", msg.info.phone);
				printf("用户类型:%d\n", msg.info.type);
				printf("车牌号:%s\n", msg.info.car_num);
				break;
			case 2:
				/*向服务器端确认需要修改密码的用户的编号以及名字*/
				strcpy(msg.info.name, msg.name);
				getchar();
				printf("请输入业主编号 > ");
				
				input_no1:
				if(scanf("%d", &(msg.info.no)) == 0){
					printf("input type error, exp 1001\n");
					fgets(clear, N, stdin);
					goto input_no1;
				}
				getchar();
				printf("请输入新的登录密码 > ");
				fgets(msg.passwd, N, stdin);
				msg.passwd[strlen(msg.passwd) - 1] = '\0';
				
				msg.type = CHANGE;
				send(sockfd, &msg, sizeof(MSG), 0);
				break;
			case 3:
				msg.type = QUIT;
				send(sockfd, &msg, sizeof(MSG), 0);
				goto Exit;
		}
	}
Exit:
	close(sockfd);
	return 0;
}

代码分析如下:
上述代码为跳转到业主操作界面,选择不同的选项,则执行不同的请求代码, 第183~234行为 通过分支语句跳转到不同的操作,实现不同功能,包括查询业主的信息,修改业主的登录密码以及退出系统。

/*添加业主信息*/
int do_adduser(int sockfd, MSG *msg){
	printf("请输入业主的姓名 > ");
	getchar();
	fgets((msg->info).name, N, stdin);
	(msg->info).name[strlen((msg->info).name) - 1] = '\0';

	printf("请输入业主的详细地址 > ");
	fgets((msg->info).addr, N, stdin);
	(msg->info).addr[strlen((msg->info).addr) - 1] = '\0';

	printf("请输入业主的手机号码 > ");
	fgets((msg->info).phone, N, stdin);
	(msg->info).phone[strlen((msg->info).phone) - 1] = '\0';

	printf("请输入业主的车牌号 > ");
	fgets((msg->info).car_num, N, stdin);
	(msg->info).car_num[strlen((msg->info).car_num) - 1] = '\0';

	printf("请输入业主办理停车位起始日期 > ");
	fgets((msg->info).time_start, N, stdin);
	(msg->info).time_start[strlen((msg->info).time_start) - 1] = '\0';

	printf("请输入业主停车位使用截止日期 > ");
	fgets((msg->info).time_end, N, stdin);
	(msg->info).time_end[strlen((msg->info).time_end) - 1] = '\0'; 
	
	char clear[N];

input_location:
	printf("请输入业主停车位编号 > ");
	if(scanf("%d", &(msg->info.location)) == 0){
		printf("input type error, exp 1001\n");
		fgets(clear, N, stdin);
		goto input_location;
	}
	getchar();
input_no:
	printf("请输入业主编号 > ");
	if(scanf("%d", &(msg->info.no)) == 0){
		printf("input type error, exp 1001\n");
		fgets(clear, N, stdin);
		goto input_no;
	}
	getchar();
input_card:
	printf("请输入停车位年卡费用 > ");
	if(scanf("%d", &(msg->info.card)) == 0){
		printf("input type error, exp 1200\n");
		fgets(clear, N, stdin);
		goto input_card;
	}
	getchar();
input_type:
	printf("请输入业主类型 > ");
	if(scanf("%d", &(msg->info.type)) == 0){
		printf("input type error\n");
		printf("类型选择:1.物业管理 2.小区常住业主 3.小区租户\n");
		fgets(clear, N, stdin);
		goto input_card;
	}
	getchar();

	printf("请输入业主系统登陆密码 > ");
	fgets(msg->passwd, N, stdin);
	msg->passwd[strlen(msg->passwd) - 1] = '\0'; 	
	
	/*发送操作类型给服务器*/
	msg->type = ADD;

	/*发送给服务器的结构体类型,必须和客户端一致*/
	send(sockfd, msg, sizeof(MSG), 0);
	recv(sockfd, msg, sizeof(MSG), 0);

	return msg->sign;//返回服务器端的处理信息
}

代码分析如下:
上述代码为添加新业主信息时,需要执行的操作请求,其核心为对其描述业主信息的结构体进行初始化操作。部分信息输入采用了类似递归函数的方式,实现输入有误时可以重新输入,最后将填充了新业主信息的结构体发送给服务器,并接收服务器的反馈判断是否添加成功。

/*删除业主信息*/
int do_deluser(int sockfd, MSG *msg){
	printf("请输入删除业主的姓名 > ");
	getchar();
	fgets((msg->info).name, N, stdin);
	(msg->info).name[strlen((msg->info).name) - 1] = '\0';

	printf("请输入删除业主的编号 > ");
	if(scanf("%d", &(msg->info.no)) == 0){
		msg->info.no = 0;
	}

	msg->type = DELETE;

	send(sockfd, msg, sizeof(MSG), 0);
	recv(sockfd, msg, sizeof(MSG), 0);

	return msg->sign;//返回服务器端的处理信息
}

代码分析如下:
上述代码为物业管理请求删除业主信息时,跳转的核心代码。这里采用名字与编号组合的方式来确定某一个业主,发送请求的类型为删除请求。服务器接收此结构体信息,并执行对比,将文件中匹配的业主信息删除删除的操作是通过文件先后写的操作实现。第332 ~ 334 行为客户端得到服务器的反馈,返回处理的结果。

/*修改业主信息*/
int do_modifyuser(int sockfd, MSG *msg){
	char clear[N];
	
	printf("请输入被修改业主的姓名 > ");
	getchar();
	fgets((msg->info).name, N, stdin);
	(msg->info).name[strlen((msg->info).name) - 1] = '\0';

input_no:
	printf("请输入被修改业主编号 > ");
	if(scanf("%d", &(msg->info.no)) == 0){
		printf("input type error, exp 1001\n");
		fgets(clear, N, stdin);
		goto input_no;
	}
	getchar();

	printf("请输入业主新的详细地址 > ");
	fgets((msg->info).addr, N, stdin);
	(msg->info).addr[strlen((msg->info).addr) - 1] = '\0';

	printf("请输入业主新的手机号码 > ");
	fgets((msg->info).phone, N, stdin);
	(msg->info).phone[strlen((msg->info).phone) - 1] = '\0';

	printf("请输入业主新的车牌号 > ");
	fgets((msg->info).car_num, N, stdin);
	(msg->info).car_num[strlen((msg->info).car_num) - 1] = '\0';

	printf("请输入业主新办理停车位起始日期 > ");
	fgets((msg->info).time_start, N, stdin);
	(msg->info).time_start[strlen((msg->info).time_start) - 1] = '\0';

	printf("请输入业主新停车位使用截止日期 > ");
	fgets((msg->info).time_end, N, stdin);
	(msg->info).time_end[strlen((msg->info).time_end) - 1] = '\0'; 
	

input_location:
	printf("请输入业主新停车位编号 > ");
	if(scanf("%d", &(msg->info.location)) == 0){
		printf("input type error, exp 1001\n");
		fgets(clear, N, stdin);
		goto input_location;
	}
	getchar();

input_card:
	printf("请输入停车位年卡费用 > ");
	if(scanf("%d", &(msg->info.card)) == 0){
		printf("input type error, exp 1200\n");
		fgets(clear, N, stdin);
		goto input_card;
	}
	getchar();
input_type:
	printf("请输入业主类型 > ");
	if(scanf("%d", &(msg->info.type)) == 0){
		printf("input type error, exp 2\n");
		fgets(clear, N, stdin);
		goto input_card;
	}
	getchar();

	printf("请输入业主系统登陆密码 > ");
	fgets(msg->passwd, N, stdin);
	msg->passwd[strlen(msg->passwd) - 1] = '\0'; 	
	
	/*发送操作类型给服务器*/
	msg->type = CHANGE;

	/*发送给服务器的结构体类型,必须和客户端一致*/
	send(sockfd, msg, sizeof(MSG), 0);
	recv(sockfd, msg, sizeof(MSG), 0);

	return msg->sign;//返回服务器端的处理信息
}

代码分析如下:
上述代码为对业主信息更新时,客户端执行请求的操作代码,本次选择更新操作为更新全部信息,降低工作难度,名字与业主编号不在修改范围内,本程序不建议修改编号与名字,否则会出现错误,其他则全部可以选择修改修,改的方式为将原有信息全部覆盖。

/*查询业主信息*/
int do_selectuser(int sockfd, MSG *msg){
	printf("请输入业主的姓名 > ");
	getchar();

	fgets((msg->info).name, N, stdin);
	(msg->info).name[strlen((msg->info).name) - 1] = '\0';

	/*当输入其他字符时,默认要查询的no值为0*/
	printf("请输入业主编号 > ");
	if(scanf("%d", &(msg->info.no)) == 0){
		msg->info.no = 0;
	}
	msg->type = READ;
	send(sockfd, msg, sizeof(MSG), 0);
	recv(sockfd, msg, sizeof(MSG), 0);

	return msg->sign;
}

代码分析如下:
上述代码为物业管理查询业主信息时,客户端执行的请求将需要查询的业主姓名与编号,封装到结构体 MSG,并发送给服务器处理。

系统展示

这里就不详细讲了,这里只给出一个简单的图例:
小区物业停车管理系统(实现多用户登录)_第4张图片
其中最左边为物业管理员,中间为服务器的终端反馈,最后边为普通业主的执行操作。

你可能感兴趣的:(Linux,linux,c语言)