Bootstrap

Linux应用开发综合实战:在线词典

开发环境、工具
Linux Ubuntu 14.04、sqlite3、gcc、gdb、C 语言
实现功能
普通用户的注册,服务器将用户信息存入数据库中。
普通用户和管理员(root)用户的登录验证。
客户端传入要查询的单词,服务器端查询字典文件匹配后返回结果,并记录查询的信息和时间。
客户端可查看自己查询的记录,管理员用户可查看所有用户的查询记录。
项目描述
1、本项目为华清的Linux应用开发综合实战。
2、采用 C/S 架构,基于 TCP/IP 协议,采用多进程实现服务器与多个客户端之间的通信
3、使用结构体 struct 作为信息传递的途径
4、使用标准IO读取词典文件
5、采用轻量级数据库 sqlite3 在服务器记录用户信息和查询记录
功能解析
在这里插入图片描述

流程框架
客户端:
在这里插入图片描述
服务器:
在这里插入图片描述
具体实现
客户端:

/*./client 192.168.10.8 5004*/

#include "net.h"

#define N 32

#define R 1  //user - register
#define L 2  //user - login
#define Q 3  //user - query
#define H 4  //user - history

//定义通信双方的信息结构体
typedef struct {
	int type;
	char name[N];
	char data[256];
	int flags;  //0-普通用户 1-root用户
}MSG;

void usage (char * s);                 //命令行参数格式输入错误时提示
int do_register(int sockfd, MSG *msg);//注册
int do_login(int sockfd, MSG *msg);    //登录
int do_query(int sockfd,MSG *msg);    //查找单词
int do_history(int sockfd, MSG *msg); //查看历史记录

int main(int argc, char *argv[])
{
	MSG msg;

	//1.判断命令行参数是否输入正确
	int port;
	port = atoi(argv[2]); //字符串转换成整型

	if (argc != 3) {
		usage(argv[0]);
		exit(1);
	}

	if (port < 5000) {
		usage(argv[0]);
		exit(1);
	}

	//2.创建流式套接字
	int sockfd;
	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		perror("socket");
		exit(1);
	}

	//3.申请服务器连接请求
	//3.1填充服务器(结构体)信息
	struct sockaddr_in sin;

	bzero(&sin, sizeof(sin));
	sin.sin_family = AF_INET;    //地址族,这里是ipv4
	sin.sin_port = htons(port);  //服务器端口(转换成网络字节序)
	sin.sin_addr.s_addr = inet_addr(argv[1]);  //服务器ip地址(点分式ip转换成32位网络字节序ip)

	//3.2连接服务器
	if (connect(sockfd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
		perror("connect");
		exit(1);
	}

	int n;

	while (1) {
		printf("\t***********************在线词典*************************\n");
		printf("\t********************************************************\n");
		printf("\t**1:注册              2:用户登录                3:退出**\n");
		printf("\t********************************************************\n");
		
		printf("Please enter your choice:");
		scanf("%d", &n);
		getchar();

		switch (n) {
			case 1:
				do_register(sockfd, &msg);
				break;
			case 2:
				if (do_login(sockfd, &msg) == 1) {
					goto next;
				break;
				}
				case 3:
				printf("已退出程序!\n");
				close(sockfd);
				exit(0);
				break;
			default:
				printf("Invalid data cmd!");
		}
	}

next:
	while (1) {
		printf("\t************************在线词典************************\n");
		if (msg.flags == 1) {
		printf("\t********************欢迎root用户登录********************\n");
		} 
		printf("\t********************************************************\n");
		printf("\t**1:查找单词             2:历史记录             3:退出**\n");
		printf("\t********************************************************\n");
		
		printf("Please enter your choice:");
		scanf("%d", &n);
		getchar();

		switch (n) {
			case 1:
				do_query(sockfd, &msg);
				break;
			case 2:
				do_history(sockfd, &msg);
				break;
			case 3:
				printf("已退出程序!\n");
				close(sockfd);
				exit(0);
				break;
			default:
				printf("Invalid data cmd!\n");
		}
	}

	return 0;
}


//命令行参数格式输入错误时提示
void usage (char * s) {
	printf("Please enter the correct format!\n");
	printf("\nUsage:\n\t %s server_ip server_port", s);
	printf("\n\t server_ip: tcp server ip address");
	printf("\n\t server_port: tcp server port(server_port > 5000)\n\n");
}

//注册
int do_register(int sockfd, MSG *msg) {
	msg->type = R;
	printf("请输入用户名:");
	scanf("%s", msg->name);
	getchar();

	printf("请输入密码:");
	scanf("%s", msg->data);

	//将注册信息发送给服务器
	if (send(sockfd, msg, sizeof(MSG), 0) < 0) {
		perror("send");
		return -1;
	}

	//服务器返回一个回应(ok or usr already exist)
	if (recv(sockfd, msg, sizeof(MSG), 0) < 0) {
		perror("recv");
		return -1;
	}

	printf("%s\n", msg->data);

}

//登录
int do_login(int sockfd, MSG *msg) {
	msg->type = L;
	printf("请输入用户名:");
	scanf("%s", msg->name);
	getchar();

	printf("请输入密码:");
	scanf("%s", msg->data);

	//将登录信息发送给服务器
	if (send(sockfd, msg, sizeof(MSG), 0) < 0) {
		perror("send");
		return -1;
	}

	//服务器返回一个回应(ok or error)
	if (recv(sockfd, msg, sizeof(MSG), 0) < 0) {
		perror("recv");
		return -1;
	}

	if (strncmp(msg->data, "成功!", 3) == 0) {	
		printf("登录成功!\n");
		return 1;
	} 
	else {
		printf("%s\n", msg->data);
	}

	return 0;
}

//查找单词
int do_query(int sockfd, MSG *msg) {
	msg->type = Q;

	while (1) {
		puts("--------------------------------------\n");
		printf("请输入单词:");
		scanf("%s", msg->data);
		getchar();

		//若客户端输入'#',则返回上级目录
		if (strncmp(msg->data, "#", 1) == 0) 
			break;

		//将要查的单词信息发送给服务器
		if (send(sockfd, msg, sizeof(MSG), 0) < 0) {
			perror("send");
			return -1;
		}

		//服务器返回单词的注释信息
		if (recv(sockfd, msg, sizeof(MSG), 0) < 0) {
			perror("recv");
			return -1;
		}

		printf("%s\n", msg->data);
	}

	return 0;
}

//查看历史记录
int do_history(int sockfd, MSG *msg) {
	msg->type = H;

	send(sockfd, msg, sizeof(MSG), 0);
	puts("---------------------------------------");
	while (1) {
		recv(sockfd, msg, sizeof(MSG), 0);
	
		if(msg->data[0] == '\0')
			break;

		printf("%s\n", msg->data);

	}
	puts("----------------------------------------");
	return 0;
}

服务器端

#include "net.h"

#define N 32

#define  R  1   // user - register
#define  L  2   // user - login
#define  Q  3   // user - query
#define  H  4   // user - history

#define  DATABASE  "my.db"

// 定义通信双方的信息结构体
typedef struct {
	int type;
	char name[N];
	char data[256];
	int flags; // 0-普通用户 1-root用户
}MSG;

int do_client(int acceptfd, sqlite3 *db);
void do_register(int acceptfd, MSG *msg, sqlite3 *db);
int do_login(int acceptfd, MSG *msg, sqlite3 *db);
int do_query(int acceptfd, MSG *msg, sqlite3 *db);
int do_history(int acceptfd, MSG *msg, sqlite3 *db);
int history_callback(void* arg,int f_num,char** f_value,char** f_name);
int do_searchword(int acceptfd, MSG *msg, char word[]);
int get_date(char *date);

int main(int argc, char *argv[])
{
	//1.打开数据库
	sqlite3 *db;
	if (sqlite3_open(DATABASE, &db) != SQLITE_OK) {
		printf("%s\n", sqlite3_errmsg(db));
		exit(1);
	} else {
		printf("open DATABASE success!\n");
	}

	//2.创建流式套接字
	int sockfd;

	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
		perror("socket");
		exit(1);
	}

	int b_reuse = 1;
	setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int));

	//3.绑定本地服务器和端口
	//3.1填充结构体信息
	struct sockaddr_in sin;

	bzero(&sin, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(SERV_PORT);
	sin.sin_addr.s_addr = htonl(INADDR_ANY);
	//3.2绑定
	if (bind(sockfd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
		perror("bind");
		exit(1);
	}

	//4.将套接字设置为监听模式
	if (listen(sockfd, 5) < 0) {
		perror("listen");
		exit(1);
	}

	//5.处理僵尸进程
	signal(SIGCHLD, SIG_IGN);

	int acceptfd;
	pid_t pid;

	//6.等待连接请求,处理数据(多进程并发)
	while (1) {
		if ((acceptfd = accept(sockfd, NULL, NULL)) < 0) {
			perror("accept");
			exit(1);
		}

		if ((pid = fork()) < 0) {
			perror("fork");
			exit(1);
		} 
		else if (pid == 0) { //子进程处理客户端具体的消息
			close(sockfd);
			do_client(acceptfd, db);
		}
		else {  //父进程用来接收客户端的请求
			close(acceptfd);
		}

	}

	return 0;
}

//子进程处理函数
int do_client(int acceptfd, sqlite3 *db) {
	MSG msg;
	while (recv(acceptfd, &msg, sizeof(msg), 0) > 0) {
		printf("type:%d\n", msg.type);
		switch (msg.type) {
		case R:
			do_register(acceptfd, &msg, db);
			break;
		case L:
			do_login(acceptfd, &msg, db);
			break;
		case Q:
			do_query(acceptfd, &msg, db);
			break;
		case H:
			do_history(acceptfd, &msg, db);
			break;
		default:
			printf("Invalid data msg\n");
		}

	}
	printf("客户端已退出\n");
	close(acceptfd);
	exit(0);
}

//注册
void do_register(int acceptfd, MSG *msg, sqlite3 *db) {
	char *errmsg;
	char sql[128];

	sprintf(sql, "insert into usr values('%s', '%s');", msg->name, msg->data);
	printf("%s\n", sql);

	if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK) {
		printf("%s\n", errmsg);
		strcpy(msg->data, "用户名已存在!");
	} 
	else {
		printf("client register ok!\n");
		strcpy(msg->data, "成功!");
	}

	if (send(acceptfd, msg, sizeof(MSG), 0) < 0) {
		perror("send");
		return ;
	}
	return ;
}

//登录
int do_login(int acceptfd, MSG *msg, sqlite3 *db) {
	char *errmsg;
	char sql[128];
	char **resultp;
	int nrow;
	int ncloumn;

	sprintf(sql, "select * from usr where name = '%s' and pass = '%s';", msg->name, msg->data);
	printf("%s\n", sql);

	if (sqlite3_get_table(db, sql, &resultp, &nrow, &ncloumn, &errmsg) != SQLITE_OK) {
		printf("%s\n", errmsg);
		return -1;
	}
	else {
		printf("get_table ok!\n");
	}

	//查询成功,数据库拥有此用户(用查找结果的行数做判断)
	if (nrow == 1) {
		if (strncmp(msg->name, "root", 4) == 0) {
			msg->flags = 1;  //表示此用户为root用户
		}

		strcpy(msg->data, "成功!");
		send(acceptfd, msg, sizeof(MSG), 0);
		return 1;
	} //没有查到
	else if (nrow == 0) {
		strcpy(msg->data, "密码或用户名错误!");
		send(acceptfd, msg, sizeof(MSG), 0);
	}

	return 0;
}

//查询
int do_query(int acceptfd, MSG *msg, sqlite3 *db) {
	char word[64] = {};
	int found = -1;
	char date[128] = {};
	char sql[128] = {};
	char *errmsg;

	strcpy(word, msg->data);
	found = do_searchword(acceptfd, msg, word);

	if (found == 1) { //找到了
		//得到系统时间
		get_date(date);
		//把查询历史记录下来
		sprintf(sql, "insert into record values('%s','%s','%s');", msg->name, date, word);

		if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK) {
			printf("%s\n", errmsg);
			return -1;
		} else {
			printf("Insert record done.\n");
		}
	}
	else {  //没找到
		strcpy(msg->data, "Not found!");
	}

	send(acceptfd, msg, sizeof(MSG), 0);
	return 0;
}

//查看历史记录
int do_history(int acceptfd, MSG *msg, sqlite3 *db) {
	char sql[128] = {};
	char *errmsg;

	if (msg->flags == 1) {
		sprintf(sql, "select * from record;");
		printf("%s\n", sql);
	} else {
		sprintf(sql, "select * from record where name = '%s';", msg->name);
		printf("%s\n", sql);
	}


	//查询数据库
	if (sqlite3_exec(db, sql, history_callback, (void *)&acceptfd, &errmsg) != SQLITE_OK) {
		printf("%s\n", errmsg);
	} else {
		printf("Query record done.\n");
	}

	//所有记录查询完毕后,给客户端发出一个结束信息
	msg->data[0] = '\0';

	send(acceptfd, msg, sizeof(MSG), 0);

	return 0;
}

//得到查询结果,并将历史记录发给客户端
int history_callback(void* arg,int f_num,char** f_value,char** f_name) {
	// record , name , data , word
	int acceptfd;
	MSG msg;

	acceptfd = *((int *)arg);

	sprintf(msg.data, "%s ,%s, %s", f_value[0],f_value[1], f_value[2]);
	printf("%s\n", msg.data);

	send(acceptfd, &msg, sizeof(MSG), 0);

	return 0;
}



//查找单词
int do_searchword(int acceptfd, MSG *msg, char word[]) {
	FILE *fp;
	int len = 0;
	char temp[512] = {};
	int result;
	char *p;

	//打开文件
	if ((fp = fopen("dict.txt", "r")) == NULL) {
		perror("fopen");
		strcpy(msg->data, "Fail to open dict.txt");
		send(acceptfd, msg, sizeof(MSG), 0);
		return -1;
	}

	//打印客户查询的单词
	len = strlen(word);
	printf("%s,len=%d\n", word, len);

	//一行一行读取文件来查询单词
	while (fgets(temp, 512, fp) != NULL) {
		result = strncmp(temp, word, len);
		if (result < 0) 
			continue;
		if (result > 0 || ((result == 0) && (temp[len] != ' ')))
			break;

		//找到了查询的单词
		//越过空格去找到注释的位置
		p = temp + len; //abandon   v.akdsf dafsjkj
		while (*p == ' ') {
			p++;
		}

		strcpy(msg->data, p);
		printf("found word:%s\n", msg->data);
		//注释拷贝完毕后,应关闭文件
		fclose(fp);
		return 1;
	}
	fclose(fp);
	return 0;
}

//获取系统时间
int get_date(char *date) {
	time_t t;
	struct tm *tp;

	time(&t);
	//转换时间格式
	tp = localtime(&t);

	sprintf(date, "%d-%d-%d %d:%d:%d", tp->tm_year+1900, tp->tm_mon+1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec);
	printf("%s\n", date);

	return 0;
}

头文件

#ifndef _NET_H_
#define _NET_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sqlite3.h>
#include <signal.h>
#include <time.h>

#define SERV_PORT 5004
#define SERV_IP 192.168.10.8

#endif

运行截图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

;