前言:
仅做记录和分享自己学习的过程,学艺不精,code新人,如有错误欢迎在评论区讨论、指正!
一、项目功能简介
在线英英词典
1、用户注册和登录验证
1.1服务器:将用户信息和历史记录保存在数据库中。
1.2客户端:输入用户名和密码
1.3服务器:在数据库中查找、匹配,返回结果.
2、单词在线翻译
根据客户端输入的单词在字典文件中搜索
3、历史记录查询显示查找记录和查找时间
二、项目演示截图
1.登录注册界面
2.用户功能界面
三、项目设计思想
因为要实现的主要是查询等一系列的工作,所以选择用数据库结合网络来做。
项目主要分为三个板块:数据库、客户端、服务器
数据库部分
数据库中有三张表,分别是:词典表,用户信息表,跟查询记录表
name(name text ,pswd text);//用户信息
dict(word text,explain text);//词典内容
record(name text,word text,explain text,restime text);//查询记录
数据导入
因为只需要导入一次词典,所以就放在服务器外面,避免每次运行都重复导入词典,
这里给出的词典参考内容是txt文件,
通过循环调用 fgets函数,读取txt文本内容,并插入到词典表中
注意对每行的末尾的换行符进行处理\r\n两个符号都要处理,
还有文本中自带的'’可能会影响sql语句,所以将全部的‘’在文本文件中替换为,再导入
观察文本发现,每一行由 单词 空格 解释 三部分组成,
#include"head.h"
int main()
{
//创建数据库,并打开
int err;
sqlite3* db;
if (0 != (err = sqlite3_open("dict.db", &db)))
{
fprintf(stderr, "open database:%s\n", strerror(err));
return -1;
}
//创建词典表
/*primary key*/
char* errmsg1 = NULL;
char* sql1 = "create table if not exists dict(word text ,explain text)";
if (0 != sqlite3_exec(db, sql1, NULL, NULL, &errmsg1))
{
fprintf(stderr, "create table:%s\n", errmsg1);
return -1;
}
/*将文档中的单词导入数据库,
用标准I/O的方法,使用fgets函数读入一行,
读入后将单词和解释分割开来,插入单词表中。*/
char buf[500];
char word[25];
char explain[400];
char sql3[500];
char* errmsg3 = NULL;
int i;
FILE* fp = fopen("./dict.txt", "r");
if(fp==NULL)
{
perror("fopen:");
return -1;
}
while (fgets(buf, sizeof(buf), fp))//循环读取一行
{
buf[strlen(buf)-1]='\0';//处理末尾换行符\r\n;
buf[strlen(buf)-1]='\0';
i=0;
memset(word, 0, sizeof(word));//单词
memset(explain, 0, sizeof(explain));//解释
memset(sql3, 0, sizeof(sql3));//sql语句
while (buf[i] != ' ') i++;
strncpy(word, buf, i);//拷贝单词到Word
buf[i]='\0';
i++;
while (buf[i] == ' ') i++;//跳过空格
strcpy(explain, buf + i);
sprintf(sql3, "insert into dict values('%s','%s')", word, explain); //把变量按照格式存在sql中
if (0 != sqlite3_exec(db,sql3,NULL,NULL,&errmsg3))
{
fprintf(stderr, "dict create:%s\n", errmsg3);
return -1;
}
}
return 0;
}
客户端
1.连接部分:创建套接字,连接服务器
2.登录注册界面:三个选项(每选择一个选项,向服务器发送相应的请求)
1.登录login、2.注册register、3.退出quit
•选择登录,调用do_login函数,登录成功则跳转到功能界面
•选择注册,调用do_register函数,进行注册操作
•选择退出,发送"quit"命令给服务器,关闭套接字并退出程序
3.功能界面:三个选项(每选择一个选项,向服务器发送相应的请求)
1.翻译translation、2.历史记录查询history、3.返回上级菜单go back
•选择翻译:调用do_translation函数,进行单词翻译操作
•选择查询历史记录:调用do_history函数进行历史记录查询操作
•选择go back :返回上级菜单()
4.关闭套接字并退出程序
服务器
这里采用信号处理方式,处理僵尸进程,
void handler(int sig)//用信号机制处理僵尸进程
{
while (waitpid(-1, NULL, WNOHANG) > 0);
}
、、、、、、、、、、、、
signal(SIGCHLD, handler);
1.“打开数据库和创建表:用户表(name,pswd),和记录表(name,word,explain,restime)。
2. 连接准备:创建套接字,绑定本机地址和端口,监听连接。
3. 循环接收客户端连接:创建子进程fork,处理客服端请求
(1).登录:login:do_login:在user表中查找到相应记录,则登录成功
(2).注册:register:do_register,向user表中插入一条新记录
(3).翻译:translation:do_translation,向record表中插入一条新记录
(4).历史查询:history:do_history,在record表中查找相应的记录,并发送给客户端
四、代码实现
头文件head.h
为了方便使用,将许多头文件放入一个head.h文件中(本项目并没有使用到head.h中所有头文件)
#ifndef __HEAD_H__
#define __HEAD_H__
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <stdlib.h>
//file io
#include <sys/types.h>
#include <sys/stat.h>
#include<fcntl.h>
//network
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sqlite3.h>
#include<dirent.h>
#include<time.h>
#define SIZE 64
typedef struct sockaddr SA; //通用地址结构
typedef struct sockaddr_in IPaddr_t; //IPv4地址结构
typedef struct
{
char name[30];
char word[30];
char explain[300];
char restime[50];
}msg_t;
#endif
客服端client.c
#include "head.h"
char username[30];
//注册
void do_register(int sockfd)
{
char name[30];
char pswd[30];
char order[SIZE] = "register";//请求
send(sockfd, order, SIZE, 0);//发送注册请求
printf("请输入账号:");
fgets(name, sizeof(name), stdin);
name[strlen(name) - 1] = '\0';
printf("请输入密码:");
fgets(pswd, sizeof(pswd), stdin);
pswd[strlen(pswd) - 1] = '\0';
send(sockfd, name, sizeof(name), 0);
send(sockfd, pswd, sizeof(pswd), 0);
recv(sockfd, order, 2, 0);
if (strcmp(order, "Y") == 0)
printf("注册成功\n");
else {
printf("注册失败\n");
}
}
//登录,成功返回1,失败返回0
int do_login(int sockfd)
{
int ret = 0;
char name[30];
char pswd[30];
char order[SIZE] = "login";
send(sockfd, order, SIZE, 0);
printf("请输入账号:");
fgets(name, sizeof(name), stdin);
name[strlen(name) - 1] = '\0';
printf("请输入密码:");
fgets(pswd, sizeof(pswd), stdin);
pswd[strlen(pswd) - 1] = '\0';
send(sockfd, name, sizeof(name), 0);
send(sockfd, pswd, sizeof(pswd), 0);
recv(sockfd, order, 2, 0);
if (strcmp(order, "Y") == 0)
{
printf("登录成功!\n");
ret = 1;
strcpy(username, name);
}
else
printf("登录失败\n");
return ret;
}
//翻译,英英翻译,输入单词,查询得到解释
int do_translation(int sockfd)
{
int ret = 0;
char order[SIZE] = "translation";
char word[30];
char explain[400];
send(sockfd, order, SIZE, 0);
printf("请输入需要查询的单词(go back 返回上级菜单):");
fgets(word, sizeof(word), stdin);
word[strlen(word) - 1] = '\0';//去掉换行符,避免干扰单词比较
send(sockfd, word, sizeof(word), 0);
//printf("word:%s\n",word);
if (0 == strcmp(word, "go back")) {
ret = 1;
return ret;
}
recv(sockfd, order, 2, 0);
if (0 == strcmp(order, "Y"))
{
recv(sockfd, explain, sizeof(explain), 0);
printf("explain:%s\n", explain);
send(sockfd, username, sizeof(username), 0);
}
else
{
printf("未查询到该单词\n");
//printf("hello\n");
}
}
//历史记录查询
int do_history(int sockfd)
{
char order[SIZE] = "history";
char name[30];
char len[20];
char record[500];
int nrow, ncolumn;
int ret;
msg_t msg;
send(sockfd, order, SIZE, 0);
printf("请输入需要查询的账号(go back 返回上级菜单):");
printf("(go back 返回上级菜单):");
fgets(name, sizeof(name), stdin);
name[strlen(name) - 1] = '\0';
send(sockfd, username, sizeof(username), 0);
if (0 == strcmp(name, "go back")) {
ret = 1;
return ret;
}
recv(sockfd, order, 2, 0);
if (strcmp(order, "Y") == 0)
{
recv(sockfd, &nrow, sizeof(int), 0);
printf("nrow:%d\n",nrow);
int i;
for (i = 0; i < nrow + 1; i++)
{
recv(sockfd, &msg, sizeof(msg), 0);
printf("%s | %s | %s | %s\n", msg.name,msg.word,msg.explain,msg.restime);
}
printf("共%d条记录\n",nrow);
}
else
{
printf("未查询到相关记录\n");
}
}
int main()
{
//1.创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket");
return -1;
}
printf("socket success!\n");
//2.主动连接服务器
IPaddr_t srvaddr = {
.sin_family = AF_INET, //IPv4
.sin_port = htons(5555), //port
.sin_addr.s_addr = inet_addr("127.0.0.1")
};
if (0 > connect(sockfd, (SA*)&srvaddr, sizeof(srvaddr))) {
perror("connect");
return -1;
}
printf("connect success!\n");
//3.通信,实现功能
int num, num1, ret, ret1,ret2;
char order[SIZE];
AAA:
while (1)//登陆注册界面
{
printf("欢迎来到英语知识海洋,这是你需要的英英词典\n");
printf("1.login 2.register 3.quit\n");
printf("请输入你的选择:");
if (1 != scanf("%d", &num))
{
printf("输入错误,请重新输入\n");
fgets(order, SIZE, stdin);//吃掉空格
//continue;
}
fgets(order, SIZE, stdin);//吃掉回车
switch (num)
{
case 1:ret = do_login(sockfd); if (1 == ret) goto XXX; break;
case 2:do_register(sockfd); break;
case 3:send(sockfd,"quit",5,0);close(sockfd); exit(0);
default:printf("输入错误,请重新输入\n"); break;
}
}
XXX:
while (1)//功能界面
{
printf("***********************************************\n");
printf("1.translation 2.history 3.go back\n");
printf("请输入你的选择:");
if (1 != scanf("%d", &num1))
{
printf("输入错误,请重新输入\n");
fgets(order, SIZE, stdin);//吃掉空格
continue;
}
fgets(order, SIZE, stdin);//吃掉回车
switch (num1)
{
case 1:
while (1) {
ret1 = do_translation(sockfd);
if (ret1 == 1)
break;
} break;
case 2:
while(1)
{
ret2=do_history(sockfd);
if (ret2== 1)
break;
} break;
case 3: goto AAA; break;
default: printf("输入错误,请重新输入\n"); break;
}
}
//4.关闭套接字
close(sockfd);
return 0;
}
服务器server.c
注册成功,更新user用户表(name,pswd);
查询单词成功,更新record记录表(name,word,explain,restime);
这里用time()和localtime()获取系统时间,并记录
time_t tm = time(NULL);//返回当前的时间戳(以秒为单位)
struct tm* lt;//将时间戳转换为本地时间的结构体指针。
lt = localtime(&tm);
其中tm结构体成员如下:
#include"head.h"
#include <signal.h>
#include <sys/wait.h>
//注册
void do_register(int connfd, sqlite3* db)
{
char sql[128];
char name[30];
char pswd[30];
char* errmsg;
recv(connfd, name, sizeof(name), 0);
recv(connfd, pswd, sizeof(pswd), 0);
sprintf(sql, "insert into user values('%s','%s')", name, pswd);//插入user表
if (0 != sqlite3_exec(db, sql, NULL, NULL, &errmsg))//注册失败,发送失败信号给客户端
{
fprintf(stderr, "register:%s\n", errmsg);
send(connfd, "N", 2, 0);
return;
}
send(connfd, "Y", 2, 0);
}
//登录
void do_login(int connfd, sqlite3* db)
{
char name[30];
char pswd[30];
char order[SIZE];
char sql[128];
char** resultp = NULL;
char* errmsg = NULL;
int nrow, ncolumn;
recv(connfd, name, sizeof(name), 0);
recv(connfd, pswd, sizeof(pswd), 0);
sprintf(sql, "select * from user where name='%s' and pswd='%s'", name, pswd);
if (0 != sqlite3_get_table(db, sql, &resultp, &nrow, &ncolumn, &errmsg))
{
fprintf(stderr, "lodin:%s\n", errmsg);
send(connfd, "N", 2, 0);
return;
}
if (nrow > 0)//查找到记录
send(connfd, "Y", 2, 0);
else
send(connfd, "N", 2, 0);
}
//单词在线翻译
void do_translation(int connfd, sqlite3* db)
{
char word[30];
char name[30];
char restime[100];
char explain[400];
char sql[128];
char sql1[600];
char* errmsg1 = NULL;
char** resultp = NULL;
char* errmsg = NULL;
char buf[400];
int nrow, ncolumn;
time_t tm = time(NULL);//返回当前的时间戳(以秒为单位)
struct tm* lt;//将时间戳转换为本地时间的结构体指针。
lt = localtime(&tm);
sprintf(restime, "%d-%d-%d %d:%d:%d", lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday, lt->tm_hour, \
lt->tm_min, lt->tm_sec);
recv(connfd, word, sizeof(word), 0);
if (strcmp(word, "go back") == 0)
return;
sprintf(sql, "select explain from dict where word='%s'", word);
if (0 != sqlite3_get_table(db, sql, &resultp, &nrow, &ncolumn, &errmsg))
{
fprintf(stderr, "translation:%s", errmsg);
return;
}
if (nrow > 0)
{
send(connfd, "Y", 2, 0);
strcpy(explain, resultp[1]);
//printf("explain:%s\n", explain);
send(connfd, explain, sizeof(explain), 0);
recv(connfd, name, sizeof(name), 0);//name
//查找单词成功,在查询表中添加一条记录,name,word,explain,restime
sprintf(sql1, "insert into record values('%s','%s','%s','%s')", name, word, explain, restime);
//sprintf(sql, "insert into user values('%s','%s')", name, pswd);//插入user表
//printf("%s\n", sql1);
if (0 != sqlite3_exec(db, sql1, NULL, NULL, &errmsg1))
{
fprintf(stderr, "record:%s", errmsg);
return;
}
//printf("本次查询记录成功\n");
}
else
send(connfd, "N", 2, 0);
}
//历史记录查询
void do_history(int connfd, sqlite3* db)
{
msg_t msg;
memset(&msg, 0, sizeof(msg));
char name[30];
char sql[128];
char** resultp = NULL;
char* errmsg = NULL;
int nrow, ncolumn;
recv(connfd, name, sizeof(name), 0);
sprintf(sql, "select * from record where name='%s'", name);
if (sqlite3_get_table(db, sql, &resultp, &nrow, &ncolumn, &errmsg))
{
fprintf(stderr, "history:%s", errmsg);
return;
}
//printf("sql:%s nrow:%d\n", sql, nrow);
strcpy(msg.name, name);
if (nrow > 0)
{
send(connfd, "Y", 2, 0);
send(connfd, &nrow, sizeof(int), 0);
int i, j, count = 0;
for (i = 0; i < nrow + 1; i++)
{
memset(&msg, 0, sizeof(msg));
for (j = 0; j < ncolumn; j++)
{
if (j == 0) strcpy(msg.name, resultp[count++]);
else if (j == 1) strcpy(msg.word, resultp[count++]);
else if (j == 2) strcpy(msg.explain, resultp[count++]);
else strcpy(msg.restime, resultp[count++]);
}
send(connfd, &msg, sizeof(msg), 0);
}
}
else
{
send(connfd, "N", 2, 0);
}
}
void handler(int sig)//用信号机制处理僵尸进程
{
while (waitpid(-1, NULL, WNOHANG) > 0);
}
int main()
{
signal(SIGCHLD, handler);
//创建数据库,并打开
int err;
sqlite3* db;
if (0 != (err = sqlite3_open("dict.db", &db)))
{
fprintf(stderr, "open database:%s\n", strerror(err));
return -1;
}
//创建用户表
char* errmsg = NULL;
char* sql = "create table if not exists user(name text primary key,pswd text)";
if (0 != sqlite3_exec(db, sql, NULL, NULL, &errmsg))
{
fprintf(stderr, "create table:%s\n", errmsg);
return -1;
}
// 创建查询记录表
char* errmsg2 = NULL;
char* sql2 = "create table if not exists record(name text,word text,explain text,sertime text)";
if (0 != sqlite3_exec(db, sql2, NULL, NULL, &errmsg2))
{
fprintf(stderr, "create table:%s\n", errmsg2);
return -1;
}
//1.创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket");
return -1;
}
printf("socket success!\n");
//2.设置重用本机地址和端口
int on = 1;
if (0 > setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) {
perror("setsocket");
return -1;
}
//3.绑定本机通信地址和端口
IPaddr_t srvaddr = {
.sin_family = AF_INET, //IPv4
.sin_port = htons(5555), //port
.sin_addr.s_addr = htonl(INADDR_ANY),
//.sin_addr.s_addr=inet_addr("192.168.7.199")
};
if (0 > bind(sockfd, (SA*)&srvaddr, sizeof(srvaddr))) {
perror("bind");
return -1;
}
printf("bind success!\n");
//4.设置监听套接字
if (0 > listen(sockfd, 5)) {
perror("listen");
return -1;
}
printf("listen success\n");
pid_t pid;
int ret;
char order[SIZE];
while (1)
{
//5.接收客户端的连接
IPaddr_t cliaddr;
socklen_t addrlen = sizeof(cliaddr); //需要初始化
int connfd = accept(sockfd, (SA*)&cliaddr, &addrlen);
if (connfd < 0) {
perror("accept");
return -1;
}
printf("accept: IP-%s Port_%hu\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
//6.通信,实现功能
if (0 > (pid = fork()))
{
perror("fork:");
break;
}
else if (0 == pid)//子进程
{
close(sockfd);
while (1)
{
ret = recv(connfd, order, SIZE, 0);
if (ret < 0)
{
perror("recv:");
break;
}
if (strcmp(order, "login") == 0)
do_login(connfd, db);
else if (strcmp(order, "register") == 0)
do_register(connfd, db);
else if (strcmp(order, "translation") == 0)
do_translation(connfd, db);
else
do_history(connfd, db);
}
close(connfd);
exit(0);
}
close(connfd);
}
//7.关闭套接字
close(sockfd);
sqlite3_close(db);
return 0;
}
sprintf( )和sscanf( ):
sprintf()
和 sscanf()
是C语言中的两个函数,用于格式化字符串的输入输出操作
sprintf() 函数:
功能:将格式化的数据写入字符串中。
用法:sprintf() 的原型是 int sprintf(char *str, const char *format, ...);
... 是可变参数列表,包含要格式化输出的数据。
format 是格式控制字符串,类似于 printf() 函数中的格式字符串,指定输出的格式和数据类型。
str 是目标字符串的指针,用来接收格式化后的输出;
实例:
char buf[100];
int num = 123;
sprintf(buf, "The number is: %d", num);
printf("string: %s\n", buf);
输出:string: The number is: 123
sscanf() 函数:
功能:根据指定的格式从字符串中读取格式化的输入
用法:sscanf() 的原型是 int sscanf(const char *str, const char *format, ...);
str : 输入的字符串,从这个字符串中读取数据。
format : 格式控制字符串,指定如何解析输入字符串中的数据。
... : 可变参数列表,用来接收解析出来的数据。
示例:
char input[] = "John 25 1.75";
char name[20];
int age;
float height;
sscanf(input, "%s %d %f", name, &age, &height);
printf("Name: %s, Age: %d, Height: %.2f\n", name, age, height);
输出:Name: John, Age: 25, Height: 1.75
sprintf() 用于将格式化的数据输出到字符串中,类似于 printf() 将输出写入到控制台。
sscanf() 用于从字符串中读取格式化的输入数据,类似于 scanf() 从控制台读取输入数据。
五、实验总结
这是第一次实打实的自己实现项目,个人感觉还蛮不容易的,从搭建框架到接口函数的实现再代码的调整,在不断地调整中终于实现了这些基础功能,希望在接下来的学习和生活中再接再厉,取得进步。