1,简介:
1,在线词典功能,分为客户端和服务器端
2,客户端有三个模块:注册、登录、查询(查询单词、查询历史记录)
3,服务器端要实现多并发服务器,这里采用多进程并发服务器:注册、登录、查询(查询单词、查询历史记录)
4,用户分为普通用户和管理员用户:管理员用户可以查询所有普通用户的历史记录,还可以看到词库中未定义的单词查询记录,普通用户只能查到自己的,词库中中有定义的记录
5,客户端和服务器通信只要通过一个信息结构体实现:
typedef struct{
int flag; //1:usr or 2:root
int type;
char name[N];
char data[256]; //password or word
}MSG;
6,服务器端有单词词库
7,服务器端有两张数据库表格,一张用来存放用户信息,一张用来存放用户单词查询记录
8,多进程并发服务器要做僵尸进程处理
2,框架图
2.1,客户端框架
2.1,服务器端框架
3,代码
3.1,客户端代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define N 20
#define R 1
#define L 2
#define Q 3
#define H 4
#define USR 1
#define ROOT 2
typedef struct{
int flag; //1:usr or 2:root
int type;
char name[N];
char data[256]; //password or word
}MSG;
int do_client(int sockfd);
int do_register(int connectfd,MSG *msg);
int do_login(int connectfd,MSG *msg);
int do_query(int connectfd,MSG *msg);
int do_history(int connectfd,MSG *msg);
int main(int argc, const char *argv[])
{
int serverfd;
struct sockaddr_in sin;
if(argc != 3)
{
printf("usage: %s <server_ip> <server_port>\n",argv[0]);
exit(0);
}
//创建流式套接字
if((serverfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
{
perror("socket");
exit(-1);
}
//填充协议地址结构体
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(atoi(argv[2]));
sin.sin_addr.s_addr = inet_addr(argv[1]);
//连接服务器
if(connect(serverfd,(struct sockaddr *)&sin,sizeof(sin)) < 0)
{
perror("connect");
goto _error1;
}
do_client(serverfd);
_error1:
close(serverfd);
return 0;
}
int do_client(int sockfd)
{
MSG msg;
bzero(&msg,sizeof(msg));
msg.flag = -1;
while(msg.flag < 0)
{
printf("Please choose permission [1:usr 2:root]");
if(scanf("%d",&msg.flag) == 0)
msg.flag = -1;
while(getchar() != '\n');
if(msg.flag != 1 && msg.flag != 2)
msg.flag = -1;
}
/* 注册登录模块 */
while(1)
{
msg.type = -1;
while(msg.type < 0)
{
printf("********************************************\n");
printf("*1:register 2:login 3:quite *\n");
printf("********************************************\n");
printf("PLease choose:");
if(scanf("%d",&msg.type) == 0)
msg.type = -1;
while(getchar() != '\n');
}
switch (msg.type)
{
case R:
{
do_register(sockfd,&msg);
break;
}
case L:
{
if(do_login(sockfd,&msg) == 1)
{
goto _NEXT;
}
break;
}
case 3:
{
close(sockfd);
printf("Exit the online dictionary program\n");
exit(0);
break;
}
default:
printf("Error cmd.\n");
}
}
_NEXT:
/* 查询模块 */
while(1)
{
msg.type = -1;
while(msg.type < 0)
{
printf("********************************************\n");
printf("*1:query_word 2:history_record 3:quite *\n");
printf("********************************************\n");
printf("Please choose:");
if(scanf("%d",&msg.type) == 0)
msg.type = -1;
while(getchar() != '\n');
}
switch (msg.type)
{
case 1:
{
msg.type = Q;
do_query(sockfd,&msg);
break;
}
case 2:
{
msg.type = H;
do_history(sockfd,&msg);
break;
}
case 3:
{
close(sockfd);
printf("Exit the online dictionary program\n");
exit(0);
break ;
}
default:
printf("Error cmd.\n");
}
}
}
int do_register(int connectfd,MSG *msg)
{
char str[20];
int flag;
while(1)
{
printf("PLease input name:");
if(scanf("%s",msg->name)){};
while(getchar() != '\n');
flag = -1;
while(flag < 0)
{
printf("PLease input password:");
if(scanf("%s",str)){};
while(getchar() != '\n');
printf("Please confirm password:");
if(scanf("%s",msg->data)){};
while(getchar() != '\n');
if(strncmp(msg->data,str,strlen(str)) == 0)
{
flag = 1;
}
else
{
printf("Entered passwords differ,Please re-enter them.\n");
}
}
if(send(connectfd,msg,sizeof(MSG),0) < 0)
{
perror("do_register fail to send");
return -1;
}
if(recv(connectfd,msg,sizeof(MSG),0) < 0)
{
perror("do_register fail to recv");
return -1;
}
if(strncasecmp(msg->data,"ok",strlen("ok")) == 0)
{
printf("register success.\n");
return 1;
break;
}
else
{
printf("%s\n",msg->data);
printf("re-register or cancel [1:re-register 2:cancel]");
int tmp;
if(scanf("%d",&tmp)){};
while(getchar() != '\n');
if(tmp == 2)
break;
}
}
return 0;
}
int do_login(int connectfd,MSG *msg)
{
while(1)
{
printf("PLease input name:");
if(scanf("%s",msg->name)){};
while(getchar() != '\n');
printf("PLease input password:");
if(scanf("%s",msg->data)){};
while(getchar() != '\n');
if(send(connectfd,msg,sizeof(MSG),0) < 0)
{
perror("do_login fail to send");
return -1;
}
if(recv(connectfd,msg,sizeof(MSG),0) < 0)
{
perror("do_login fail to recv");
return -1;
}
if(strncasecmp(msg->data,"ok",strlen("ok")) == 0)
{
printf("login success.\n");
return 1;
break;
}
else
{
printf("%s\n",msg->data);
printf("re-login or cancel [1:re-login 2:cancel]");
int tmp;
if(scanf("%d",&tmp)){};
while(getchar() != '\n');
if(tmp == 2)
break;
}
}
return 0;
}
int do_query(int connectfd,MSG *msg)
{
while(1)
{
printf("Please input query_word('#' exit):");
if(scanf("%s",msg->data)){};
while(getchar() != '\n');
/* 返回上级菜单 */
if(strncmp(msg->data,"#",strlen("#")) == 0)
{
break;
}
/* 把含有有查询单词的消息结构体发送给服务器 */
if(send(connectfd,msg,sizeof(MSG),0) < 0)
{
perror("do_query fail to send.");
return -1;
}
/* 阻塞接受 */
if(recv(connectfd,msg,sizeof(MSG),0) < 0)
{
perror("do_query fail to recv.");
return -1;
}
/* 处理从服务器接受的信息 */
printf("%s",msg->data);
}
return 0;
}
int do_history(int connectfd,MSG *msg)
{
if(send(connectfd,msg,sizeof(MSG),0) < 0)
{
perror("do_history fail to send");
return -1;
}
while(1)
{
if(recv(connectfd,msg,sizeof(MSG),0) < 0)
{
perror("do_history fail to recv");
return -1;
}
#if 1
if(msg->data[0] == '\0')
{
break;
}
#else
/* 这种方法是错误的,strlen("\0")是错误的 */
if(strncmp(msg->data,"\0",strlen("\0")) == 0)
{
break;
}
#endif
printf("%s\n",msg->data);
}
return 0;
}
3.2,服务器端代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <netinet/in.h>
#include <sqlite3.h>
#include <signal.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <errno.h>
#include <time.h>
#define SERV_PORT 5001
#define BACKLOG 5
#define N 20
#define R 1
#define L 2
#define Q 3
#define H 4
#define USR 1
#define ROOT 2
#define DATABASE "table.db"
typedef struct{
int flag; //1:usr or 2:root
int type;
char name[N];
char data[256]; //password or word
}MSG;
typedef struct{//回调函数传参
int fd;
MSG msg;
}HIS_MSG;
int do_create_table(sqlite3 *db);//判断表格是否存在,不存在则创建
int do_client_handle(int newfd,struct sockaddr_in cin,sqlite3 *db);//子进程处理客户端词典请求操作
int 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);
const char *get_date(char * date);//获取本地时间
int do_searchworld(MSG *msg);//查询词典数据库文件dict.txt中的单词释义
int history_callback(void *his_msg, int f_num, char **f_value, char **f_name);//历史记录查询回调函数
int main(int argc, const char *argv[])
{
sqlite3 *db;
int socketfd;
struct sockaddr_in sin;
/* 数据库存在则打开,不存在则创建 */
if(sqlite3_open(DATABASE,&db) != SQLITE_OK)
{
printf("%s\n",sqlite3_errmsg(db));
return -1;
}
else
{
printf("open database:%s success.\n",DATABASE);
}
/* 表格不存在则创建数据库表格 */
if(do_create_table(db) < 0)
{
goto _error1;//创建数据库表格失败
}
/* 创建流式套接字 */
if((socketfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
{
perror("socket");
goto _error1;
}
/* 允许绑定地址快速重用 */
int b_reuse = 1;
setsockopt(socketfd,SOL_SOCKET,SO_REUSEADDR,&b_reuse,sizeof(int));
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);
sin.sin_addr.s_addr = htonl(INADDR_ANY);// 让服务器可以绑定在任意的IP上
/* 绑定 */
if(bind(socketfd,(struct sockaddr *)&sin,sizeof(sin)) < 0)
{
perror("bind");
goto _error2;
}
/* 将套接字设置为监听模式 */
if(listen(socketfd,BACKLOG) < 0)
{
perror("listen");
goto _error2;
}
signal(SIGCHLD,SIG_IGN) ; //处理僵尸进程
pid_t pid;
int status;
int newfd;
struct sockaddr_in cin;
socklen_t cin_add_len = sizeof(cin);
bzero(&cin,sizeof(cin));
while(1)
{
/* 阻塞等待客户端连接 */
if((newfd = accept(socketfd,(struct sockaddr *)&cin,&cin_add_len)) < 0)
{
perror("acept");
goto _error2;
}
/* 如果连接从成功,创建子进程 */
if((pid = fork()) < 0)
{
perror("fork");
goto _error3;
}
else if(pid == 0)//子进程
{
close(socketfd);
do_client_handle(newfd,cin,db);//子进程处理客户端词典请求操作
}
else //父进程
{
close(newfd);
waitpid(-1, &status, WNOHANG);//以非阻塞方式回收当前进程的任意一个子进程
}
}
_error3:
close(newfd);
_error2:
close(socketfd);
_error1:
sqlite3_close(db);
return 0;
}
int do_create_table(sqlite3 *db)
{
char sql[128];
char **resultp;
int nrow;
int ncolumn;
char *errmsg;
/* 判断表格usr是否存在,不存在则创建 */
sprintf(sql,"select * from sqlite_master where type='table' and name='usr';");
sqlite3_get_table(db,sql,&resultp,&nrow,&ncolumn,&errmsg);
if(nrow == 0)
{
sprintf(sql,"create table %s(name text primary key,permission integer,password text);","usr");
if(sqlite3_exec(db,sql,NULL,NULL,&errmsg) != SQLITE_OK)
{
printf("create table:%s %s\n","usr",errmsg);
return -1;
}
else
{
printf("create table:%s success.\n","usr");
}
}
else
{
printf("table:%s is OK.\n","usr");
}
/* 判断表格record是否存在,不存在则创建 */
sprintf(sql,"select * from sqlite_master where type='table' and name='record';");
sqlite3_get_table(db,sql,&resultp,&nrow,&ncolumn,&errmsg);
if(nrow == 0)
{
sprintf(sql,"create table %s(name text,date text,word text,is_undefine Integer);","record");
if(sqlite3_exec(db,sql,NULL,NULL,&errmsg) != SQLITE_OK)
{
printf("create table:%s %s\n","record",errmsg);
return -1;
}
else
{
printf("create table:%s success.\n","record");
}
}
else
{
printf("table:%s is OK.\n","record");
}
return 0;
}
int do_client_handle(int newfd,struct sockaddr_in cin,sqlite3 *db)//子进程处理客户端词典请求操作
{
MSG msg;
int ret = -1;
char ipv4_addr[16];
inet_ntop(AF_INET,(void *)&cin.sin_addr.s_addr,ipv4_addr,sizeof(cin));
printf("client(ip:%s port:%d) is connected.\n",ipv4_addr,ntohs(cin.sin_port));
while(1)
{
bzero(&msg,sizeof(msg));
do{
ret = recv(newfd,&msg,sizeof(msg),0);
}while(ret < 0 && errno == EINTR);//阻塞读取
if(ret < 0)
{
perror("recv");
close(newfd);
return -1;
}
if(ret == 0) //客户端已关闭
{
goto _exithandle;
break;
}
switch (msg.type)
{
case R:
do_register(newfd,msg,db);
break;
case L:
do_login(newfd,msg,db);
break;
case Q:
do_query(newfd,msg,db);
break;
case H:
do_history(newfd,msg,db);
break;
default:
printf("Invalid data msg.\n");
}
}
_exithandle:
printf("client(ip:%s port:%d) exit.\n",ipv4_addr,ntohs(cin.sin_port));
close(newfd);
exit(0);
return 0;
}
int do_register(int acceptfd,MSG msg,sqlite3 *db)
{
char sql[128];
char *errmsg;
/* 将用户信息插入数据库表格usr(名字(主键),权限,密码) */
sprintf(sql,"insert into usr values('%s',%d,'%s');",msg.name,msg.flag,msg.data);
if(sqlite3_exec(db,sql,NULL,NULL,&errmsg) != SQLITE_OK)
{
printf("do_register %s\n",errmsg);
strcpy(msg.data,"the name already exit.");//插入失败说明用户名已存在
}
else
{
printf("insert usr success.\n");
strcpy(msg.data,"OK");
}
if(send(acceptfd,&msg,sizeof(MSG),0) < 0)
{
perror("do_register fail to send");
return -1;
}
return 0;
}
int do_login(int acceptfd,MSG msg,sqlite3 *db)
{
char sql[128];
char *errmsg;
char **resultp;
int nrow;
int ncolumn;
sprintf(sql,"select * from usr where name='%s';",msg.name);
if(sqlite3_get_table(db,sql,&resultp,&nrow,&ncolumn,&errmsg) < 0)
{
printf("do_register %s",errmsg);
return -1;
}
if(nrow == 0)
{
printf("login name:%s does not exit.\n",msg.name);
strcpy(msg.data,"the name does not exit.");
}
else if(nrow == 1)//用户存在
{
if(msg.flag != atoi(resultp[ncolumn+1]))
{
printf("login usr permissions not match.\n");
strcpy(msg.data,"usr permission not match.");
}
else if(msg.flag == atoi(resultp[ncolumn+1]))//用户和权限匹配
{
if(strncmp(msg.data,resultp[ncolumn+2],strlen(msg.data)) != 0)
{
printf("login password is wrony.\n");
strcpy(msg.data,"password is wrong.");
}
else//登录成功
{
printf("login success.\n");
strcpy(msg.data,"OK");
}
}
}
if(send(acceptfd,&msg,sizeof(msg),0) < 0)
{
perror("do_login fail to send");
return -1;
}
return 0;
}
int do_query(int acceptfd,MSG msg,sqlite3 *db)
{
char date[20];
int found;
char word[20];//保存要查询的单词
int is_undefine;
char sql[128];
char *errmsg;
/* 获取本地时间 */
get_date(date);
/* 查询词典数据库文件dict.txt */
strcpy(word,msg.data);
found = (do_searchworld(&msg));
if(found == 1)
{
is_undefine = 0;
}
if(found == 0)
{
is_undefine = 1;
}
/* 将查询信息发送给客户端:释义|The meaning of the word could not be found|错误信息 */
if(send(acceptfd,&msg,sizeof(MSG),0) < 0)
{
perror("do_query fail to send.");
return -1;
}
/* 一个单词查询完毕,服务端打印提示信息 */
printf("Word:%s query finished.\n",word);
/* 将查询信息插入数据库表格record(名字,日期时间,单词,是否未定义) */
sprintf(sql,"insert into record values('%s','%s','%s',%d);",msg.name,date,word,is_undefine);
if(sqlite3_exec(db,sql,NULL,NULL,&errmsg) != SQLITE_OK)
{
printf("do_query %s\n",errmsg);
return -1;
}
return 0;
}
int do_history(int acceptfd,MSG msg,sqlite3 *db)
{
HIS_MSG his_msg;
char sql[128];
char *errmsg;
/* 装填回调函数传参结构体 */
his_msg.fd = acceptfd;
his_msg.msg = msg;
/* 装填sql语句 */
if(msg.flag == USR)
{
sprintf(sql,"select * from record where name='%s' and is_undefine=%d;",msg.name,0);
}
if(msg.flag == ROOT)
{
sprintf(sql,"select * from record;");
}
/* 查询历史记录,执行回调函数 */
if(sqlite3_exec(db,sql,history_callback,(void *)&his_msg,&errmsg) != SQLITE_OK)
{
printf("do_history %s\n",errmsg);
return -1;
}
else
{
printf("Query history record successful.\n");
/* 历史记录查询完了,给客户端发送查询完毕标志“\0” */
strcpy(msg.data,"\0");
if(send(acceptfd,&msg,sizeof(MSG),0) < 0)
{
perror("do_history fail to send");
return -1;
}
return 1;
}
return 0;
}
const char *get_date(char * date)//获取本地时间
{
time_t t;
struct tm tp;
time(&t);
tp = *localtime(&t);
sprintf(date,"%04d-%02d-%02d %02d:%02d:%02d",tp.tm_year+1900,tp.tm_mon+1,tp.tm_mday,tp.tm_hour,tp.tm_min,tp.tm_sec);
return date;
}
int do_searchworld(MSG *msg)//查询词典数据库文件dict.txt中的单词释义
{
FILE *fp;
int len;
char str[512];
int result;
char *p;
/* 打印要查询的单词信息 */
len = strlen(msg->data);
printf("%s , %d\n",msg->data,len);
if((fp = fopen("dict.txt","r")) == NULL)
{
perror("do_searchworld fail to fopen");
strcpy(msg->data,"can't open online dictionary database dict.txt.\n");
return -1;
}
/* 从流中读取一行 */
while(fgets(str,511,fp) != NULL)
{
result = strncmp(str,msg->data,len);
if(result < 0)
{
continue ;
}
if(result > 0 || str[len] != ' ')
{
strcpy(msg->data,"The meaning of the word could not be found.\n");
break;
}
/* 找到了查询的单词 */
p = str +len;
while(*p == ' ')//去掉单词释义前的空格
{
p++;
}
strcpy(msg->data,p);
fclose(fp);
return 1;
}
fclose(fp);
return 0;
}
int history_callback(void *his_msg, int f_num, char **f_value, char **f_name)
{
HIS_MSG *h_msg = (HIS_MSG *)his_msg;
/*create table %s(name text,date text,word text,is_undefine Integer);","record");*/
if(h_msg->msg.flag == USR)
{
sprintf(h_msg->msg.data,"%s %s",f_value[1],f_value[2]);
printf("%s\n",h_msg->msg.data);
}
if(h_msg->msg.flag == ROOT)
{
sprintf(h_msg->msg.data,"%-20s%s %-15s%s",f_value[0],f_value[1],f_value[2],f_value[3]);
}
if(send(h_msg->fd,&h_msg->msg,sizeof(MSG),0) < 0)
{
perror("history_callback fail to send");
return -1;
}
return 0;
}