在线词典项目(附源码以及解析)
文章目录
项目设计总览
项目要求:
1、用户的 注册、登录、注销 功能设计:
注册:将用户名和密码保存至数据库当中(要判断用户名是否被注册)
登录:用户输入的数据将会和数据库中的数据进行比较,如果一致登录成功否则失败
2.登录成功**(词典功能设计)**
词典的翻译、历史记录、退出 功能设计
翻译:用户输入单词,服务器从本地文件中查询到相应单词后将翻译传送至客户端
历史记录查询:用户每次查询单词,将用户查询的单词以及对应的翻译,查询的时间存储到数据库当中用户选择该功能的时候,就可以查询之前查看过得单词
项目成果展示:
用户注册
(客户端)
(服务器)
用户登录
(客户端)
用户登录成功进入进入二级菜单
查询单词
(客户端)
(服务器)
历史查词记录
(客户端)
(服务器)
源代码
client.h
#ifndef _CLIENT_H
#define _CLIENT_H
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
// 结构体保存命令,传输到服务器的一些基本信息
struct msg
{
char cmd; // 命令识别号
char hostname[20]; // 用户名
char passwords[20]; // 密码
char word[20]; // 查询的单词
char data[256]; // 存放翻译单词的信息
char e_msg[100]; // 处理信息
};
struct msg mymsg; // 我们将所要从客户端发送的数据,封装到该结构体中
int ret; // ret用作接收函数的返回值(return)
int strcp(char *str1, char *str2);
int register_func(struct msg aabb, int sockfd); // 注册函数
int login_func(struct msg aabb, int sockfd); // 登录函数
int query_word(struct msg aabb, int sockfd); // 查词请求
int query_records(struct msg aabb, int sockfd); // 历史查词
void init_struct(struct msg aabb); // 初始化结构体
void print_struct(struct msg aabb); // 用于调试查看结构体成员
#endif
client.c
#include "client.h" //引用自己定义的头文件使用“”括上
// 字符串的拷贝 将str1中内容拷贝到str2 中
int strcp(char *str1, char *str2)
{
int i, j;
j = strlen(str1) - 1;
for (i = 0; i < j; i++)
{
str2[i] = str1[i];
}
return 0;
}
// 注册函数功能
int register_func(struct msg aabb, int sockfd)
{
char name[20] = "\0";
char temp1[20] = "\0";
char temp2[20] = "\0";
int flag = 1;
aabb.cmd = 1;
printf("请输入注册的用户名:(用户名将作为用户的登录账户!) \n");
getchar();
fgets(name, sizeof(name), stdin);
strcp(name, aabb.hostname);
// printf("%s\n",name); //之所以前面要使用getchar,自己解掉注释输出看一下用户名
mima:
printf("请输入登录密码:\n");
fgets(temp1, sizeof(temp1), stdin);
printf("请再次输入登录密码:\n");
fgets(temp2, sizeof(temp2), stdin);
if (!strncmp(temp1, temp2, strlen(temp2)))
{
strcp(temp2, aabb.passwords);
}
else
{
printf("请确保两次密码相同!\n");
system("clear");
goto mima;
}
//----上面已经配置好需要发送的结构体了,下面开始将结构体传输到服务器:
send(sockfd, &aabb, sizeof(aabb), 0);
return 0;
}
int login_func(struct msg aabb, int sockfd)
{
aabb.cmd = 2;
//==============用户验证=====================
printf("请输入用户名: \n");
getchar();
scanf("%s", aabb.hostname);
// printf("用户名:%s\n", aabb.hostname);
printf("请输入用户密码: \n");
scanf("%s", aabb.passwords);
// printf("密码:%s\n", aabb.passwords);
ret = send(sockfd, &aabb, sizeof(aabb), 0);
if (ret < 0)
{
perror("send");
return -1;
}
ret = recv(sockfd, &aabb, sizeof(aabb), 0);
if (ret == -1)
{
perror("recv");
return -1;
}
printf("aabb.e_msg=%s\n", aabb.e_msg);
if (!strncmp(aabb.e_msg, "OK", 2))
{
printf("登录成功!\n");
}
else
{
printf("登录失败!\n");
return -1;
}
//====================================================
return 0;
}
// 用户登录成功后的查询功能
int query_word(struct msg aabb, int sockfd)
{
//===============向服务器发起查词请求==================================
aabb.cmd = 4;
printf("请输入需要查询的单词: \n");
// getchar();
scanf("%s", aabb.word);
ret = send(sockfd, &aabb, sizeof(aabb), 0);
if (ret == -1)
{
perror("send");
return -1;
}
else
{
printf("正在查询中...... \n\n");
}
//===================接受服务器的查词结果======================
ret = recv(sockfd, &aabb, sizeof(aabb), 0);
if (ret == -1)
{
perror("recv");
return -1;
}
// print_struct(aabb);
// printf("data=%s\n",aabb.data);
// printf("e_msg=%s\n",aabb.e_msg);
if (!strncmp(aabb.e_msg, "ok", 2))
{
printf("查询结果:%s\n", aabb.data);
}
else
{
printf("单词表中无此单词,请检查单词拼写是否有误\n");
}
return 0;
}
int query_records(struct msg aabb, int sockfd)
{
aabb.cmd = 5;
ret = send(sockfd, &aabb, sizeof(aabb), 0);
printf("record =%s\n", aabb.hostname);
if (ret == -1)
{
perror("send");
return -1;
}
// 接收服务器的,传递回来的历史记录信息
while (1)
{
recv(sockfd, &aabb, sizeof(aabb), 0);
if (!strncmp(aabb.e_msg, "end", 3))
{
break;
}
// 输出历史信息
printf("%s\n", aabb.data);
}
return 0;
}
void init_struct(struct msg aabb)
{
// memset(&aabb, 0, sizeof(aabb));
memset(&aabb.cmd, 0, sizeof(aabb.cmd));
memset(&aabb.passwords, 0, sizeof(aabb.passwords));
memset(&aabb.word, 0, sizeof(aabb.word));
memset(&aabb.data, 0, sizeof(aabb.data));
memset(&aabb.e_msg, 0, sizeof(aabb.e_msg));
}
// 该函数是楼主为了测试查看数据方便,所编写
void print_struct(struct msg aabb)
{
printf("cmd=%c \n", aabb.cmd);
printf("hostname=%s \n", aabb.hostname);
printf("passwords=%s \n", aabb.passwords);
printf("word=%s \n", aabb.word);
printf("data=%s \n", aabb.data);
printf("e_msg=%s \n", aabb.e_msg);
}
main_client.c
/*************************************************************************
> File Name: main_client.c
> Author: xiaoxiongbenbi
> Mail: aabb@qq.com
************************************************************************/
#include "client.h" //引用自己定义的头文件使用“”括上
int sockfd; // sockfd为套接字文件描述符
int cmd, n; // cmd 用作一级菜单的判定标志 n用作二级菜单的判定标志
struct sockaddr_in servaddr;
socklen_t addrlen = sizeof(struct sockaddr_in);
char buf[1024] = "\0";
int main(int argc, const char *argv[])
{
if(argc != 3) //argc表示main函数中的参数个数,不了解可以自己搜索main函数的参数
{
printf("Usage:%s ip port.\n",argv[0]);//没有传IP和端口号
}
// 1、创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
perror("socket");
return -1;
}
// 2、绑定网络信息
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[2]));
servaddr.sin_addr.s_addr = inet_addr(argv[1]);
// 3、发送连接请求
ret = connect(sockfd, (struct sockaddr *)&servaddr, addrlen);
if (ret == -1)
{
perror("connect");
return -1;
}
recv(sockfd, buf, sizeof(buf), 0);
if (!strncmp(buf, "ok", 2))
{
printf("已经成功连接到服务器\n");
}
// 4、功能实现
while (1)
{
// 打印用户登录提示
printf("||*******输入数字1,2,3 选择下列选项********||\n");
printf("\n");
printf("||***********小笨熊在线词典***************||\n");
printf("|| 1-注册 || 2-登录 || 3-退出 ||\n");
printf("||***************************************||\n");
scanf("%d", &cmd);
switch (cmd)
{
case 1:
init_struct(mymsg);
register_func(mymsg,sockfd);
system("clear");
case 2:
init_struct(mymsg);
while (1)
{
if (0 == login_func(mymsg,sockfd))
{
// 登录成功进入二级菜单
goto next;
break;
}
system("clear");
}
case 3:
close(sockfd);
exit(0);
break;
default:
system("clear");
printf("请输入数字1,2,3\n");
break;
}
}
next:
while (1)
{
printf("**************输入数字1,2,3 选择下列选项**************\n");
printf("****************************************************\n");
printf("* 1--查询单词 2--历史查词记录 3--退出 *\n");
printf("****************************************************\n");
printf("Please choose: ");
scanf("%d", &n);
getchar();
switch (n)
{
case 1:
init_struct(mymsg);
query_word(mymsg,sockfd);
break;
case 2:
init_struct(mymsg);
query_records(mymsg,sockfd);
break;
case 3:
close(sockfd);
exit(0);
break;
default:
printf("Invalid data cmd.\n");
}
}
// 5、关闭文件描述符号
close(sockfd);
return 0;
}
server.h
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sqlite3.h>
#include <time.h>
// 结构体保存命令,传输到客户端的一些基本信息
struct msg
{
char cmd; // 命令识别号
char hostname[20]; // 用户名
char passwords[20]; // 密码
char word[20]; // 查询的单词
char data[256]; // 存放翻译单词的信息
char e_msg[100]; // 处理信息
};
int ret;
struct msg sermsg;
int strcp(char *str1, char *str2); // 将str1 复制到str2中
int create_table(sqlite3 *db); // 创建存放用户的users表 和 存放查词记录的record表
int insert_Data_to_users(sqlite3 *db, char *buf, char *UID, char *hostname, char *passwords); // 向users表中插入数据
int insert_Data_to_record(sqlite3 *db, char *buf, char *hostname, char *data); // 向record表中插入数据
int register_func(struct msg aabb, sqlite3 *db, char *buf); // 注册函数
int login_func(sqlite3 *db, int connfd, struct msg aabb); // 登录函数
static int callback(void *data, int argc, char **argv, char **azColName); // 回调函数
int query_word(sqlite3 *db, char *buf, int connfd, struct msg aabb);
int query_records(sqlite3 *db, int connfd, struct msg aabb);
static inline void get_time(char *buf); // 获取系统时间
void init_struct(struct msg aabb); // 初始化结构体
void print_struct(struct msg aabb); // 该函数用于方便调试结构体成员
server.c
/*************************************************************************
> File Name: server.c
> Author: xiaoxiongbenbi
> Mail: aabb@qq.com
************************************************************************/
#include "server.h" //引用自己定义的头文件使用“”括上
// 字符串的拷贝 将str1中内容拷贝到str2 中
int strcp(char *str1, char *str2)
{
int i, j;
j = strlen(str1);
for (i = 0; i < j; i++)
{
str2[i] = str1[i];
}
return 0;
}
// 创建存放用户的users表 和 存放查词记录的record表
int create_table(sqlite3 *db)
{
// 创建表准备
// UID 字段 自动增长
const char *creat1 = "create table if not exists users(Data char,UID INTEGER PRIMARY KEY autoincrement,hostname char unique ,passwords char );\n";
const char *creat2 = "create table if not exists record(time char,hostname char,data char);\n";
// printf("%s", creat1);
// printf("%s", creat2);
char *p2 = 0;
ret = sqlite3_exec(db, creat1, 0, 0, &p2); // 我的理解是:sqlite3_exec()作为API,收到我的命令creat表,告诉数据库执行相关命令。
if (ret == 0)
{
printf("用户注册表users创建成功!\n");
}
else
{
printf("%s", p2);
}
// 创建历史查询记录表
ret = sqlite3_exec(db, creat2, 0, 0, &p2);
if (ret == 0)
{
printf("查词记录表record创建成功!\n");
}
else
{
printf("%s", p2);
}
sqlite3_free(p2);
return 0;
}
// 向users表中插入数据
int insert_Data_to_users(sqlite3 *db, char *buf, char *UID, char *hostname, char *passwords)
{
char *rt;
char *p2 = 0;
// 这里的UID我们设置的是主键自增长,不需要 m对其进行设置
rt = sqlite3_mprintf("INSERT INTO users VALUES('%s',NULL,'%s','%s')", buf, hostname, passwords); // 插入数据准备
ret = sqlite3_exec(db, rt, 0, 0, &p2); // 执行命令
return ret;
}
// 向record表中插入数据
int insert_Data_to_record(sqlite3 *db, char *buf, char *hostname, char *data)
{
char *rt;
char *p2 = 0;
// record(time char,hostname char,data char)
rt = sqlite3_mprintf("INSERT INTO record VALUES('%s','%s','%s')", buf, hostname, data); // 插入数据准备
ret = sqlite3_exec(db, rt, 0, 0, &p2); // 执行命令
return ret;
}
// 词典用户注册函数功能
int register_func(struct msg aabb, sqlite3 *db, char *buf)
{
memset(buf, 0, sizeof(buf)); // 清空存放系统时间 数组buff
get_time(buf); // 获取时间放在buff中
// 向注册表中插入数据
// int insert_Data_to_users( char *UID, char *hostname, char *passwords);
ret = insert_Data_to_users(db, buf, NULL, aabb.hostname, aabb.passwords);
if (ret == -1)
{
printf("数据插入失败!\n");
return -1;
}
else
{
printf("数据插入成功!\n");
}
// sqlite3_close(db);
return 0;
}
// 登录函数功能
int login_func(sqlite3 *db, int connfd, struct msg aabb)
{
char sql[512] = "\0"; // 128太小会警告
char *errmsg;
int nrow;
int ncloumn;
char **resultp;
sprintf(sql, "select * from users where hostname = '%s' and passwords = '%s' ;", aabb.hostname, aabb.passwords);
printf("%s\n", sql);
if (sqlite3_get_table(db, sql, &resultp, &nrow, &ncloumn, &errmsg) != SQLITE_OK)
{
printf("%s\n", errmsg);
return -1;
}
else
{
printf("get_table ok!\n");
}
// 查询成功,数据库中拥有此用户
if (nrow == 1)
{
strcpy(aabb.e_msg, "OK"); // 这里把OK放进data里,client.c中才会那么比较
send(connfd, &aabb, sizeof(aabb), 0);
return 0;
}
if (nrow == 0) // 密码或者用户名错误,或者写else就可以
{
strcpy(aabb.e_msg, "usr/passwd wrong.");
send(connfd, &aabb, sizeof(aabb), 0);
return -1;
}
}
// 单词查询
int query_word(sqlite3 *db, char *buf, int connfd, struct msg aabb)
{
FILE *fd;
char temp[256] = "\0";
char *p;
int len, result; // len 记录单词的长度
// 记录查询的单词长度
len = strlen(aabb.word);
printf("len = %d\n", len);
// 打开保存在服务器本地的dict.txt(单词表)
if (NULL == (fd = fopen("dict.txt", "r")))
{
strcpy(aabb.e_msg, "open dictionary file error!\n");
if (-1 == send(connfd, &aabb, sizeof(aabb), 0))
{
perror("send");
return -1;
}
}
printf("本地单词表文件已经打开!\n");
// 在dict.txt 中查找所要搜寻的单词
/***
* 因为在dict.txt我们的单词是每一行存储一个,
* 所以可以利用fgets函数的特点 : 读到换行符号就会停止
*/
printf("word=%s\n", aabb.word);
while (fgets(temp, 256, fd) != NULL)
{
// printf("temp = %s\n", temp);
// 比较单词 找到之后直接跳出对比 没有找继续直到整个文件全部找完
result = strncmp(aabb.word, temp, len);
if (result == 0)
{
break;
}
memset(buf, 0, sizeof(buf));
}
// 找到或者找完之后 进行判断看
if (result == 0)
{
strcp(temp, aabb.data);
strcpy(aabb.e_msg, "ok");
// 下列注释语句用作调试
// print_struct(aabb);
// printf("data=%s\n",aabb.data);
memset(buf, 0, sizeof(buf));
get_time(buf); // 获取时间放在buff中
// 向查词历史表中插入数据
// record(time char,hostname char,data char)
ret = insert_Data_to_record(db, buf, aabb.hostname, aabb.data);
if (ret == -1)
{
printf("数据插入record表失败!\n");
return -1;
}
else
{
printf("数据插入record表成功!\n");
}
}
else
{
strcpy(aabb.e_msg, "error");
}
ret = send(connfd, &aabb, sizeof(aabb), 0);
if (ret == -1)
{
perror("send");
return -1;
}
fclose(fd);
return 0;
}
// 得到查询结果,并且需要将历史记录发送给客户端
int history_callback(void *params, int column_size, char **column_value, char **column_name)
{
int connfd; //在回调函数中要想通过套接字发送数据,就需要设置一个形参来接收sqlite3_exec传递过来的参数(第四个参数,也就是套接字文件描述符)
connfd = *((int *)params);
sprintf(sermsg.data, "%s , %s , %s ", column_value[0], column_value[1], column_value[2]);
printf("%s\n", sermsg.data);
send(connfd, &sermsg, sizeof(sermsg), 0);
return 0;
}
// 历史查词记录
int query_records(sqlite3 *db, int connfd, struct msg aabb)
{
char sql[512] = "\0";
char *errmsg;
sprintf(sql, "select * from record where hostname = '%s'", aabb.hostname);
// 查询数据库
if (sqlite3_exec(db, sql, history_callback, (void *)&connfd, &errmsg) != SQLITE_OK)
{
printf("%s\n", errmsg);
}
else
{
printf("Query record done.\n");
}
strcp("end", aabb.e_msg);
printf("aabb=%s\n", aabb.e_msg);
send(connfd, &aabb, sizeof(aabb), 0);
return 0;
}
static inline void get_time(char *buf)
{
struct tm *tm_ptr;
time_t the_time;
(void)time(&the_time);
tm_ptr = gmtime(&the_time);
sprintf(buf, "%02d-%02d-%02d %02d:%02d:%02d", tm_ptr->tm_year + 1900, tm_ptr->tm_mon + 1, tm_ptr->tm_mday, (tm_ptr->tm_hour) + 8, tm_ptr->tm_min, tm_ptr->tm_sec);
}
// 初始化结构体
void init_struct(struct msg aabb)
{
memset(&aabb, 0, sizeof(aabb));
}
// 该函数用于方便调试结构体成员
void print_struct(struct msg aabb)
{
printf("cmd=%c-- \n", aabb.cmd);
printf("hostname=%s-- \n", aabb.hostname);
printf("passwords=%s-- \n", aabb.passwords);
printf("word=%s-- \n", aabb.word);
printf("data=%s-- \n", aabb.data);
printf("e_msg=%s-- \n", aabb.e_msg);
}
main_server.c
#include "server.h" //引用自己定义的头文件使用“”括上
int sockfd, connfd;
sqlite3 *db = NULL;
struct sockaddr_in servaddr, cliaddr;
socklen_t addrlen = sizeof(struct sockaddr_in);
char buf[1024] = "\0";
int main(int argc, const char *argv[])
{
if(argc != 3) //argc表示main函数中的参数个数,不了解可以自己搜索main函数的参数
{
printf("Usage:%s ip port.\n",argv[0]);//没有传IP和端口号
}
// 1、打开数据库
if (sqlite3_open("dir.db", &db) != SQLITE_OK)
{
printf("%s\n", sqlite3_errmsg(db));
return -1;
}
else
{
printf("open dir.db success\n");
}
// 创建用户注册表和历史查词记录表
create_table(db);
// 1、创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
perror("socket");
return -1;
}
// 2、绑定网络信息
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[2]));
servaddr.sin_addr.s_addr = inet_addr(argv[1]);
ret = bind(sockfd, (struct sockaddr *)&servaddr, addrlen);
if (ret == -1)
{
perror("bind");
return -1;
}
// 3、监听套接字
ret = listen(sockfd, 5);
if (ret == -1)
{
perror("listen");
return -1;
}
// 4、接收连接请求
connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &addrlen);
if (connfd == -1)
{
perror("accept");
return -1;
}
send(connfd, "ok", 2, 0);
printf("连接成功\n");
// 5、功能实现
while (1)
{
memset(buf, 0, sizeof(buf)); // 初始化存放时间的数组 buf
ret = recv(connfd, &sermsg, sizeof(sermsg), 0);
if (ret == -1)
{
perror("recv");
return -1;
}
else
{
printf("%c\n", sermsg.cmd);
}
switch (sermsg.cmd)
{
case 1:
// printf("%s\n", sermsg.hostname);
// printf("%s\n", sermsg.passwords);
register_func(sermsg, db, buf);
break;
case 2:
login_func(db, connfd, sermsg);
break;
case 3:
sqlite3_close(db);
close(connfd);
close(sockfd);
break;
case 4:
query_word(db, buf, connfd, sermsg);
break;
case 5:
query_records(db, connfd, sermsg);
break;
}
init_struct(sermsg);
}
return 0;
}
gcc如何编译多个文件呢?
在项目中我们大部分的时间一般都在修改和调试程序,所以能使用脚本来编写执行程序的编译。就能够大大地提升开发中的效率。
下面我给出我在此项目中所编写的shell脚本。可以参考学习一下!
make.sh
第一种写法:
#########################################################################
# File Name: make.sh
# Author: xiaoxiongbenbi
# mail: aabb@qq.com
#########################################################################
#!/bin/bash
gcc -I. -c main_server.c
gcc -I. -c server.c
gcc -I. -c main_client.c
gcc -I. -c client.c
gcc client.o main_client.o -o client
gcc server.o main_server.o -o server -lsqlite3 #server.c 中用到了sqlite3中的函数就必须链接sqlite3提供的函数库
rm *.o #把生成的中间.o文件删除
echo "compliing success!"
第二种写法:
#########################################################################
# File Name: makefile.sh
# Author: xiaoxiongbenbi
# mail: aabb@qq.com
# Created Time: 2023年01月17日 星期二 16时28分38秒
#########################################################################
#!/bin/bash
gcc -I. main_server.c server.c -o server -lsqlite3
gcc -I. main_client.c client.c -o client
echo "compliing success!"
下方连接文章中有更加详细的介绍(可供参考):
(6条消息) gcc运行多源文件c语言,使用gcc同时编译多个文件_weixin_39823200的博客-CSDN博客
sqlite3_exec()函数中的回调函数
关于sqlite3数据库的函数 ----sqlite3_exec()函数中的回调函数
不了解的同学可以翻看我前面的文章。
工程分享
链接:https://pan.baidu.com/s/1vl1yf4JRZtDYi-kuHiePOg?pwd=gsja
提取码:gsja
–来自百度网盘超级会员V4的分享
最后也欢迎大家的指正和批评!