项目总体要求
服务器端功能需求:
客户端功能需求:
dictionary.h
#ifndef __DICTIONARY_H__
#define __DICTIONARY_H__
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include "string.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <sqlite3.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PORT 8888
#define IP "192.168.8.249"
#define DB_NAME "dict.db"
#define WORD_SQL "create table if not exists dictionary (word char,meaning char);"
#define HISTORY_SQL "create table if not exists history (name char,word char,meaning char,time char);"
#define HISTORY_INSERT_FORMAT "insert into history values(\"%s\",\"%s\",\"%s\",\"%s\")"
#define HISTORY_SELECT_FORMAT "select * from history where name = \"%s\""
#define USER_SQL "create table if not exists user (name char primary key,passwd char,status);"
#define USER_INSERT_FORMAT "insert into user values(\"%s\",\"%s\",%d)"
#define USER_SELECT_FORMAT "select * from user where name = \"%s\" and passwd = \"%s\""
#define USER_UPDATE_FORMAT "update user set status = %d where name = '%s'"
#define DICT_SELECT_SQL "select * from dictionary"
#define DICT_SELECT_FORMAT "select * from dictionary where word = \"%s\""
#define WORD_INSERT_FORMAT "insert into dictionary values(\"%s\",\"%s\");"
#define FILE_PATH_TEST "dict_test.txt"
#define FILE_PATH "dict.txt"
#define DICT_ROW 7987
#define DICT_TEST_ROW 64
#define ERR_MSG(msg) do {\
fprintf(stderr,"line: __%d__ ",__LINE__);\
perror(msg);\
} while (0)
//定义消息类型结构体,用于客户端向服务器发送消息
typedef struct _msg
{
char name[20];
char passwd[20];
char text[128];
int status; //用户登录状态 1.登录成功 0.退出
int type; //1.注册 2.登录 3.退出 4.查单词 5.查历史记录
} msg_t;
/*******************服务器部分所用函数**********************/
//获取当前系统时间
void get_system_time();
//接收及发送的函数
int deal_cli_inter(int newfd,struct sockaddr_in cin,sqlite3 *db);
//回收僵尸进程
void handler(int sig);
//数据库初始化,包括库,表的创建,以及数据的导入
void database_init(sqlite3 *db);
//数据库数据导入
int load_db(sqlite3 *db,char *filepath);
//服务器端注册
void do_server_register(int newfd, msg_t* msg, sqlite3 *db);
//服务器端登录
void do_server_login(int newfd,msg_t* msg,sqlite3 * db);
//服务器端退出
void do_server_exit(int newfd,msg_t* msg,sqlite3 * db);
//服务器查询单词
void do_server_word_query(int newfd,msg_t* msg,sqlite3 * db);
//服务器查询历史记录
void do_server_history_query(int newfd,msg_t* msg,sqlite3 * db);
/*******************客户端部分所用函数**********************/
//客户端处理 ctrl+c SIGINT信号的函数
void ctrlc_handler(int sig);
//客户端注册功能
void do_register(int cfd,msg_t* msg);
//客户端登录功能
void do_login(int cfd,msg_t* msg);
//客户端查单词
void do_query_word(int cfd,msg_t* msg);
//客户端查历史记录
void do_query_history(int cfd,msg_t* msg);
//客户端给服务器发送用户退出标识
void set_user_exit_status(int cfd,msg_t* msg);
//客户端打印一级菜单
void print_1st_level_menu();
//客户端打印二级菜单
void print_2nd_level_menu();
#endif
dict_server.c
#include "dictionary.h"
int main(int argc, char const *argv[])
{
//捕获17号信号,注册新的处理函数,用于回收僵尸进程
__sighandler_t s = signal(SIGCHLD,handler);
if (SIG_ERR == s)
{
printf("signal");
return -1;
}
//1.socket 创建流式套接字 -->> TCP
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if (sfd < 0)
{
ERR_MSG("socket");
return -1;
}
printf("sfd = %d\n",sfd);
//允许端口快速被复用
int reuse = 1;
if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse)) < 0)
{
ERR_MSG("setsockopt");
return -1;
}
//填充地址信息结构体,真实的地址信息结构体在man 7 ip
//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
//2.bind: 功能 将IP地址和端口号绑定到指定套接字中
if(bind(sfd, (const struct sockaddr *)&sin,sizeof(sin)) < 0)
{
ERR_MSG("bind");
return -1;
}
printf("bind success __%d__\n",__LINE__);
//3.listen: 将套接字设置为被动监听状态,只负责监听是否有客户端连接成功
if(listen(sfd,10) <0)
{
ERR_MSG("listen");
return -1;
}
printf("listen success __%d__\n",__LINE__);
int login_fail_flag = 0; //登录失败状态标识 1:失败 0:成功
//初始化数据库,导入数据
//创建数据库
sqlite3 *db = NULL;
if(sqlite3_open(DB_NAME,&db)!= SQLITE_OK)
{
fprintf(stderr,"database create failed,__%d__,%d,%s\n",__LINE__,sqlite3_errcode(db),
sqlite3_errmsg(db));
return -1;
}
printf("database create success!\n");
database_init(db);
struct sockaddr_in cin; //存储客户端的地址信息
socklen_t addrlen = sizeof(cin);
int newfd = 0;
int cpid = 0;
while (1)
{
//父进程只负责连接
newfd = accept(sfd,(struct sockaddr *)&cin,&addrlen);
if (newfd < 0)
{
ERR_MSG("accept");
return -1;
}
printf("[%s | %d] newfd = %d\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd);
cpid = fork();
if (0==cpid)
{
close(sfd); //sfd对于子进程没有用
//与客户端交互的代码
deal_cli_inter(newfd,cin,db);
//退出子进程
exit(0);
} else if(cpid > 0)
{
close(newfd); //newfd对于父进程没有用
}
}
close(sfd);
return 0;
}
int deal_cli_inter(int newfd,struct sockaddr_in cin,sqlite3 *db)
{
//通过文件描述符,读取数据
//原型 ssize_t recv(int sockfd, void *buf, size_t len, int flags);
msg_t msg;
bzero(&msg,sizeof(msg));
ssize_t ret = 0;
while (1)
{
ret = recv(newfd,&msg,sizeof(msg),0);
if (ret < 0)
{
ERR_MSG("recv");
break;
} else if (0 == ret)
{
//当用户名存在的时候,将用户表status置0
/*
在一级菜单界面,按下退出,由于还未登录,所以不能将状态位置0
*/
printf("[%s:%d] 断开连接\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));
break;
}
printf("[%s:%d] 连接成功\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));
/*******************************************************/
switch (msg.type)
{
case 1:
do_server_register(newfd, &msg, db);
break;
case 2:
do_server_login(newfd, &msg, db);
break;
case 3:
do_server_exit(newfd, &msg, db);
break;
case 4:
do_server_word_query(newfd, &msg, db);
break;
case 5:
do_server_history_query(newfd,&msg, db);
break;
default:
break;
}
}
close(newfd);
return 0;
}
//数据库初始化,创建数据库及建表
void database_init(sqlite3 *db)
{
//创建表
//1.创建单词及释义表
char *errmsg = NULL;
if (sqlite3_exec(db,WORD_SQL,NULL,NULL,&errmsg)!= SQLITE_OK)
{
fprintf(stderr,"dictionary table create failed,__%d__,%d,%s\n",__LINE__,sqlite3_errcode(db),errmsg);
return;
}
printf("dictionary table create success!\n");
//2.创建历史记录表
if (sqlite3_exec(db,HISTORY_SQL,NULL,NULL,&errmsg)!= SQLITE_OK)
{
fprintf(stderr,"history table create failed,__%d__,%d,%s\n",__LINE__,sqlite3_errcode(db),errmsg);
return;
}
printf("history table create success!\n");
//3.创建用户表
if (sqlite3_exec(db,USER_SQL,NULL,NULL,&errmsg)!= SQLITE_OK)
{
fprintf(stderr,"user table create failed,__%d__,%d,%s\n",__LINE__,sqlite3_errcode(db),errmsg);
return;
}
printf("user table create success!\n");
load_db(db,FILE_PATH);
}
//调入词典数据
int load_db(sqlite3 *db,char *filepath)
{
//首先检查词典数据库中有无数据,有数据就阻止导入
char **pres = NULL;
int row,column;
char *errmsg = NULL;
if(sqlite3_get_table(db,DICT_SELECT_SQL,&pres,&row,&column,&errmsg)!= SQLITE_OK)
{
fprintf(stderr,"server load_db query dictionary failed,__%d__,%d,%s\n",__LINE__,sqlite3_errcode(db),errmsg);
return -1;
}
printf("server load_db query dictionary,row = %d,row =%d\n",row,column);
if (row >= DICT_ROW)
{
printf("词典数据库中数据已存在!\n");
return -1;
}
//1.以只读的方式打开文件
FILE *fp = fopen(filepath,"r");
if (NULL == fp)
{
ERR_MSG("dict source file open");
return -1;
}
char buf[128] = {0};
int16_t ret = 0;
//读取数据,写入数据库
char sql[128] = {0};
if (NULL == fp)
{
ERR_MSG("dict source file open");
return -1;
}
int num = 1;
char word[64] = {0};
char meaning[64] = {0};
while (1)
{
bzero(word,sizeof(word));
bzero(meaning,sizeof(meaning));
bzero(buf,sizeof(buf));
int m_index = 0;
int j = 0;
if (NULL == fgets(buf,sizeof(buf),fp))
{
printf("所有数据导入完毕,准备退出\n");
sleep(1);
break;
} else
{
buf[strlen(buf)-1] = 0;
//printf("[%d] %s\n",num,buf);
//遍历buf,找到第一个空格
for (int i = 0; i < strlen(buf); i++)
{
word[i] = buf[i];
if (buf[i] == ' ' && buf[i+1] == ' ' && buf[i+2] == ' ')
{
word[i] = 0;
m_index = i+3;
}
if (0!=m_index)
{
if (j < sizeof(meaning))
{
meaning[j] = buf[m_index];
m_index++;
j++;
}
}
}
//构建sql插入数据
sprintf(sql,WORD_INSERT_FORMAT,word,meaning);
printf("[%d] sql=%s\n",num,sql);
if (sqlite3_exec(db,sql,NULL,NULL,&errmsg)!= SQLITE_OK)
{
fprintf(stderr,"dict insert failed,__%d__,%d,%s\n",__LINE__,sqlite3_errcode(db),errmsg);
return -1;
}
printf("[%d] %s %s inserted ok\n",num,word,meaning);
num++;
}
}
return 0;
}
void do_server_register(int newfd, msg_t *msg, sqlite3 *db)
{
char sql[128] = {0};
msg->status = 0; //注册成功后用户处于未登录状态
sprintf(sql,USER_INSERT_FORMAT,msg->name,msg->passwd,msg->status);
printf("register sql = %s \n",sql);
char *errmsg = NULL;
if (sqlite3_exec(db,sql,NULL,NULL,&errmsg)!= SQLITE_OK)
{
fprintf(stderr,"insert user failed,__%d__,%d,%s\n",__LINE__,sqlite3_errcode(db),errmsg);
//插入失败,返回用户名已存在
strcpy(msg->text,"注册失败,该账户已经被注册!");
ssize_t sret = send(newfd,msg,sizeof(msg_t),0);
if (sret<0)
{
ERR_MSG("server do_server_register send");
return;
}
return;
} else
{
//插入成功,返回 返回插入成功
//2. 发送消息给客户端
strcpy(msg->text,"用户注册成功");
ssize_t sret = send(newfd,msg,sizeof(msg_t),0);
if (sret<0)
{
ERR_MSG("server do_server_register send");
return;
}
}
return;
}
void do_server_login(int newfd, msg_t* msg, sqlite3 *db)
{
char sql[128] = {0};
sprintf(sql,USER_SELECT_FORMAT,msg->name,msg->passwd) ;
printf("server do_server_login sql = %s \n",sql);
char **pres = NULL;
int row,column;
char *errmsg = NULL;
if(sqlite3_get_table(db,sql,&pres,&row,&column,&errmsg)!= SQLITE_OK)
{
fprintf(stderr,"sqlite3_get_table failed,__%d__,%d,%s\n",__LINE__,sqlite3_errcode(db),errmsg);
return;
}
printf("server do_server_login,row = %d,row =%d\n",row,column);
if (row <=0)
{
//用户不存在 ,提示 登录失败,账号/密码错误!
strcpy(msg->text,"登录失败,账号/密码错误!");
msg->status = 0; //登录成功状态码
ssize_t sret = send(newfd,msg,sizeof(msg_t),0);
if (sret<0)
{
ERR_MSG("server do_server_login send");
return;
}
} else
{
//用户存在
//查询数据库status=1 提示,请勿重复登录 status=0 登录成功,并将status置为1
printf("server do_server_login pres[5]=%s\n",pres[5]);
if (0==strcmp("1",pres[5]))
{
strcpy(msg->text,"登录失败,重复登录!!");
} else if (0==strcmp("0",pres[5]))
{
strcpy(msg->text,"登录成功!");
msg->status = 1; //登录成功状态码
//更新数据库status字段
char sql[128] = {0};
sprintf(sql, USER_UPDATE_FORMAT,msg->status, msg->name);
printf("server do_server_login sql = %s \n",sql);
char *errmsg = NULL;
if (sqlite3_exec(db,sql,NULL,NULL,&errmsg)!= SQLITE_OK)
{
fprintf(stderr,"server do_server_login failed,__%d__,%d,%s\n",__LINE__,sqlite3_errcode(db),errmsg);
msg->status = 0;
strcpy(msg->text,errmsg);
ssize_t sret = send(newfd,msg,sizeof(msg_t),0);
if (sret<0)
{
ERR_MSG("server do_server_login send");
return;
}
return;
}
printf("%s server do_server_login 用户设置退出状态成功\n",msg->name);
}
ssize_t sret = send(newfd,msg,sizeof(msg_t),0);
if (sret<0)
{
ERR_MSG("server do_server_login send");
return;
}
}
}
void do_server_exit(int newfd,msg_t* msg,sqlite3 * db)
{
char sql[128] = {0};
sprintf(sql, USER_UPDATE_FORMAT,msg->status, msg->name);
printf("server do_server_exit sql = %s \n",sql);
char *errmsg = NULL;
if (sqlite3_exec(db,sql,NULL,NULL,&errmsg)!= SQLITE_OK)
{
fprintf(stderr,"server do_server_exit failed,__%d__,%d,%s\n",__LINE__,sqlite3_errcode(db),errmsg);
return;
}
printf("%s server do_server_exit 用户设置退出状态成功\n",msg->name);
}
//服务器查询单词
void do_server_word_query(int newfd,msg_t* msg,sqlite3 * db)
{
char sql[256] = {0};
sprintf(sql,DICT_SELECT_FORMAT,msg->text) ;
printf("server do_server_word_query sql = %s \n",sql);
char **pres = NULL;
int row,column;
char *errmsg = NULL;
if(sqlite3_get_table(db,sql,&pres,&row,&column,&errmsg)!= SQLITE_OK)
{
fprintf(stderr,"server do_server_word_query failed,__%d__,%d,%s\n",__LINE__,sqlite3_errcode(db),errmsg);
return;
}
printf("server do_server_word_query,row = %d,row =%d\n",row,column);
if (row <=0)
{
//单词未查到 回复 NOT FOUND!!
printf("单词 %s 的释义未查到\n",msg->text);
strcpy(msg->text,"NOT FOUND!!");
ssize_t sret = send(newfd,msg,sizeof(msg_t),0);
if (sret<0)
{
ERR_MSG("server do_server_word_query send");
return;
}
} else
{
//单词查到
//回复单词+释义
strcat(msg->text,"\t");
strcat(msg->text,pres[3]);
printf("word and def to be sent to client==>%s\n",msg->text);
ssize_t sret = send(newfd,msg,sizeof(msg_t),0);
if (sret<0)
{
ERR_MSG("server do_server_word_query send");
return;
}
//发送成功后插入历史记录表
char sql[128] = {0};
char time_buf[128] = {0};
get_system_time(time_buf);
sprintf(sql,HISTORY_INSERT_FORMAT,msg->name,pres[2],pres[3],time_buf);
printf("do_server_word_query sql = %s \n",sql);
char *errmsg = NULL;
if (sqlite3_exec(db,sql,NULL,NULL,&errmsg)!= SQLITE_OK)
{
fprintf(stderr,"do_server_word_query insert history failed,__%d__,%d,%s\n",__LINE__,sqlite3_errcode(db),errmsg);
return;
}
printf("用户 %s,单词 %s 插入历史记录成功\n",msg->name,pres[2]);
}
}
//服务器查询历史记录
void do_server_history_query(int newfd,msg_t* msg,sqlite3 * db)
{
char **pres = NULL;
int row,column;
char *errmsg = NULL;
char sql[256] = {0};
sprintf(sql,HISTORY_SELECT_FORMAT,msg->name);
printf("server do_server_history_query sql = %s \n",sql);
if(sqlite3_get_table(db,sql,&pres,&row,&column,&errmsg)!= SQLITE_OK)
{
fprintf(stderr,"server do_server_history_query failed,__%d__,%d,%s\n",__LINE__,sqlite3_errcode(db),errmsg);
return;
}
printf("server do_server_history_query row = %d,column =%d\n",row,column);
int count = 0;
//row中不包含表头的一行,但是结果中有表头的内容,所以需要将表头的一行加上
for (int i = 0; i < row+1; i++)
{
for (int j = 0; j < column; j++)
{
if(i>0)
{
if (j>0)
{
bzero(msg->text,sizeof(msg->text));
sprintf(msg->text,"%s\t",pres[count]);
//发送给客户端
ssize_t sret = send(newfd,msg,sizeof(msg_t),0);
if (sret<0)
{
ERR_MSG("server do_server_history_query send");
return;
}
}
}
count++;
}
if(i > 0)
{
bzero(msg->text,sizeof(msg->text));
strcpy(msg->text,"\n");
//发送给客户端
ssize_t sret = send(newfd,msg,sizeof(msg_t),0);
if (sret<0)
{
ERR_MSG("server do_server_history_query send");
return;
}
}
}
//发送结束标识给客户端
strcpy(msg->text,"**OVER**\n");
//发送给客户端
ssize_t sret = send(newfd,msg,sizeof(msg_t),0);
if (sret<0)
{
ERR_MSG("server do_server_history_query send");
return;
}
}
//获取当前系统时间
void get_system_time(char *time_buf)
{
time_t cur_systime_milli = time(NULL);
struct tm *t = localtime(&cur_systime_milli);
sprintf(time_buf,"%4d-%02d-%02d %02d-%02d-%02d",
t->tm_year+1900,t->tm_mon+1,t->tm_mday,t->tm_hour,
t->tm_min,t->tm_sec);
}
//回收僵尸进程
void handler(int sig)
{
while (waitpid(-1,NULL,WNOHANG)>0); //循环回收僵尸进程
}
dict_client.c
#include "dictionary.h"
int cfd = 0;
char name[20] = {0};
int main(int argc, char const *argv[])
{
//捕获ctrl + c SIGINT信号,向用户发送退出请求
__sighandler_t s = signal(SIGINT,ctrlc_handler);
if (SIG_ERR == s)
{
printf("signal");
return -1;
}
/**********连接服务器部分 begin***************************/
//1. 创建流式套接字
cfd = socket(AF_INET,SOCK_STREAM,0);
if (cfd < 0)
{
ERR_MSG("socket");
return -1;
}
//填充要连接的服务器的ip和端口
struct sockaddr_in sin;
sin.sin_family = AF_INET; //必须填AF_INET
sin.sin_port = htons(PORT); //服务器绑定的端口号
sin.sin_addr.s_addr = inet_addr(IP); //服务器绑定的IP
//3. 连接服务器 int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
if(connect(cfd,(struct sockaddr *)&sin,sizeof(sin)) < 0)
{
ERR_MSG("connect");
return -1;
}
printf("connect success\n");
/*******************连接服务器部分 end***************************/
/*********************主逻辑部分 begin*********************************/
char choose = 0;
while (1)
{
msg_t msg;
print_1st_level_menu();
choose = getchar();
while (getchar()!=10);
switch (choose)
{
case '1':
do_register(cfd,&msg);
break;
case '2':
do_login(cfd,&msg);
break;
case '3':
goto END; //服务器提示断开连接
break;
default:
printf("输入错误,请重新输入\n");
}
//请按任意键清屏
printf("请按任意键清屏\n");
while (getchar()!=10);
system("clear");
}
/*********************主逻辑部分 end*********************************/
END:
close(cfd);
return 0;
}
void print_1st_level_menu()
{
printf("..................主界面.....................\n");
printf(".................1.注册.......................\n");
printf(".................2.登录.......................\n");
printf(".................3.退出.......................\n");
printf(".............................................\n");
printf("请输入你的选择>>");
}
void print_2nd_level_menu()
{
system("clear");
printf("........................................\n");
printf("............1.查单词.......................\n");
printf("............2.历史记录.......................\n");
printf("............3.返回上一级.......................\n");
printf(".............................................\n");
printf("请输入你的选择>>");
}
//客户端注册功能
void do_register(int cfd,msg_t* msg)
{
//1. 构造发送消息结构体
bzero(msg,sizeof(msg));
msg->type = 1; //注册
printf("请输入账户名>>>");
fscanf(stdin,"%s",msg->name);
while (getchar()!=10);
printf("请输入密码>>>");
fscanf(stdin,"%s",msg->passwd);
while (getchar()!=10);
//2. 发送消息给服务器
ssize_t sret = send(cfd,msg,sizeof(msg_t),0);
if (sret<0)
{
ERR_MSG("client do_register send");
return;
}
//3.接收服务器返回的消息
ssize_t rret = recv(cfd,msg,sizeof(msg_t),0);
if (rret<0)
{
ERR_MSG("client do_register recv");
return;
}
//4.返回的消息判断是否注册成功
printf("%s\n",msg->text);
}
//客户端登录功能
void do_login(int cfd,msg_t *msg)
{
//1. 构造发送消息结构体
bzero(msg,sizeof(msg_t));
msg->type = 2; //登录
printf("请输入登录账户名>>>");
fscanf(stdin,"%s",msg->name);
while (getchar()!=10);
printf("请输入登录密码>>>");
fscanf(stdin,"%s",msg->passwd);
while (getchar()!=10);
//2. 发送消息给服务器
ssize_t sret = send(cfd,msg,sizeof(msg_t),0);
if (sret<0)
{
ERR_MSG("client do_login send");
return;
}
//3.接收服务器返回的消息
ssize_t rret = recv(cfd,msg,sizeof(msg_t),0);
if (rret<0)
{
ERR_MSG("client do_login recv");
return;
}
//根据返回消息结构体判断登录是否成功
//失败,提示错误原因,返回一级菜单==> 继续往下走
if (0==msg->status)
{
printf("%s\n",msg->text);
return;
} else if (1==msg->status)
{
strcpy(name,msg->name);
//成功进入二级菜单
char choose = 0;
while (1)
{
print_2nd_level_menu();
choose = getchar();
while (getchar()!=10);
switch (choose)
{
case '1': //==> 4
do_query_word(cfd,msg);
break;
case '2': //==> 5
do_query_history(cfd,msg);
break;
case '3': //6.返回上一级
//给服务发送用户退出标识
set_user_exit_status(cfd,msg);
return;
default:
printf("输入错误,请重新输入\n");
}
//请按任意键清屏
printf("请按任意键清屏\n");
while (getchar()!=10);
system("clear");
}
}
}
void set_user_exit_status(int cfd,msg_t *msg)
{
//把登录状态位置为0
msg->status = 0;
msg->type = 3;
//2. 发送消息给服务器
ssize_t sret = send(cfd,msg,sizeof(msg_t),0);
if (sret<0)
{
ERR_MSG("client set_user_exit_status send");
return;
}
/*
//3.接收服务器返回的消息
ssize_t rret = recv(cfd,&msg,sizeof(msg),0);
if (rret<0)
{
ERR_MSG("client do_login recv");
return;
}
*/
}
//客户端查单词
void do_query_word(int cfd,msg_t *msg)
{
while (1)
{
bzero(msg->text,sizeof(msg->text));
printf("请输入单词(输入#退出)>>>");
fscanf(stdin,"%s",msg->text);
while (getchar()!=10);
if (strcmp(msg->text,"#")==0)
{
return;
}
msg->type = 4;
//2. 发送消息给服务器
ssize_t sret = send(cfd,msg,sizeof(msg_t),0);
if (sret<0)
{
ERR_MSG("client do_login send");
return;
}
//3.接收服务器返回的消息
bzero(msg->text,sizeof(msg->text));
ssize_t rret = recv(cfd,msg,sizeof(msg_t),0);
if (rret<0)
{
ERR_MSG("client do_login recv");
return;
}
printf("%s\n",msg->text);
}
}
//客户端查历史记录
void do_query_history(int cfd,msg_t *msg)
{
msg->type = 5;
//2. 发送消息给服务器
ssize_t sret = send(cfd,msg,sizeof(msg_t),0);
if (sret<0)
{
ERR_MSG("client do_query_history send");
return;
}
//3.循环接收服务器返回的消息
while (1)
{
ssize_t rret = recv(cfd,msg,sizeof(msg_t),0);
if (rret<0)
{
ERR_MSG("client do_login recv");
return;
}
printf("%s",msg->text);
if (0==strcmp("**OVER**\n",msg->text))
{
break;
}
}
}
void ctrlc_handler(int sig)
{
//把登录状态位置为0
msg_t msg;
bzero(&msg,sizeof(msg_t));
msg.status = 0;
msg.type = 3;
strcpy(msg.name,name);
//2. 发送消息给服务器
ssize_t sret = send(cfd,&msg,sizeof(msg_t),0);
if (sret<0)
{
ERR_MSG("client ctrlc_handler send");
return;
}
printf("ctrl+c signal sent,name=%s\n",msg.name);
kill(getpid(),9);
}
测试过程:
- 单词数据导入及表的创建 (见 dict.db)
数据库结构
词典数据库中内容
注:首次初始化成功之后,后续关闭服务器无需再重新导入单词及初始化其他表格
- 客户端注册功能
- 注册成功的情况
注册前:
注册后: (注册完毕,用户处于未登录状态 status = 0)
按下清屏键后
此时数据库中
2)重复注册的情况
- 登录功能
1)登录成功后跳到二级菜单界面,同时status置为1
2)此时再次启动一个client,输入同样的用户名和密码,提示已登录,请勿重复登录
数据库中的status字段不受影响,仍为1,处于登录状态
在重复登录界面,点击3正常退出或者按ctrl+c强制退出,均对已登录的用户的status无影响
3)在已经登录成功的用户界面,按返回上一级,用户退出,退出状态status被置为0
在登录成功的二级菜单界面按ctrl+c强制结束客户端,已登录用户的status会被置为0
4) 登录密码或者姓名错误的情况
正确用户名和密码:
重新登录,输入正确用户名和密码,登录成功
- 点击1,查询单词
输入一个不存在的单词,显示未查询到
输入一个存在的单词,显示单词+释义 (同时插入查询历史表格中)
按下 # 键的效果
- 查询历史记录
再次查询一个单词
再次查看历史
- 按下返回上一级,用户退出,登录状态置0
按下清屏键后
查看数据库status字段,用户退出
- 同时登录多用户,每个用户查询不同的单词,查看历史记录
查看数据库
两个客户端退出登录