一.概述
在线电子词典设计内容
1.用户管理
用户注册,登录,退出,注销用户
2.查询管理
用户登录,查询历史记录,退出登录
二.知识储备
在该项目中用到文件IO,标准IO,Makefile,sqlite3数据库,TCP协议,epoll并发技术及相关网络编程技术,C语言相关知识
三.设计思维
1.并发流程图
2.客户机流程图
四.系统设计
电子词典项目由基于TCP协议的服务器和客户机实现,同时利用epoll并发技术可以实现多用户同时登陆进行查询单词。在客户端用户可以进行用户登录,注册和退出,在用户进行注册后返回主界面可进行登录或退出,用户进行登录操作即客户机与服务器进行连接,连接成功后即可输入要查询的单词,由服务器获取客户端发来的数据并对数据解析查找所对应的单词释义发送给客户端完成查询单词操作,用户登录后一旦查询单词则会产生查询记录保存在文件中,用户可以查看自己的历史记录(每个用户都有属于自己的历史记录文件),注销用户即将用户信息删除并返回一级主界面,退出功能则不细说。
1.系统数据结构设计
typedef struct _users{
char name[256];//用户名
int password;//密码
}user_t;
定义结构体初始化用户名和密码,注册则将结构体中信息插入数据库中保存
2.系统主要函数设计
2.1客户端
2.1.1int main函数
该函数主要创建客户端流式套接字,数据库句柄,打印相关帮助信息为用户提供选择,不同选择会进入不同分支
2.1.2void print_menu_one_func();
该函数打印一级界面提示用户做出选择
2.1.3int create_tables_func(sqlite3 *my_db,const char *sql);
该函数功能为在数据库中创建表格保存用户的用户名和密码
参数:my_db:数据库句柄
sql:创建表格的sql语句
2.1.4int insert_tables_func(sqlite3 *my_db,user_t user,const char *tablename);
该函数功能为向哪个表中插入用户信息
参数:my_db:数据库句柄
user:用户结构体
tablename:数据库中对应的表名
2.1.5int judge_user_func(sqlite3 *my_db,user_t user,const char *tablename);
该函数功能为判断该用户是否已经注册
参数:my_db:数据库句柄
user:用户结构体
tablename:数据库中对应的表名
2.1.6int delete_user_func(sqlite3 *my_db,user_t user,const char *tablename);
该函数功能为注销用户,在数据库中删除用户信息
参数:my_db:数据库句柄
user:用户结构体
tablename:数据库中对应的表名
2.1.7int register_user_func(sqlite3 *my_db,user_t *user,const char *tablename);
该函数功能为用户登录,在登录前会判断该用户是否已经注册,在登陆的同时会将登录时间及用户名写入登录日志
参数:my_db:数据库句柄
user:用户结构体(这里使用地址传参,后面查询单词,并将记录写入文件要用到用户信息)
tablename:数据库中对应的表名
2.1.8int client_user_func(int socket_fd);
该函数功能为客户机与服务器进行连接
参数:socket_fd,依据TCP协议创建的流式套接字
2.1.9void print_menu_two_func();
该函数打印二级界面提示用户做出选择
2.1.10int create_user_history_func(user_t user,const char *world);
该函数功能为将用户查询单词记录写入文件
参数:user:用户结构体
world:查询的单词
2.1.11int search_history_func(user_t user);
该函数功能为用户查看自己的查询历史记录
参数:user:用户结构体
2.2服务器
2.2.1int main函数
该主函数主要负责创建流式套接字,创建epoll实例,事件结构体,就绪事件数组,通过epoll并发实现与多客户机接收和发送数据
2.2.2int sever_func(int socket_fd);
该函数功能为流式套接字绑定相关属性,并设为监听状态
2.2.3int search_data_func(sqlite3 *my_db,const char *tablename,const char *world,int connect_fd);
该函数功能为接受客户机发来的数据并在数据库中查询相应的单词并发送给客户机
参数:my_db:数据库句柄
tablename:存储单词的表名
world:客户机发送来的单词
connect_fd:与客户机连接的文件描述符
五.运行效果图
1.客户机
1.1.用户注册
1.2.用户登录
1.3.查找单词
1.4查找历史记录
1.5注销用户
1.6退出一级界面
1.7用户登录日志
2.服务器
六.总结
通过编写此次项目,对前期所学C语言,Makefile,IO,网络编程的知识进行的复盘,也很好的进行了综合应用,对后续编写大型项目积累了经验,更多的是对前期知识的巩固。在此次项目中,虽然考虑到了不少,但仍有很多不足,比如:在用户注册对的时候没有对数据库中原有的用户信息比对产看是否存在已经注册的情况,当输入一个单词因为输入不正确或者什么的不当在单词库中找不到时会一直阻塞,这里是以数字按键进行下一步,存在误操作输入不当没有进行检查并进行提示,需重启才可进行,虽有许多不足吧但也实现了不少功能,在这里希望可以给没有思路的友友们提供一个很好的思路,本人代码仅供参考。
注:此次项目中采用分文件编程因需编译文件多利用Makefile,这里Makefile和数据库不再细说,单词表是提前导入好的。
七.代码
1.客户机
1.1client.h
#ifndef _CLIENT_H
#define _CLIENT_H
#include<stdio.h>
#include <stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<string.h>
#include<stdbool.h>
#include<sys/epoll.h>
#include<sqlite3.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
typedef struct _users{
char name[256];
int password;
}user_t;
void print_menu_one_func();
int create_tables_func(sqlite3 *my_db,const char *sql);
int insert_tables_func(sqlite3 *my_db,user_t user,const char *tablename);
int judge_user_func(sqlite3 *my_db,user_t user,const char *tablename);
int delete_user_func(sqlite3 *my_db,user_t user,const char *tablename);
int register_user_func(sqlite3 *my_db,user_t *user,const char *tablename);
int client_user_func(int socket_fd);
void print_menu_two_func();
int create_user_history_func(user_t user,const char *world);
int search_history_func(user_t user);
#endif
1.2client.c
#include"client.h"
void print_menu_one_func(){
printf("\t-----------------------------------------------------------\n");
printf("\t| 在线电子词典 |\n");
printf("\t| 版本 :4.6.2 |\n");
printf("\t| 创作者 :晴耕雨读912 |\n");
printf("\t| 功能 : |\n");
printf("\t| [1] 登录 |\n");
printf("\t| [2] 注册 |\n");
printf("\t| [3] 退出 |\n");
printf("\t| 注意 :用户只有登陆成功后才能进入查询单词界面 |\n");
printf("\t-----------------------------------------------------------\n");
}
//创建用户数据表函数
int create_tables_func(sqlite3 *my_db,const char *sql){
if(NULL==my_db || NULL==sql){
printf("入参失败,请检查...\n");
return -1;
}
char *errmage;
int ret=sqlite3_exec(my_db,sql,NULL,NULL,&errmage);
if(SQLITE_OK!=ret){
printf("创建数据表失败:%s\n",errmage);
return -2;
}
return 0;
}
//用户数据表插入数据函数
int insert_tables_func(sqlite3 *my_db,user_t user,const char *tablename){
if(NULL==my_db || NULL==tablename){
printf("入参失败,请检查...\n");
return -1;
}
memset(&user,0,sizeof(user));
printf("请输入用户名>>");
scanf("%s",user.name);
printf("请输入用户名密码>>");
scanf("%d",&user.password);
char *sql_user=sqlite3_mprintf("insert into %q values('%q',%d)",tablename,user.name,user.password);
char *errmage;
int ret=sqlite3_exec(my_db,sql_user,NULL,NULL,&errmage);
if(SQLITE_OK!=ret){
printf("数据表插入数据失败:%s\n",errmage);
return -2;
}
sqlite3_free(sql_user);
return 0;
}
//判断用户是否存在函数
int judge_user_func(sqlite3 *my_db,user_t user,const char *tablename){
if(NULL==my_db || NULL==tablename){
printf("入参失败,请检查...\n");
return -1;
}
int my_callback_func1(void *data,int argc,char **argv,char **zazColName){
char *name=(char *)data;
//printf("%s\n",argv[0]);
//printf("%s\n",name);
int a=strcmp(name,argv[0]);
//printf("%d\n",a);
if(strcmp(name,argv[0])){
printf("用户名不存在...\n");
return -1;
}
return 0;
}
int my_callback_func2(void *data,int argc,char **argv,char **zazColName){
int password=*((int *)data);
//printf("%s\n",argv[1]);
//printf("%d\n",password);
int value=atoi(argv[1]);
//printf("%d\n",value);
if(password!=value){
printf("密码不正确,请重新输入...\n");
return -1;
}
return 0;
}
const char *sql_func1=sqlite3_mprintf("select *from %q where 用户名='%q'",tablename,user.name);
const char *sql_func2=sqlite3_mprintf("select *from %q where 用户名='%q'",tablename,user.name);
//printf("%s\n",sql_func1);
//printf("%s\n",sql_func2);
char *errmage;
int ret=sqlite3_exec(my_db,sql_func1,&my_callback_func1,&user.name,&errmage);
if(SQLITE_OK!=ret){
printf("数据表查找用户名数据失败:%s\n",errmage);
return -2;
}
ret=sqlite3_exec(my_db,sql_func2,&my_callback_func2,&user.password,&errmage);
if(SQLITE_OK!=ret){
printf("数据表查找用户密码数据失败:%s\n",errmage);
return -2;
}
return 0;
}
//注销用户函数
int delete_user_func(sqlite3 *my_db,user_t user,const char *tablename){
if(NULL==my_db || NULL==tablename){
printf("入参失败,请检查...\n");
return -1;
}
memset(&user,0,sizeof(user));
printf("请输入用户名>>");
scanf("%s",user.name);
printf("请输入用户名密码>>");
scanf("%d",&user.password);
int value=judge_user_func(my_db,user,tablename);
char *sql_user=sqlite3_mprintf("delete from %q where 用户名='%q'",tablename,user.name);
char *errmage;
int ret=sqlite3_exec(my_db,sql_user,NULL,NULL,&errmage);
if(SQLITE_OK!=ret){
printf("数据表删除数据失败:%s\n",errmage);
return -2;
}
sqlite3_free(sql_user);
printf("注销用户成功...\n");
return 0;
}
//用户登录函数
int register_user_func(sqlite3 *my_db,user_t *user,const char *tablename){
if(NULL==my_db || NULL==tablename){
printf("入参失败,请检查...\n");
return -1;
}
memset(user,0,sizeof(user));
printf("请输入用户名>>");
scanf("%s",user->name);
printf("请输入用户名密码>>");
scanf("%d",&user->password);
int value=judge_user_func(my_db,*user,tablename);
int get_line(FILE *fp){
int ret=0;
int count=0;
while((ret=fgetc(fp))!=-1){
if(ret=='\n'){
count++;
}
}
return count;
}
if(0==value){
printf("登录成功,请开始您的单词查询之旅...\n");
//创建日志文件,将用户登录信息写入日志
FILE *fp=fopen("/home/lyh/23071/network/epoll_words/register.txt","a+");
if(NULL==fp){
perror("fopen error");
return -1;
}
static time_t sec=0;
static time_t old_sec=0;
int line=get_line(fp);
time(&sec);
if(sec!=old_sec){
struct tm *my_time=localtime(&sec);
line++;
//拼接用户信息
fprintf(fp, "%d.%4d-%02d-%02d %02d:%02d:%02d:%s\n", line, \
my_time->tm_year+1900, my_time->tm_mon+1, my_time->tm_mday, \
my_time->tm_hour, my_time->tm_min, my_time->tm_sec,user->name);
fflush(fp);//手动刷新缓冲区
old_sec = sec;
}
}else{
return -1;
}
return 0;
}
//客户机启动函数
int client_user_func(int socket_fd){
//目标主机地址信息结构体
struct sockaddr_in severInfo={.sin_family=AF_INET,.sin_addr=inet_addr("192.168.250.100"),.sin_port=htons(7777)};
int socket_len=sizeof(severInfo);
//链接目标主机
int ret=connect(socket_fd,(struct sockaddr *)&severInfo,socket_len);
if(-1==ret){
perror("connect error:");
return -1;
}
printf("TCP客户端启动\n");
return 0;
}
//二级界面函数
void print_menu_two_func(){
printf("\t-----------------------------------------------------------\n");
printf("\t| 在线电子词典 |\n");
printf("\t| 版本 :4.6.2 |\n");
printf("\t| 创作者 :晴耕雨读912 |\n");
printf("\t| 功能 : |\n");
printf("\t| [1] 查单词 |\n");
printf("\t| [2] 查询历史记录 |\n");
printf("\t| [3] 退出查询系统 |\n");
printf("\t| [4] 注销用户 |\n");
printf("\t| 注意 :用户只有登陆成功后才能进入查询单词界面 |\n");
printf("\t-----------------------------------------------------------\n");
}
//创建用户历史记录函数
int create_user_history_func(user_t user,const char *world){
char pathname[4096]={0};
sprintf(pathname,"/home/lyh/23071/network/epoll_words/%s.txt",user.name);
//char pathname[1024]="/home/lyh/23071/network/epoll_words/user.txt";
//printf("%s\n",pathname);
FILE *fp=fopen(pathname,"a+");
if(NULL==fp){
perror("fopen error");
return -1;
}
int get_line(FILE *fp){
int ret=0;
int count=0;
while((ret=fgetc(fp))!=-1){
if(ret=='\n'){
count++;
}
}
return count;
}
static time_t sec=0;
static time_t old_sec=0;
int line=get_line(fp);
time(&sec);
if(sec!=old_sec){
struct tm *my_time=localtime(&sec);
line++;
//拼接用户信息
fprintf(fp, "%d.%4d-%02d-%02d %02d:%02d:%02d---%s\n", line, \
my_time->tm_year+1900, my_time->tm_mon+1, my_time->tm_mday, \
my_time->tm_hour, my_time->tm_min, my_time->tm_sec,world);
fflush(fp);//手动刷新缓冲区
old_sec = sec;
}
}
//查询历史记录函数
int search_history_func(user_t user){
char pathname[4096]={0};
sprintf(pathname,"/home/lyh/23071/network/epoll_words/%s.txt",user.name);
//char pathname[1024]="/home/lyh/23071/network/epoll_words/user.txt";
//printf("%s\n",pathname);
FILE *fp=fopen(pathname,"r");
if(NULL==fp){
perror("fopen error");
return -1;
}
char buf[512]={0};
while(fgets(buf,sizeof(buf),fp)!=NULL){
printf("%s\n",buf);
}
}
1.3client_main.c
#include"client.h"
int main(int argc, char const *argv[])
{
sqlite3 *my_database;
user_t user;
int ret=0;
printf("欢迎您使用电子词典...\n");
ret=sqlite3_open("my_epoll_worlds_database.db",&my_database);
if(SQLITE_OK!=ret){
printf("创建数据库失败...\n");
exit(EXIT_FAILURE);
}
int option=0;
int option2=0;
int value=0;
while(true){
print_menu_one_func();
printf("请输入您的选择>>");
scanf("%d",&option);
if(3==option){
printf("欢迎您下次使用...\n");
exit(EXIT_FAILURE);
}else if(2==option){
//注册用户则将信息写入数据库保存
//创建用户数据表
char *sql1="create table if not exists user1(用户名 text primary key,密码 int)";
create_tables_func(my_database,sql1);
//将用户信息写入数据表
insert_tables_func(my_database,user,"user1");
printf("恭喜您,注册成功...\n");
continue;
}else if(1==option){
//判断用户是否已注册
value=register_user_func(my_database,&user,"user1");
if(0!=value){
continue;
}
int socket_fd=socket(AF_INET,SOCK_STREAM,0);
if(-1==socket_fd){
perror("socket error");
exit(EXIT_FAILURE);
}
client_user_func(socket_fd);
//查询单词---epoll并发
while(true){
print_menu_two_func();
printf("请输入您的选择>>");
scanf("%d",&option2);
getchar();
if(4==option2){
delete_user_func(my_database,user,"user1");
break;
}else if(3==option2){
printf("已退出登录\n");
break;
}else if(2==option2){
//创建历史记录文件并查看---专属本用户的
search_history_func(user);
}else if(1==option2){
//向服务器发送要查询的数据由服务器查找并返回给客户机需要的内容
char buf[4096]={0};
memset(buf,0,sizeof(buf));
printf("请输入要查询的单词>>");
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]='\0';
printf("%s\n",buf);
int nbytes=write(socket_fd,buf,strlen(buf));
//printf("%d\n",nbytes);
if(-1==nbytes){
perror("write error:");
return -1;
}
char oldbuf[4096]={0};
strcpy(oldbuf,buf);
memset(buf,0,sizeof(buf));
nbytes=read(socket_fd,buf,sizeof(buf));
if(-1==nbytes){
perror("read error:");
return -1;
}
if(0==nbytes){
printf("服务器已关闭\n");
return -1;
}
buf[strlen(buf)-1]='\0';
printf("单词: %s: %s\n",oldbuf,buf);
create_user_history_func(user,oldbuf);
}
}
close(socket_fd);
}
}
return 0;
}
2.服务器
2.1sever.h
#ifndef _SEVER_H
#define _SEVER_H
#include<stdio.h>
#include <stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<string.h>
#include<stdbool.h>
#include<sys/epoll.h>
#include<sqlite3.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
//epoll---TCP服务器
int sever_func(int socket_fd);
#endif
2.2sever.c
#include"sever.h"
//服务器启动函数
int sever_func(int socket_fd){
//绑定套接字
struct sockaddr_in severInfo={.sin_family=AF_INET,.sin_port=htons(7777),.sin_addr.s_addr=INADDR_ANY};
int socket_len=sizeof(severInfo);
int ret=bind(socket_fd,(struct sockaddr *)&severInfo,socket_len);
if(-1==ret){
perror("bind error");
return -1;
}
//将套接字设为监听状态
ret=listen(socket_fd,10);
if(-1==ret){
perror("listen error");
return -1;
}
printf("epoll---TCP服务器启动\n");
return 0;
}
//创建用户数据表函数
int create_tables_func(sqlite3 *my_db,const char *sql){
if(NULL==my_db || NULL==sql){
printf("入参失败,请检查...\n");
return -1;
}
char *errmage;
int ret=sqlite3_exec(my_db,sql,NULL,NULL,&errmage);
if(SQLITE_OK!=ret){
printf("创建数据表失败:%s\n",errmage);
return -2;
}
return 0;
}
int search_data_func(sqlite3 *my_db,const char *tablename,const char *world,int connect_fd){
if(NULL==my_db || NULL==tablename || NULL==world){
printf("入参失败,请检查...\n");
return -1;
}
int i=0;
char *errmage;
//回调函数
int my_callback_func1(void *data,int argc,char **argv,char **zazColName){
int fd=*(int *)data;
int i=0;
char buf[512]={0};
for(i=0;i<argc;i++){
sprintf(buf,"%s:%s\n",zazColName[i],argv[i]);
}
printf("%s\n",buf);
int nbtyes=write(fd,buf,strlen(buf));
if(-1==nbtyes){
perror("write error");
return -1;
}
return 0;
}
char *sql=sqlite3_mprintf("select *from %q where 单词='%q'",tablename,world);
int ret=sqlite3_exec(my_db,sql,&my_callback_func1,&connect_fd,&errmage);
if(SQLITE_OK!=ret){
printf("数据表查找数据失败:%s\n",errmage);
return -2;
}
sqlite3_free(sql);
return 0;
}
2.3sever_main.c
#include"sever.h"
int main(int argc, char const *argv[])
{
int socket_fd=socket(AF_INET,SOCK_STREAM,0);
if(-1==socket_fd){
perror("socket error");
exit(EXIT_FAILURE);
}
sever_func(socket_fd);
struct sockaddr_in clientInfo={.sin_family=AF_INET};
int client_len=sizeof(clientInfo);
//创建文件描述符集合体
int epoll_fd=epoll_create1(0);
if(-1==epoll_fd){
perror("epoll creat error");
exit(EXIT_FAILURE);
}
//创建事件结构体
struct epoll_event my_event={.events=EPOLLIN,.data.fd=socket_fd};
//将关心的文件描述符放入事件实例中
int ret=epoll_ctl(epoll_fd,EPOLL_CTL_ADD,socket_fd,&my_event);
if(-1==ret){
perror("epoll ctl add error");
exit(EXIT_FAILURE);
}
//创建就绪事件结构体数组
struct epoll_event my_user_revents[2048]={0};
int maxrevents=sizeof(my_user_revents)/sizeof(struct epoll_event);
int nbtyes=0;
int i=0;
int connect_fd=0;
char buf[512]={0};
//创建数据库
sqlite3 *my_database;
ret=sqlite3_open("my_epoll_worlds_database.db",&my_database);
if(SQLITE_OK!=ret){
printf("创建数据库失败...\n");
exit(EXIT_FAILURE);
}
//创建数据表
char *sql1="create table if not exists world1(单词 text primary key,释义 text)";
create_tables_func(my_database,sql1);
//进行数据循环收发
while(true){
int fds=epoll_wait(epoll_fd,my_user_revents,maxrevents,-1);
if(-1==fds){
perror("epoll_wait error");
exit(EXIT_FAILURE);
}
for(i=0;i<fds;i++){
//判断就绪事件类型
if(socket_fd==my_user_revents[i].data.fd){
connect_fd=accept(my_user_revents[i].data.fd,(struct sockaddr *)&clientInfo,&client_len);
if(-1==connect_fd){
perror("accept error");
continue;
}
printf("客户机IP:%s 端口:%d\n",inet_ntoa(clientInfo.sin_addr),ntohs(clientInfo.sin_port));
//将新产生的就绪事件放入实例中
my_event.data.fd=connect_fd;
my_event.events=EPOLLIN;
epoll_ctl(epoll_fd,EPOLL_CTL_ADD,connect_fd,&my_event);
}else{
memset(buf,0,sizeof(buf));
nbtyes=read(my_user_revents[i].data.fd,buf,sizeof(buf));
if(-1==nbtyes){
perror("read error");
continue;
}
if(0==nbtyes){
printf("客户机已关闭\n");
//将就绪事件删除
epoll_ctl(epoll_fd,EPOLL_CTL_DEL,my_user_revents[i].data.fd,&my_user_revents[i]);
close(my_user_revents[i].data.fd);
continue;
}
printf("客户端数据%s\n",buf);
//单词已插入数据表
//查询数据并发送给客户端
int value=search_data_func(my_database,"world1",buf,my_user_revents[i].data.fd);
if(0!=value){
printf("单词不存在...\n");
}
}
}
}
return 0;
}