2023-1-7作业

项目要求:

  1. 登录注册功能,不能重复登录,重复注册
  2. 单词查询功能
  3. 历史记录功能,存储单词,意思,以及查询时间
  4. 基于TCP,支持多客户端连接
  5. 采用数据库保存用户信息与历史记录
  6. 将dict.txt的数据导入到数据库中保存。
  7. 按下ctrl+c退出客户端后,注销该客户端的登录信息

格式要求:

  1. main函数只跑逻辑,不允许跑功能代码
  2. 功能代码封装成函数

 服务器代码:

dictser.h:

#ifndef __DICTSER_H__
#define __DICTSER_H__

struct sockaddr_in cin;
typedef void (*sighandler_t)(int);

//协议
struct cli_msg        
{                     
    char type;        
    char username[50];
    char password[50];
	char word[50];
	char mean[50];
};    

//创建并打开一个数据库
sqlite3* SQL_create();
//创建一个表格作为词典
void dictionary_create(sqlite3*db);
//创建一个表格存储注册信息
void register_message_create(sqlite3*db);
//创建一个表格存储登录信息
void login_message_create(sqlite3*db);
//导入词典数据
void dictionary_insert(sqlite3*db);
//导入注册信息
int register_message_insert(sqlite3*db, char*username, char*password);
//导入登入信息
void login_message_insert(int newfd, char*username, sqlite3*db);
//查询注册信息,判断用户、密码是否已注册
int register_select(char*username, char*password, sqlite3*db);
//查询登录信息,判断用户名是否已登录
int login_username_select(char*username, sqlite3*db);
//删除登录信息
int login_message_delete(int newfd, sqlite3*db);
//修改用户密码
void password_update(sqlite3*db);
//查询词典2,并发送信息
void dictionary_select2(int newfd, struct cli_msg* msg, sqlite3*db);
//关闭数据库连接
void SQL_close(sqlite3*db);

//回收僵尸进程
void Recycling_zombie_processes();
//建立TCP通信
int TCP_communication();
//处理客户端信息
void deal_cil_msg(int newfd, struct sockaddr_in cin, sqlite3*db);
//处理客户端注册信息
void deal_cil_msg1(int newfd, struct sockaddr_in cin, sqlite3*db, struct cli_msg msg);
//处理客户端登录信息
void deal_cil_msg2(int newfd, struct sockaddr_in cin, sqlite3*db, struct cli_msg msg);
//处理客户端查询请求
void deal_cil_msg3(int newfd, struct sockaddr_in cin, sqlite3*db, struct cli_msg msg);
//处理客户端退出登录请求
void deal_cil_msg4(int newfd, struct sockaddr_in cin, sqlite3*db, struct cli_msg msg);
//向客户端发送信息
void send_to_cli(int newfd, struct sockaddr_in cin, int flag);
//向客户端发送信息2
void send_to_cli2(int newfd, struct sockaddr_in cin, int flag);

#endif

dictser.c:

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

#define IP "192.168.8.77"
#define PORT 6666

#define ERR_MSG(msg) do{\
	fprintf(stderr,"line:%d\n", __LINE__);\
	perror(msg);\
}while(0)

//创建并打开一个数据库
sqlite3* SQL_create()
{
	printf("初始化词典数据库中...\n");
	sqlite3 *db = NULL;
	if(sqlite3_open("./mysq.db", &db) != SQLITE_OK)
	{
		fprintf(stderr, "sqlite3_open failed:%d %s\n",\
				sqlite3_errcode(db), sqlite3_errmsg(db));
		return NULL;
	}
	//printf("数据库创建成功\n");
	
	//创建一个表格作为词典
	dictionary_create(db);

	//导入词典数据
	printf("导入词典数据中...\n");
	dictionary_insert(db);

	//创建一个表格存储注册信息
	register_message_create(db);

	//创建一个表格存储登录信息
	login_message_create(db);

	printf("词典数据库初始化完毕\n");
	return db;
}

//创建一个表格作为词典
void dictionary_create(sqlite3*db)
{
	char table_name[] = "dictionary";
	char sql[128] = "";
	sprintf(sql, "create table if not exists %s (word char, mean char);", table_name);
	char *errmsg = NULL;
	if(sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)
	{
		fprintf(stderr, "line:%d sqlite3_exec failed: %s\n", __LINE__, errmsg);
		return ;
	}
	//printf("%s create success\n", table_name);
	
}

//创建一个表格存储注册信息
void register_message_create(sqlite3*db)
{
	char table_name[] = "register";
	char sql[128] = "";
	sprintf(sql, "create table if not exists %s (username char primary key, password char);", table_name);
	char *errmsg = NULL;
	if(sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)
	{
		fprintf(stderr, "line:%d sqlite3_exec failed: %s\n", __LINE__, errmsg);
		return ;
	}
	//printf("%s create success\n", table_name);
}

//创建一个表格存储登录信息
void login_message_create(sqlite3*db)
{
	char table_name[] = "login";
	char sql[128] = "";
	sprintf(sql, "create table if not exists %s (newfd int, username char primary key);", table_name);
	char *errmsg = NULL;
	if(sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)
	{
		fprintf(stderr, "line:%d sqlite3_exec failed: %s\n", __LINE__, errmsg);
		return ;
	}
	//printf("%s create success\n", table_name);
}

//导入词典数据
void dictionary_insert(sqlite3*db)
{
	char buf[128] = "";
	char word[100] = "";
	char mean[200] = "";
	char sql[128] = "";
	char *errmsg = NULL;
	FILE *fp;
	int res = 0; 

	//打开"./dict.txt"文件,以读的方式
	fp = fopen("./dict.txt", "r");
	if(NULL == fp)
	{
		perror("fopen");
		return ;
	}
	
	//循环读取文件,一次读取一行,直到文件读取完毕后跳出循环
	while(fgets(buf, sizeof(buf), fp) != NULL)
	{
		buf[strlen(buf)-1] = 0;

		for(int i = 0; buf[i+2]!='\0'; i++)
		{
			if(buf[i]!=' ' && buf[i+1]==' ' && buf[i+2]==' ')   //读取单词
			{
				strncpy(word, buf, i+1);
			}
			else if(buf[i]==' ' && buf[i+1]==' ' && buf[i+2]!=' ')    //读取意思
			{
				strcpy(mean, buf+i+2);
				break;
			}
		}

		//拼接SQL命令
		sprintf(sql, "insert into dictionary values (\"%s\", \"%s\");", word, mean);

		//运行SQL命令
		if(sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)
		{
			fprintf(stderr, "line:%d sqlite3_exec failed: %s\n", __LINE__, errmsg);
			return ;
		}

		bzero(buf, sizeof(buf));
		bzero(word, sizeof(word));
		bzero(mean, sizeof(mean));
	}
	printf("词典数据导入成功\n");

	//关闭文件
	fclose(fp);
}

//导入注册信息
int register_message_insert(sqlite3*db, char*username, char*password)
{
	char sql[254] = "";
	char *errmsg = NULL;

	//拼接SQL命令
	sprintf(sql, "insert into register values (\"%s\", \"%s\");", username, password);

	//运行SQL命令
	if(sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)
	{
		fprintf(stderr, "line:%d sqlite3_exec failed: %s\n", __LINE__, errmsg);
		return -1;
	}
	return 1;
}

//导入登入信息
void login_message_insert(int newfd, char*username, sqlite3*db)
{
	char sql[128] = "";
	char *errmsg = NULL;

	//拼接SQL命令
	sprintf(sql, "insert into login values (\"%d\",\"%s\");", newfd, username);

	//运行SQL命令
	if(sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)
	{
		fprintf(stderr, "line:%d sqlite3_exec failed: %s\n", __LINE__, errmsg);
		return ;
	}
}

//查询登录信息,判断用户名是否已登录
int login_username_select(char*username, sqlite3*db)
{
	char sql[128] = "select * from login";
	char **pres = NULL;
	int row, colum;
	char *errmsg = NULL;
	int flag = 0;

	if(sqlite3_get_table(db, sql, &pres, &row, &colum, &errmsg) != SQLITE_OK)
	{
		fprintf(stderr, "line:%d sqlite3_exec failed: %s\n", __LINE__, errmsg);
		return -1;
	}
	//printf("row=%d colum=%d\n", row, colum);  //共有几行,几列

	if(row != 0)
	{
		for(int i = 0; i < (row+1)*colum; i++)
		{
			i+=1;
			if(strcmp(pres[i], username) == 0)
			{
				flag = 1;
				break;
			}
		}
	}
	/*
	if(1 == flag)
		printf("用户已在线\n");	
	else if(0 == flag)
		printf("用户未在线\n");*/

	//释放内存空间
	sqlite3_free_table(pres);
	return flag;
}

//查询注册信息,判断用户、密码是否已注册
int register_select(char*username, char*password, sqlite3*db)
{
	char sql[128] = "select * from register";
	char **pres = NULL;
	int row, colum;
	char *errmsg = NULL;
	int flag = 0;

	if(sqlite3_get_table(db, sql, &pres, &row, &colum, &errmsg) != SQLITE_OK)
	{
		fprintf(stderr, "line:%d sqlite3_exec failed: %s\n", __LINE__, errmsg);
		return -1;
	}
	//printf("row=%d colum=%d\n", row, colum);  //共有几行,几列

	int k = 0;
	for(int i = 0; i < row+1; i++)
	{
		for(int j = 0; j < colum; j++)
		{
			if(strcmp(pres[k], username) != 0)
			{
				k+=2;
				break;
			}
			flag = 1;
			if(strcmp(pres[++k], password) == 0)
			{
				flag = 2;
				break;
			}
		}
	}
	/*
	if(2 == flag)
		printf("密码正确\n");	
	else if(1 == flag)
		printf("密码错误\n");
	else if(0 == flag)
		printf("用户名不存在\n");*/

	//释放内存空间
	sqlite3_free_table(pres);
	return flag;
}

//删除登录信息
int login_message_delete(int newfd, sqlite3*db)
{
	char sql[128] = "";
	char *errmsg = NULL;

	//拼接SQL命令
	sprintf(sql, "delete from login where newfd=%d;", newfd);

	//运行SQL命令
	if(sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)
	{
		fprintf(stderr, "line:%d sqlite3_exec failed: %s\n", __LINE__, errmsg);
		return -1;
	}
	//printf("delete values success\n");
	return 1;
}

//修改用户密码
void password_update(sqlite3*db)
{
	char username[50] = "";
	char password[50] = "";
	char update_password[50] = "";
	char sql[128] = "";
	char *errmsg = NULL;

	while(1)
	{
		printf("请输入username>>>");
		scanf("%s", username);
		while(getchar() != 10);

		printf("请输入password>>>");
		scanf("%s", password);
		while(getchar() != 10);
	}

	printf("修改成>>>");
	scanf("%s", update_password);
	while(getchar() != 10);

	//拼接SQL命令
	sprintf(sql, "update register set password=\"%s\" where username=\"%s\";", update_password, username);

	//运行SQL命令
	if(sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)
	{
		fprintf(stderr, "line:%d sqlite3_exec failed: %s\n", __LINE__, errmsg);
		return ;
	}
	printf("update values success\n");
}


//查询词典2,并发送信息
void dictionary_select2(int newfd, struct cli_msg* msg, sqlite3*db)
{
	char sql[128] = "select * from dictionary";
	char **pres = NULL;
	int row, colum;
	char *errmsg = NULL;
	int flag = 0;

	if(sqlite3_get_table(db, sql, &pres, &row, &colum, &errmsg) != SQLITE_OK)
	{
		fprintf(stderr, "line:%d sqlite3_exec failed: %s\n", __LINE__, errmsg);
		return ;
	}
	//printf("row=%d colum=%d\n", row, colum);  //共有几行,几列

	int k = 0;
	strcpy((*msg).mean, "词库中无此单词");
	for(int i = 0; i < row+1; i++)
	{
		for(int j = 0; j < colum; j++)
		{
			if(strcmp(pres[k++], (*msg).word) == 0)
			{
				flag = 1;
				strcpy((*msg).mean, pres[k]);
				//向客户端发送信息
				if(send(newfd, msg, sizeof(*msg), 0) < 0)
				{
					ERR_MSG("send");
					return ;
				}
			}
		}
	}
	if(0 == flag)
	{
		//向客户端发送信息
		if(send(newfd, msg, sizeof(*msg), 0) < 0)
		{
			ERR_MSG("send");
			return ;
		}
	}

	//释放内存空间
	sqlite3_free_table(pres);

}

//关闭数据库连接
void SQL_close(sqlite3*db)
{
	if(sqlite3_close(db) != SQLITE_OK)
	{
		fprintf(stderr, "sqlite3_close failed:%d %s\n",\
				sqlite3_errcode(db), sqlite3_errmsg(db));
		return ;
	}
}

//回收僵尸进程的回调函数
void handler(int sig)
{
	while(waitpid(-1, NULL, WNOHANG) > 0);
}
//回收僵尸进程
void Recycling_zombie_processes()
{
	sighandler_t s = signal(SIGCHLD, handler);
	if(SIG_ERR == s)
	{
		ERR_MSG("signal");
		return ;
	}
}

//建立TCP通信
int TCP_communication()
{
	//创建流式套接字
	int sfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sfd < 0)
	{
		ERR_MSG("socket");
		return -1;
	}

	//允许端口快速重用--->允许程序退出后,端口号能被新的进程快速覆盖
	//必须放在bind之前
	int reuse = 1;
	if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
	{
		ERR_MSG("setsockopt");
		return -1;
	}
	//printf("允许端口快速重用成功\n");


	//填充地址信息结构体
	//根据地址族的不同,地址信息结构体不一致,AF_INET:man 7 ip
	struct sockaddr_in sin;
	sin.sin_family 		= AF_INET; 		//必须填AF_INET
	sin.sin_port 		= htons(PORT); 		//端口号的网络字节序,1024-49151
	sin.sin_addr.s_addr = inet_addr(IP); 		//本机IP地址的网络字节序
										//更换网络环境后IP地址可能改变

	//绑定服务器的IP和端口
	if(bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
	{
		ERR_MSG("bind");
		return -1;
	}
	//printf("bind success\n");

	//将套接字设置为被动监听状态
	if(listen(sfd, 5) < 0)
	{
		ERR_MSG("listen");
		return -1;
	}
	//printf("listen success\n");

	return sfd;
}

//处理客户端信息
void deal_cil_msg(int newfd, struct sockaddr_in cin, sqlite3*db)
{
	ssize_t res = 0;
	struct cli_msg msg;
	int flag = 0;

	while(1)
	{
		//接收
		res = recv(newfd, &msg, sizeof(msg), 0);
		if(res < 0)
		{
			ERR_MSG("recv");
			return ;
		}
		else if(0 == res)
		{
			fprintf(stderr,"[%s:%d]newfd = %d 客户端下线\n",\
					inet_ntoa(cin.sin_addr), ntohs(cin.sin_port),newfd);
			return ;
		}
		//处理客户端注册信息
		deal_cil_msg1(newfd, cin, db, msg);
		//处理客户端登录信息
		deal_cil_msg2(newfd, cin, db, msg);
		//处理客户端查询请求
		deal_cil_msg3(newfd, cin, db, msg);
		//处理客户端退出登录请求
		deal_cil_msg4(newfd, cin, db, msg);
	}
}

//处理客户端注册信息
void deal_cil_msg1(int newfd, struct sockaddr_in cin, sqlite3*db, struct cli_msg msg)
{
	int flag = 0;

	//处理注册请求
	if(msg.type == 'R')
	{
		printf("[%s:%d]newfd = %d: 请求注册\n",\
				inet_ntoa(cin.sin_addr), ntohs(cin.sin_port),newfd);
		//导入注册信息
		if(1 == register_message_insert(db, msg.username, msg.password))
		{
			flag = 1;
			printf("[%s:%d]newfd = %d: 注册成功\n",\
					inet_ntoa(cin.sin_addr), ntohs(cin.sin_port),newfd);

			//向客户端发送注册成功消息
			send_to_cli(newfd, cin, flag);
		}
		else
		{
			flag = 2;
			printf("[%s:%d]newfd = %d: 注册失败\n",\
					inet_ntoa(cin.sin_addr), ntohs(cin.sin_port),newfd);

			//向客户端发送注册失败消息
			send_to_cli(newfd, cin, flag);
		}
	}
}

//处理客户端登录信息
void deal_cil_msg2(int newfd, struct sockaddr_in cin, sqlite3*db, struct cli_msg msg)
{
	int flag = 0;

	//处理登录请求
	if(msg.type == 'L')
	{
		printf("[%s:%d]newfd = %d: 请求登录\n",\
				inet_ntoa(cin.sin_addr), ntohs(cin.sin_port),newfd);
		//查询注册信息,判断用户名、密码是否已注册
		flag = register_select(msg.username, msg.password, db);
		//若用户已注册,且密码正确
		if(2 == flag)
		{
			//判断用户是否重复登录
			flag = login_username_select(msg.username, db);
			//若该用户已登录
			if(1 == flag)
			{
				flag = 3;
				printf("[%s:%d]newfd = %d: 登陆失败\n",\
						inet_ntoa(cin.sin_addr), ntohs(cin.sin_port),newfd);
			}
			//若该用户未登录
			else
			{
				flag = 2;
				//导入登入信息
				login_message_insert(newfd, msg.username, db);
				printf("[%s:%d]newfd = %d: 登陆成功\n",\
						inet_ntoa(cin.sin_addr), ntohs(cin.sin_port),newfd);
			}
			//向客户端发送信息2
			send_to_cli2(newfd, cin, flag);
		}
		//若用户已注册但密码错误,或用户未注册
		else
		{
			printf("[%s:%d]newfd = %d: 登陆失败\n",\
					inet_ntoa(cin.sin_addr), ntohs(cin.sin_port),newfd);
			//向客户端发送信息2
			send_to_cli2(newfd, cin, flag);
		}
	}
}

//处理客户端查询请求
void deal_cil_msg3(int newfd, struct sockaddr_in cin, sqlite3*db, struct cli_msg msg)
{
	//处理查询请求
	if(msg.type == 'S')
	{
		printf("[%s:%d]newfd = %d: 请求查询\n",\
				inet_ntoa(cin.sin_addr), ntohs(cin.sin_port),newfd);

		//查询词典2,并发送信息
		dictionary_select2(newfd, &msg, db);	

		msg.type = 'O';
		//向客户端发送信息
		if(send(newfd, &msg, sizeof(msg), 0) < 0)
		{
			ERR_MSG("send");
			return ;
		}
	}
}

//处理客户端退出登录请求
void deal_cil_msg4(int newfd, struct sockaddr_in cin, sqlite3*db, struct cli_msg msg)
{
	char buf[] = "success";
	//处理退出登录请求
	if(msg.type == 'Q')
	{
		printf("[%s:%d]newfd = %d: 请求退出登录\n",\
				inet_ntoa(cin.sin_addr), ntohs(cin.sin_port),newfd);
		//删除登录信息
		if(1 == login_message_delete(newfd, db))
			printf("[%s:%d]newfd = %d: 退出登录\n",\
					inet_ntoa(cin.sin_addr), ntohs(cin.sin_port),newfd);
		//向客户端发送信息
		if(send(newfd, buf, sizeof(buf), 0) < 0)
		{
			ERR_MSG("send");
			return ;
		}
	}
}

//向客户端发送信息
void send_to_cli(int newfd, struct sockaddr_in cin, int flag)
{
	char buf[128] = "";

	if(1 == flag)
	{
		bzero(buf, sizeof(buf));
		//发送注册成功
		strcpy(buf, "注册成功");
		if(send(newfd, buf, sizeof(buf), 0) < 0)
		{
			ERR_MSG("send");
			return ;
		}
	}
	else if(2 == flag)
	{
		bzero(buf, sizeof(buf));
		//发送注册失败
		strcpy(buf, "注册失败,用户名已存在");
		if(send(newfd, buf, sizeof(buf), 0) < 0)
		{
			ERR_MSG("send");
			return ;
		}
	}
}

//向客户端发送信息2
void send_to_cli2(int newfd, struct sockaddr_in cin, int flag)
{
	char buf[128] = "";

	if(0 == flag)
	{
		bzero(buf, sizeof(buf));
		//发送用户名不存在
		strcpy(buf, "用户名不存在");
		if(send(newfd, buf, sizeof(buf), 0) < 0)
		{
			ERR_MSG("send");
			return ;
		}
	}
	else if(1 == flag)
	{
		bzero(buf, sizeof(buf));
		//发送密码错误
		strcpy(buf, "密码错误");
		if(send(newfd, buf, sizeof(buf), 0) < 0)
		{
			ERR_MSG("send");
			return ;
		}
	}
	else if(2 == flag)
	{
		bzero(buf, sizeof(buf));
		//发送登录成功
		strcpy(buf, "success");
		if(send(newfd, buf, sizeof(buf), 0) < 0)
		{
			ERR_MSG("send");
			return ;
		}
	}
	else if(3 == flag)
	{
		bzero(buf, sizeof(buf));
		//发送重复登录
		strcpy(buf, "重复登录");
		if(send(newfd, buf, sizeof(buf), 0) < 0)
		{
			ERR_MSG("send");
			return ;
		}
	}
}

main.c:

#include <sqlite3.h>
#include "dictser.h"
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>

#define ERR_MSG(msg) do{\
	fprintf(stderr,"line:%d\n", __LINE__);\
	perror(msg);\
}while(0)

int main(int argc, const char *argv[])
{
	//捕获17号信号,回收僵尸进程
	Recycling_zombie_processes();

	//初始化词典数据
	sqlite3*db = SQL_create();

	//建立TCP通信
	int sfd = TCP_communication();

	struct sockaddr_in cin;
	socklen_t addrlen = sizeof(cin);

	while(1)
	{
		//父进程只负责处理客户端的连接
		int newfd = accept(sfd, (struct sockaddr *)&cin, &addrlen);
		if(newfd < 0)
		{
			ERR_MSG("accept");
			return -1;
		}
		fprintf(stderr,"[%s:%d]newfd = %d 客户端上线\n",\
				inet_ntoa(cin.sin_addr), ntohs(cin.sin_port),newfd);

		//能运行到当前位置,则代表有客户端连接成功
		//需要创建一个子进程用于于客户端交互
		pid_t pid = fork();
		if(0 == pid)    //只在子进程中执行
		{
			//子进程中负责与客户端交互
			//处理客户端信息
			deal_cil_msg(newfd, cin, db);

			//退出子进程
			close(newfd);
			exit(0);
		}
		else if(pid > 0)    //只在父进程中执行
		{
		}
		else
		{
			ERR_MSG("fork");
		}
	}
	//关闭文件描述符
	close(sfd);
	//关闭数据库连接
	SQL_close(db);

	return 0;
}

客户端代码:

client.h:

#ifndef __CLIENT_H__
#define __CLIENT_H__

typedef void (*sighandler_t)(int);
int sfd;

struct cli_msg 
{
	char type;
	char username[50];
	char password[50];
	char word[50];
	char mean[50];
};

//创建并打开一个数据库
sqlite3* SQL_create();
//创建一个表格存储历史记录      
void history_create(sqlite3*db);
//导入历史记录数据
void history_message_insert(struct cli_msg msg,sqlite3*db);
//查询历史记录
void history_message_select(sqlite3*db);
//获取时间函数
void get_time();

//异常退出处理
void capture_SIGINT();
//创建流式套接字,与服务器建立连接
int server_connect();
//与服务器进行对话
void conversation(int sfd);
//发送注册请求
void register_request(int sfd);
//发送登录请求
int login_request(int sfd);
//发送查询请求
void search_request(int sfd, sqlite3*db);
//发送退出登入请求
int logout_login_request(int sfd);

#endif

client.c:

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

#define ERR_MSG(msg) do{\
	fprintf(stderr,"line:%d\n", __LINE__);\
	perror(msg);\
}while(0)

#define IP "192.168.8.77"     //填服务器IP
#define PORT 6666      //服务器端口

//创建并打开一个数据库                                   
sqlite3* SQL_create()                                    
{                                                        
    printf("初始化历史记录数据库中...\n");                   
    sqlite3 *db = NULL;                                  
    if(sqlite3_open("./mysq.db", &db) != SQLITE_OK)      
    {                                                    
        fprintf(stderr, "sqlite3_open failed:%d %s\n",\
                sqlite3_errcode(db), sqlite3_errmsg(db));
        return NULL;                                     
    }                                                    
    //printf("数据库创建成功\n");                        
                                                         
    //创建一个表格存储历史记录                         
    history_create(db);                           
                                                         
    printf("历史记录数据库初始化完毕\n");                    
    return db;                                           
}                                                        

//创建一个表格存储历史记录
void history_create(sqlite3*db)
{
    char table_name[] = "history";                                                 
    char sql[128] = "";                                                               
    sprintf(sql, "create table if not exists %s (word char, mean char,time char);", table_name);
    char *errmsg = NULL;                                                              
    if(sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)                       
	{
        fprintf(stderr, "line:%d sqlite3_exec failed: %s\n", __LINE__, errmsg);       
        return ;                                                                      
	}
    //printf("%s create success\n", table_name);                                      
}                                                                                     

//导入历史记录数据
void history_message_insert(struct cli_msg msg,sqlite3*db)
{
	char sql[254] = "";
	char *errmsg = NULL;
	char Time[30] = "";

	//获取时间
	get_time(Time);

	//拼接SQL命令
	sprintf(sql, "insert into history values (\"%s\", \"%s\", \"%s\");", msg.word, msg.mean, Time);

	//运行SQL命令
	if(sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)
	{
        fprintf(stderr, "line:%d sqlite3_exec failed: %s\n", __LINE__, errmsg);
		return ;
	}
}

//查询历史记录
void history_message_select(sqlite3*db)
{
	char sql[128] = "select *from history";
	char **pres = NULL;
	int row, colum;
	char* errmsg = NULL;

	if(sqlite3_get_table(db, sql, &pres, &row, &colum, &errmsg) != SQLITE_OK)
	{
		fprintf(stderr, "line:%d sqlite3_exec failed: %s\n", __LINE__, errmsg);
		return ;
	}
	//printf("row=%d colum=%d\n", row, colum);  //共有几行,几列
	printf("\n");
	printf("---------历史记录--------\n");
	
	int k = 0;
	for(int i = 0; i < row+1; i++)
	{
		for(int j = 0; j < colum; j++)
		{
			printf("%-25s", pres[k++]);
		}
		printf("\n");
	}

	//释放内存空间
	sqlite3_free_table(pres);
}

//获取时间函数
void get_time(char*Time)
{
	time_t t1, t2;
	struct tm* t = NULL;

	t2 = time(&t1);

	t = localtime(&t1);
	sprintf(Time,"%d年 %d月 %d日 %02d:%02d",\
			t->tm_year+1900, t->tm_mon+1, \
			t->tm_mday, t->tm_hour, t->tm_min);

}

//异常退出处理的回调函数
void handler(int sig)
{
	logout_login_request(sfd);	
	printf("\n");
	exit(0);
}
//异常退出处理
void capture_SIGINT()
{
	sighandler_t s = signal(SIGINT, handler);
	if(SIG_ERR == s)
	{
		ERR_MSG("signal");
		return ;
	}
}

//创建流式套接字,与服务器建立连接
int server_connect()
{
	//创建流式套接字
	int sfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sfd < 0)
	{
		ERR_MSG("socket");
		return -1;
	}

	//允许端口快速重用--->允许程序退出后,端口号能被新的进程快速覆盖
	//必须放在bind之前
	int reuse = 1;
	if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
	{
		ERR_MSG("setsockopt");
		return -1;
	}

	//填充地址信息结构体
	//根据地址族的不同,地址信息结构体不一致,AF_INET:man 7 ip
	struct sockaddr_in sin;
	sin.sin_family 		= AF_INET; 		//必须填AF_INET
	sin.sin_port 		= htons(PORT); 		//端口号的网络字节序,1024-49151
	sin.sin_addr.s_addr = inet_addr(IP); 		//本机IP地址的网络字节序
											//更换网络环境后IP地址可能改变
	//与服务器建立连接
	if(connect(sfd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
	{
		ERR_MSG("connect");
		return -1;
	}
	printf("与服务器连接成功\n");

	return sfd;
}

//与服务器进行对话
void conversation(int sfd)
{
	char buf[128] = "";
	ssize_t res = 0;

	while(1)
	{
		bzero(buf, sizeof(buf));
		fgets(buf, sizeof(buf), stdin);
		buf[strlen(buf)-1] = '\0';
		//发送
		if(send(sfd, buf, sizeof(buf), 0) < 0)
		{
			ERR_MSG("send");
			return ;
		}

		bzero(buf, sizeof(buf));
		//接收
		res = recv(sfd, buf, sizeof(buf), 0);
		if(res < 0)
		{
			ERR_MSG("recv");
			return ;
		}
		else if(0 == res)
		{
			fprintf(stderr, "服务器关闭\n");
			return ;
		}
		printf("应答消息[%s]\n", buf);
	}
}

//发送注册请求
void register_request(int sfd)
{
	char buf[128] = "";
	ssize_t res = 0;
	//组注册请求消息
	struct cli_msg msg;
	printf("请输入用户名>>>");
	scanf("%s", msg.username);
	while(getchar() != 10);
	printf("请输入密码>>>");
	scanf("%s", msg.password);
	while(getchar() != 10);
	msg.type = 'R';

	//发送注册请求
	if(send(sfd, &msg, sizeof(msg), 0) < 0)
	{
		ERR_MSG("send");
		return ;
	}
	//接收服务器回应
	bzero(buf, sizeof(buf));
	res = recv(sfd, buf, sizeof(buf), 0);
	if(res < 0)
	{
		ERR_MSG("recv");
		return ;
	}
	else if(0 == res)
	{
		fprintf(stderr, "服务器关闭\n");
		return ;
	}
	printf("[%s]\n", buf);
}

//发送登录请求
int login_request(int sfd)
{
	char buf[128] = "";
	ssize_t res = 0;
	bzero(buf, sizeof(buf));
	//组登录请求消息
	struct cli_msg msg;
	printf("请输入用户名>>>");
	scanf("%s", msg.username);
	while(getchar() != 10);
	printf("请输入密码>>>");
	scanf("%s", msg.password);
	while(getchar() != 10);
	msg.type = 'L';

	//发送登录请求
	if(send(sfd, &msg, sizeof(msg), 0) < 0)
	{
		ERR_MSG("send");
		return -1;
	}
	//接收服务器回应
	bzero(buf, sizeof(buf));
	res = recv(sfd, buf, sizeof(buf), 0);
	if(res < 0)
	{
		ERR_MSG("recv");
		return -1;
	}
	else if(0 == res)
	{
		fprintf(stderr, "服务器关闭\n");
		return -1;
	}
	printf("[%s]\n", buf);
	if(strcmp(buf,"success") == 0)
		return 1;
}

//发送查询请求
void search_request(int sfd, sqlite3*db)
{
	ssize_t res = 0;
	//组查询请求消息
	struct cli_msg msg;
	bzero(msg.word, sizeof(msg.word));
	printf("请输入单词>>>");
	fgets(msg.word, sizeof(msg.word), stdin);
	(msg.word)[strlen(msg.word)-1] = 0;
	msg.type = 'S';

	//发送查询请求
	if(send(sfd, &msg, sizeof(msg), 0) < 0)
	{
		ERR_MSG("send");
		return ;
	}
	//接收服务器回应
	while(1)
	{
		res = recv(sfd, &msg, sizeof(msg), 0);
		if(res < 0)
		{
			ERR_MSG("recv");
			return ;
		}
		else if(0 == res)
		{
			fprintf(stderr, "服务器关闭\n");
			return ;
		}
		if(msg.type == 'O')
			break;
		printf("%s\t%s\n", msg.word, msg.mean);
		//导入历史记录数据
		history_message_insert(msg, db);
	}
	return ;
}

//发送退出登入请求
int logout_login_request(int sfd)
{
	char buf[10] = "";
	ssize_t res = 0;
	//组退出登录请求消息
	struct cli_msg msg;
	msg.type = 'Q';

	//发送退出登入请求
	if(send(sfd, &msg, sizeof(msg), 0) < 0)
	{
		ERR_MSG("send");
		return -1;
	}
	//接收服务器回应
	bzero(buf, sizeof(buf));
	res = recv(sfd, buf, sizeof(buf), 0);
	if(res < 0)
	{
		ERR_MSG("recv");
		return -1;
	}
	else if(0 == res)
	{
		fprintf(stderr, "服务器关闭\n");
		return -1;
	}
	if(strcmp(buf, "success") == 0)
		return 1;
	return 0;
}

main.c:

#include <sqlite3.h>
#include "client.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


int main(int argc, const char *argv[])
{
	//捕获SIGINT信号,捕获成功则发送退出登入消息,并退出客户端
	capture_SIGINT();

	//初始化数据库,用于存储历史记录
	sqlite3*db = SQL_create();
	
	//创建流式套接字,与服务器建立连接
	sfd = server_connect();

	char choose = 0;
LOADING:
	while(1)
	{
		system("clear");
		printf("------------------------\n");		
		printf("---------1.注册---------\n");		
		printf("---------2.登入---------\n");		
		printf("---------3.退出---------\n");		
		printf("------------------------\n");		
		printf("请输入>>>");
		choose = getchar();
		while(getchar() != 10);

		switch(choose)
		{
			case '1':
				//发送请求注册消息
				register_request(sfd);

				break;
			case '2':
				//发送请求登入消息
				if(1 == login_request(sfd))
				goto OPERATE;    //登入成功,则进入下一界面
				break;
			case '3':
				goto END;
				break;
			default:
				printf("输入错误,请重新输入\n");
		}
		printf("请输入任意字符清屏>>>");
		while(getchar() != 10);
	}
OPERATE:
	while(1)
	{
		system("clear");
		printf("------------------------\n");		
		printf("---------1.查询---------\n");		
		printf("-------2.历史记录-------\n");		
		printf("-------3.返回上级-------\n");		
		printf("------------------------\n");		
		printf("请输入>>>");
		choose = getchar();
		while(getchar() != 10);

		switch(choose)
		{
			case '1':
				//发送请求查询消息
				search_request(sfd, db);
				break;
			case '2':
				//查看本地历史记录
				history_message_select(db);
				break;
			case '3':
				//发送退出登入消息
				if(1 == logout_login_request(sfd))
					goto LOADING;
				break;
			default:
				printf("输入错误,请重新输入\n");
		}
		printf("请输入任意字符清屏>>>");
		while(getchar() != 10);
	}

END:
	//关闭文件描述符
	close(sfd);
	return 0;
}

运行测试效果:

(注册测试)

 


(登入测试)

 

 

 


 (查询测试)

 

 


 

 (查看历史记录测试)


(ctrl+c退出测试)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值