服务器:
head.h
#ifndef _HEAD_H_
#define _HEAD_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sqlite3.h>
#include <signal.h>
#include <time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
//消息的类型
#define USER_REGISTER 10
#define USER_LOGIN 20
#define USER_WORD 30
#define USER_SUCCESS 40
#define USER_FAILURE 50
//__attribute__((__packed__))
//作用:告诉编译器在编译的过程中取消优化对齐。
// 方便我们在发送和接收数据的时候一个字节一个字节的排列
typedef struct
{
char _username[25]; //用户名
char _password[25]; //密码
}__attribute__((__packed__))user_t;
typedef struct
{
int type; //消息类型
int size; //消息大小
union
{
user_t uinfo; //用户信息
char _word[1024];
}content;
//客户端填单词,服务端填写单词解释
#define word content._word
#define username content.uinfo._username
#define password content.uinfo._password
}__attribute__((__packed__))mhead_t;
//'\'表示多行链接上一行表示, #deifne ....do...while(0);
//表示封装成独立的语法单元,防止被语法错误。
//注意:'\'之后不要留空格,要不然编译会有警告
extern int do_client(int sockfd,sqlite3 *pdb);
#define EXEC_SQL(db,sql,errmsg) do{\
if(sqlite3_exec(db,sql,NULL,NULL,&errmsg) < 0)\
{\
fprintf(stderr,"sqlite3 execl [%s] error : %s.\n",sql,errmsg);\
exit(EXIT_FAILURE);\
}\
}while(0);
#endif
server.c
#include "head.h"
void signal_handler(int signum)//
{
waitpid(-1,NULL,WNOHANG);
return;
}
int init_tcp(char *ip,char *port)//建立套接字,绑定ip和端口号
{
int sockfd;
struct sockaddr_in server_addr;
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
{
perror("Fail to socket");
exit(EXIT_FAILURE);
}
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(port));
server_addr.sin_addr.s_addr = inet_addr(ip);
if(bind(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0)
{
perror("Fail to bind");
exit(EXIT_FAILURE);
}
listen(sockfd,5);
printf("listen....\n");
return sockfd;
}
//.server ip port db
//数据库中已经手动创建了2个表:user_table,word_table
//注:由于我们后面函数要传承,故这里的const应该去掉
int main(int argc, char *argv[])
{
int pid;
sqlite3 *pdb;
int listenfd,connect_fd;
int addr_len = sizeof(struct sockaddr);
struct sockaddr_in peer_addr;
if(argc < 4)
{
fprintf(stderr,"Usage : %s ip port system.db.\n",argv[0]);
exit(EXIT_FAILURE);
}
//探测子进程的改变状态,回收僵尸态子进程
if(signal(SIGCHLD,signal_handler) == SIG_ERR)
{
perror("Fail to signal");
exit(EXIT_FAILURE);
}
if(sqlite3_open(argv[3],&pdb) != SQLITE_OK)
{
fprintf(stderr,"sqlite3 open %s : %s.\n",argv[3],sqlite3_errmsg(pdb));
exit(EXIT_FAILURE);
}
//初始化tcp连接,得到监听套接字
listenfd = init_tcp(argv[1],argv[2]);
//提取客户段的链接请求,创建子进程和客户端交互
while(1)
{
if((connect_fd = accept(listenfd,(struct sockaddr *)&peer_addr,&addr_len)) < 0)
{
perror("Fail to accept");
exit(EXIT_FAILURE);
}
if((pid = fork()) < 0)
{
perror("Fail to fork");
exit(EXIT_FAILURE);
}
//创建子进程处理客户端的请求
if(pid == 0){
close(listenfd);
do_client(connect_fd,pdb);
}
close(connect_fd);
}
exit(EXIT_SUCCESS);
}
do_client.c
#include "head.h"
int do_register(int sockfd,sqlite3 *pdb,char *_username,char *_password)
{
char *errmsg;
char buf[1024];
char **dbresult;
int nrow = 0,ncolumn = 0;
char sql[1024] = {0};
mhead_t *head = (mhead_t *)buf;
sprintf(sql,"select * from user_table where NAME='%s';",_username);
if(sqlite3_get_table(pdb,sql,&dbresult,&nrow,&ncolumn,&errmsg) != 0)
{
fprintf(stderr,"sqlite3 get table error : %s.\n",errmsg);
exit(EXIT_FAILURE);
}
//没有这样的用户名
if(nrow == 0)
{
//录入数据库
bzero(sql,sizeof(sql));
sprintf(sql,"insert into user_table values('%s','%s');",_username,_password);
EXEC_SQL(pdb,sql,errmsg);
printf("ok ........\n");
head->type = USER_SUCCESS;
if(send(sockfd,buf,sizeof(mhead_t),0) < 0)
{
perror("Fail to send");
exit(EXIT_FAILURE);
}
//注册失败,用户名存在
}else{
head->type = USER_FAILURE;
if(send(sockfd,buf,sizeof(mhead_t),0) < 0)
{
perror("Fail to send");
exit(EXIT_FAILURE);
}
//表示未知
printf("???????\n");
}
//插入到数据库之后,释放dbresult结果
sqlite3_free_table(dbresult);
return 0;
}
int do_login(int sockfd,sqlite3 *pdb,char *_username,char *_password)
{
char *errmsg;
char buf[1024];
char **dbresult;
char sql[1024] ={0};
int nrow = 0,ncolumn= 0;
mhead_t *head = (mhead_t *)buf;
sprintf(sql,"select * from user_table where NAME = '%s';",_username);
if(sqlite3_get_table(pdb,sql,&dbresult,&nrow,&ncolumn,&errmsg) != 0)
{
fprintf(stderr,"sqlite3 get table error404 : %s.\n",errmsg);
exit(EXIT_FAILURE);
}
// 用户名存在
if(nrow == 1)
{
bzero(sql,sizeof(sql));
sprintf(sql,"select * from user_table where PASSWORD = '%s';",_password);//确认用户名和密码是否匹配
if(sqlite3_get_table(pdb,sql,&dbresult,&nrow,&ncolumn,&errmsg) != 0)
{
perror("sqlite3_get_table");
exit(EXIT_FAILURE);
}else
{
head->type = USER_SUCCESS;
if(send(sockfd,buf,sizeof(mhead_t),0) < 0)
{
perror("Fail to send");
exit(EXIT_FAILURE);
}
printf("..........ok\n");
}
}
else{
head->type = USER_FAILURE;
if(send(sockfd,buf,sizeof(mhead_t),0) < 0)
{
perror("Fail to send");
exit(EXIT_FAILURE);
}
printf("??????\n");
}
sqlite3_free_table(dbresult);//插入到数据库之后,释放dbresult结果
return 0;
}
int do_query(int sockfd,sqlite3 *pdb,char *_word)
{
int i,j;
char *errmsg;
int count = 0;
char buf[1024];//定义历史记录缓存区
char sql[1024] = {0};
char **dbresult;
int nrow = 0, ncolumn = 0;
mhead_t *head = (mhead_t *)buf;
sprintf(sql,"select * from dict_table where word ='%s';",_word);
if(sqlite3_get_table(pdb,sql,&dbresult,&nrow,&ncolumn,&errmsg)!=0)
{
fprintf(stderr,"sqlite3 get table error :%s.\n",errmsg);
exit(EXIT_FAILURE);
}
if(nrow == 1)
{
bzero(sql,sizeof(sql));
sprintf(sql,"select * from dict_table;");
if(sqlite3_get_table(pdb,sql,&dbresult,&nrow,&ncolumn,&errmsg) != SQLITE_OK)
{
fprintf(stderr,"sqlite3_get_table %s\n",errmsg);
exit(EXIT_FAILURE);
}
for(i = 0;i <= nrow;i++)
{
for(j = 0;j < ncolumn;j++)
{
if(strcmp(dbresult[count],_word) == 0)
{
strcpy(head->word,dbresult[count]);
strcat(head->word, " ");
strcat(head->word,dbresult[count+1]);
printf("%s\n",dbresult[count + 1]);
i = nrow +1;
break;
}
count ++;
}
}
head->type = USER_SUCCESS;
if(send(sockfd,buf,sizeof(mhead_t),0)<0)
{
perror("Fail to send");
bzero(sql,sizeof(sql));
exit(EXIT_FAILURE);
}
}
else
{
head->type = USER_FAILURE;
if(send(sockfd,buf,sizeof(mhead_t),0)<0 )
{
perror("Fail to send");
exit(EXIT_FAILURE);
}
printf("?????\n");
}
sqlite3_free_table(dbresult);
}
int do_client(int sockfd,sqlite3 *pdb)
{
int n;
int count = 0;
char buf[1024];
mhead_t *head = (mhead_t *)buf;
while(1)
{
count = 0;
//接收协议头
while(1)
{
n = recv(sockfd,buf + count,sizeof(mhead_t) - count,0);
if(n <= 0){
exit(EXIT_FAILURE);
}
count += n;
printf("count : %d mhead_t : %ld\n",count,sizeof(mhead_t));
if(count == sizeof(mhead_t))
break;
}
switch(head->type)
{
case USER_REGISTER:
do_register(sockfd,pdb,head->username,head->password);
break;
case USER_LOGIN:
do_login(sockfd,pdb,head->username,head->password);
break;
case USER_WORD:
do_query(sockfd,pdb,head->word);
break;
defalut:
exit(EXIT_SUCCESS);
}
}
return 0;
}
客户端
head.h
#ifndef _HEAD_H_
#define _HEAD_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sqlite3.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//消息的类型
#define USER_REGISTER 10
#define USER_LOGIN 20
#define USER_WORD 30
#define USER_SUCCESS 40
#define USER_FAILURE 50
//__attribute__((__packed__))
//作用:告诉编译器在编译的过程中取消优化对齐。
// 方便我们在发送和接收数据的时候一个字节一个字节的排列
typedef struct
{
char _username[25]; //用户名
char _password[25]; //密码
}__attribute__((__packed__))user_t;
typedef struct
{
int type; //消息类型
int size; //消息大小
union
{
user_t uinfo; //用户信息
char _word[1024];
}content;
//客户端填单词,服务端填写单词解释
#define word content._word
#define username content.uinfo._username
#define password content.uinfo._password
}__attribute__((__packed__))mhead_t;
extern void help_info2();
extern int do_task(int sockfd);
extern int do_query(int sockfd);
extern int do_task2(int sockfd);
//'\'表示多行链接上一行表示, #deifne ....do...while(0);
//表示封装成独立的语法单元,防止被语法错误。
//注意:'\'之后不要留空格,要不然编译会有警告
#define EXEC_SQL(db,sql,errmsg) do{\
if(sqlite3_exec(db,sql,NULL,NULL,&errmsg) < 0)\
{\
fprintf(stderr,"sqlite3 execl [%s] error : %s.\n",sql,errmsg);\
exit(EXIT_FAILURE);\
}\
}while(0);
#endif
client.c
#include "head.h"
//用户提示界面1
void help_info1()
{
printf("\t-----------------------------------------------\n");
printf("\t| HENRY 在线辞典 |\n");
printf("\t|版本:0.0.1 |\n");
printf("\t|作者:HENRY老师 |\n");
printf("\t|功能: |\n");
printf("\t| [1] 登录 |\n");
printf("\t| [2] 注册 |\n");
printf("\t| [3] 退出 |\n");
printf("\t|注意:用户只有登录成功后才能进入查单词界面 |\n");
printf("\t------------------------------------------------\n");
return;
}
void help_info2()
{
printf("\t-----------------------------------------------\n");
printf("\t| HENRY 在线辞典 |\n");
printf("\t|版本:0.0.1 |\n");
printf("\t|作者:HENRY老师 |\n");
printf("\t|功能: |\n");
printf("\t| [1] 查询单词 |\n");
printf("\t| [2] 查询历史记录 |\n");
printf("\t| [3] 退出查询系统 |\n");
printf("\t|注意:用户只有登录成功后才能进入查单词界面 |\n");
printf("\t----------------------------------------------- \n");
return;
}
//用户输入指令,供大家选择
enum{
LOGIN = 1, //登陆
REGISTER = 2, //注册
QUIT = 3, //退出
QUERY = 1, //查询单词
HISTORY = 2, //查询历史
};
int init_tcp(char *ip,char *port)
{
int sockfd;
struct sockaddr_in server_addr;
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
{
perror("Fail to socket");
exit(EXIT_FAILURE);
}
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(port));
server_addr.sin_addr.s_addr = inet_addr(ip);
if(connect(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0)
{
perror("Fail to bind");
exit(EXIT_FAILURE);
}
return sockfd;
}
int do_register(int sockfd)
{
int n = 0;
int count = 0;
char buf[1024] = {0};
//定义发送的协议头
mhead_t *head = (mhead_t *)buf;
printf("\n您正在注册,请输入用户名和密码\n");
head->type = USER_REGISTER;
head->size = sizeof(mhead_t);
printf("Input username : ");
fgets(head->username,sizeof(head->username),stdin);
head->username[strlen(head->username) - 1] = '\0';
printf("Input password : ");
fgets(head->password,sizeof(head->password),stdin);
head->password[strlen(head->password) - 1] = '\0';
//发给服务器端
if(send(sockfd,buf,sizeof(mhead_t),0) < 0)
{
perror("Fail to send");
exit(EXIT_FAILURE);
}
bzero(&buf,sizeof(buf));
while(1)
{
//接收数据,TCP是可靠的连接,若是数据
//未完全接收的话,可以在接收
n = recv(sockfd,buf + count,sizeof(mhead_t) - count,0);
if(n <= 0){
perror("Fail to send");
exit(EXIT_FAILURE);
}
//若是数据未发送完成,再次接收的时候可补充
count += n;
if(count == sizeof(mhead_t))
break;
}
if(head->type == USER_SUCCESS)
{
printf("\n恭喜您,注册成功!\n");
return 0;
}else{
printf("\n很遗憾,这个用户名已经被其它用户注册过了,请重新注册");
return -1;
}
}
int do_login(int sockfd)
{
int n = 0;
int count = 0;
char buf[1024] = {0};
mhead_t *head = (mhead_t *)buf;//定义发送的协议头
printf("\n您正在登陆,请输入用户名和密码\n");
head->type = USER_LOGIN;
head->size = sizeof(mhead_t);
printf("Input username : ");
fgets(head->username,sizeof(head->username),stdin);
head->username[strlen(head->username) - 1] = '\0';
printf("Input password : ");
fgets(head->password,sizeof(head->password),stdin);
head->password[strlen(head->password) - 1] = '\0';
if(send(sockfd,buf,sizeof(mhead_t),0) < 0)
{
perror("Fail to send");
exit(EXIT_FAILURE);
}
bzero(&buf,sizeof(buf));
while(1)
{
n = recv(sockfd,buf + count,sizeof(mhead_t) - count,0);
if(n <= 0)
{
perror("Fail to send");
exit(EXIT_FAILURE);
}
count += n;
if(count == sizeof(mhead_t))
break;
}
if(head->type == USER_SUCCESS)
{
printf("\n恭喜您,登陆成功\n");
do_task2(sockfd);
return 0;
}else
{
printf("\n抱歉,用户名或密码错误");
return -1;
}
}
int do_query(int sockfd)//单词查询
{
int fd;
int ret = 0;
time_t t;
time(&t);
char buf[1024];
char words[1024];
int count = 0;
ssize_t wr_bytes;
mhead_t *head = (mhead_t *)buf;
printf("\n您,正在查询单词!\n");
head->type = USER_WORD;
head->size = sizeof(mhead_t);
printf("Input word : ");
fgets(head->word,sizeof(head->word),stdin);
head->word[strlen(head->word)-1] ='\0';
if(send(sockfd,buf,sizeof(mhead_t),0)<0) //发送给服务器
{
perror("Fail to send");
exit(EXIT_FAILURE);
}
bzero(buf,sizeof(buf));
while(1)
{
ret = recv(sockfd,buf+count,sizeof(mhead_t)-count,0);
if(ret <= 0)
{
perror("Fail to send");
exit(EXIT_FAILURE);
}
count += ret;
if(ret == sizeof(mhead_t))
{
break;
}
}
if(head->type == USER_SUCCESS) //单词存在
{
printf("%s\n",head->word);
fd = open("word_record.txt",O_RDWR|O_CREAT|O_APPEND,0666); //打开文件
if(fd == -1)
{
perror("open");
return -1;
}
sprintf(words,"%s %s\n",ctime(&t),head->word);//单词以及查询时间放入数据库
wr_bytes = write(fd,words,strlen(words)); //单词写入文件
if(wr_bytes == -1)
{
perror("write");
return -1;
}
do_task2(sockfd);
}
else
{
printf("no such word\n");//该单词不存在
return -1;
}
close(fd);
}
int do_history(int sockfd)//查询历史记录
{
FILE *fp;
char words[1024];
char * rd_bytes;
fp = fopen("word_record.txt","r");//以只读方式打开文件
if(fp == NULL)
{
perror("");
return -1;
}
fseek(fp,0,SEEK_SET);
while(1)
{
rd_bytes = fgets(words,sizeof(words),fp); //按行写入
if(rd_bytes == NULL) //没有该单词
{
perror("read");
return -1;
}
else if(rd_bytes == 0)//有该单词
{
break;//找到单词,跳出循环
}
else
{
printf("%s\n",words);
}
}
do_task2;
fclose(fp);
}
int do_task(int sockfd)
{
int cmd;
while(1)
{
//提示界面帮助,用户选择
help_info1();
printf("\n\n请选择>");
scanf("%d",&cmd);
//吃掉回车键
getchar();
switch(cmd)
{
//用户注册,我们先来写注册的函数
case REGISTER:
if(do_register(sockfd) < 0)
continue;
break;
//用户登陆
case LOGIN:
if(do_login(sockfd) < 0)
continue;
break;
case QUIT:
exit(EXIT_SUCCESS);
default:
printf("Unknow cmd.\n");
continue;
}
}
return 0;
}
int do_task2(int sockfd)
{
int cmd;
while(1)
{
//登陆成功选项页面
help_info2();
printf("\n\n请选择>");
scanf("%d",&cmd);
//吃掉回车键
getchar();
switch(cmd)
{
case QUERY://用户查询单词
if(do_query(sockfd) < 0)
continue;
break;
case HISTORY://历史查询
if(do_history(sockfd < 0))
continue;
break;
case QUIT:
exit(EXIT_SUCCESS);
default:
printf("Unknow cmd.\n");
continue;
}
}
}
//./client ip port
//由于后面要传递参数,故这里的const省略
int main(int argc, char *argv[])
{
int sockfd;
int addr_len = sizeof(struct sockaddr);
struct sockaddr_in peer_addr;
if(argc < 3)
{
fprintf(stderr,"Usage : %s argv[1] argv[2]\n",argv[0]);
exit(EXIT_FAILURE);
}
sockfd = init_tcp(argv[1],argv[2]);
do_task(sockfd);
return 0;
}