目录
前言
在线英英词典项目是一款基于网络的以单词查询为主要功能的软件,具体实现功能如下: 单词查询,账号注册与登录,历史记录管理
一、项目模块
1.网络通讯模块,服务器和客户端通基于 tcp 协议
2.数据持久化模块,使用轻量级数据库 SQLITE3
3.客户端菜单模块,使用命令行界面
二、实现步骤
1.服务器:使用sqlite3 数据库,在库中创建user表和record表(user 表,用于管理用户账号信息;record 表,用于管理历史查询信息);
2. 客户端:使用命令行界面来选择注册、登录、查询、历史记录、退出选项;
3.客户端选择注册:客户端输入账号密码,服务器接受数据后打开user表进行比对。如果user表中没有此用户则记录到表中,发送信息到客户端,客户端可继续选择登录账号;如果user表中有此用户则报错,发送信息到客户端,客户端重新注册账号;
4.客户端选择登录:客户端输入账号密码,服务器接受数据后打开user表进行比对,查看密码账号是否匹配。登录成功后可以进行查询;
5.客户端选择查询:客户端输入需要查询的单词,服务器接受单词后,打开dict.txt文档,如果没有该单词,则打印错误信息发送给客户端;如果该文档中有查询的单词,则发送该单词给客户端,并且记录此时的用户、时间和单词到record表中;
6.客户端选择查询历史记录:服务器接收到查询历史记录信息后,开始在record表中查询数据,每查到一条历史记录就给客户端发送一条记录,客户端也会一一接收;
三、功能代码
1.服务器 server.c
代码如下(示例):
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <sqlite3.h>
#include <signal.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <netinet/ip.h>
//信息结构体
typedef struct
{
char type; //选择
char name[64]; //用户名
char result[256]; // password密码 or word单词 or remark返回数据
} MSG_t;
//系统初始化的函数//打开数据文件、尝试建表
sqlite3 *process_init();
void recv_client(int rws, sqlite3 *sql_db);
void process_register(int rws, MSG_t *msg, sqlite3 *sql_db);
void process_login(int rws, MSG_t *msg, sqlite3 *sql_db);
void process_search(int rws, MSG_t *msg, sqlite3 *sql_db);
void sigHandler(int signo);
int server_init(char *ipaddr, unsigned short port, int backlog);
int get_time(char *time_s);
void process_history(int rws, MSG_t *msg, sqlite3 *sql_db);
int callback(void *arg, int ncolumn, char **f_value, char **f_name);
int main(int argc, char *argv[])
{
if(argc < 3)
{
printf("Usage:%s <ip> <port>\n",argv[0]);
return -1;
}
//数据库初始化
sqlite3 *sql_db = process_init();
//signal(SIGCHLD, sigHandler);
int sockfd = server_init(argv[1], atoi(argv[2]), 1024);
if(-1 == sockfd)
{
printf("server_init error\n");
return -1;
}
printf("listen....\n");
struct sockaddr_in caddr;//保存客户端的信息(ip port protocol)
bzero(&caddr, sizeof(caddr));
socklen_t clen = sizeof(caddr);
int ret;
while(1)
{
//rws:用于服务器和连接的客户端通信
int rws = accept(sockfd, (struct sockaddr *)&caddr, &clen);//阻塞等待客户端请求连接
if(-1 == rws)
{
perror("accept");
close(sockfd);
return -1;
}
printf("IP:%s Port:%u\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));
//创建子进程
pid_t pid = fork();
if(-1 == pid)
{
perror("fork");
break;
}
else if(0 == pid)//子进程
{
close(sockfd);//关闭监听套接字
recv_client(rws, sql_db);
}
else //父进程
{
signal(SIGCHLD, sigHandler);
close(rws);
}
}
close(sockfd);
return 0;
}
void sigHandler(int signo)//自定义信号处理函数
{
//回收子进程的资源
if(SIGCHLD == signo)
while(waitpid(-1, NULL, WNOHANG) > 0);//循环回收资源
}
int server_init(char *ipaddr, unsigned short port, int backlog)
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);//创建网络通信接口
if(-1 == sockfd)
{
perror("socket");
return -1;
}
struct sockaddr_in saddr;//服务器的地址结构
bzero(&saddr,sizeof(saddr));//memset()
saddr.sin_family = AF_INET;//指定协议族ipv4
saddr.sin_port = htons(port);//端口号:5001~65535
saddr.sin_addr.s_addr = (ipaddr == NULL)? htonl(INADDR_ANY) : inet_addr(ipaddr);//ip地址 点分式 -> 二进制网络字节序
int ret = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));//绑定 将服务器的ip和port与sockfd绑定
if(-1 == ret)
{
perror("bind");
close(sockfd);
return -1;
}
ret = listen(sockfd, backlog);//监听是否有客户端请求连接 sockfd:监听套接字
if(-1 == ret)
{
perror("listen");
close(sockfd);
return -1;
}
return sockfd;
}
//系统初始化的函数
//打开数据文件、尝试建表
sqlite3 *process_init()
{
//打开数据库文件
sqlite3 *sql_db = NULL;
int ret = sqlite3_open("test.db", &sql_db);
if (ret != SQLITE_OK)
{
printf("open sqlite3 error errmsg: %s \n", sqlite3_errmsg(sql_db));
exit(-1);
}
//建表
char *errstr = NULL;
//用户-密码
char buf[128] = "CREATE TABLE IF NOT EXISTS user(name text PRIMARY KEY, pass text)";
ret = sqlite3_exec(sql_db, buf, NULL, NULL, &errstr);
if (ret != SQLITE_OK)
{
printf("create table user error %s\n",errstr);
sqlite3_close(sql_db);
exit(-1);
}
//用户-时间-查询记录
char buff1[128] = "CREATE TABLE IF NOT EXISTS record(name text, date text, word text)";
ret = sqlite3_exec(sql_db, buff1, NULL, NULL, &errstr);
if (ret != SQLITE_OK)
{
printf("create table record error %s\n",errstr);
sqlite3_close(sql_db);
exit(-1);
}
sqlite3_free(errstr);
return sql_db;
}
void recv_client(int rws, sqlite3 *sql_db)
{
MSG_t msg;
while(recv(rws, &msg, sizeof(MSG_t), 0) > 0)
{
printf("type = %c\n", msg.type); //选择
printf("result = %s\n", msg.result); //数据
switch (msg.type)
{
case 'R': // 1. 注册
process_register(rws, &msg, sql_db);
break;
case 'L': // 2. 登录
process_login(rws, &msg, sql_db);
break;
case 'Q': // 3. 查询
process_search(rws, &msg, sql_db);
break;
case 'H': // 4. 历史记录
process_history(rws, &msg, sql_db);
break;
}
}
printf("client quit\n");
exit(0);
return;
}
// 注册
void process_register(int rws, MSG_t *msg, sqlite3 *sql_db)
{
char sql_buf[512], *errmsg;
sprintf(sql_buf, "INSERT INTO user VALUES('%s','%s')", msg->name, msg->result);
int ret = sqlite3_exec(sql_db, sql_buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf("error: %s\n", errmsg);
sprintf(msg->result, "用户名 %s 已经存在!!!", msg->name);
sqlite3_free(errmsg);
}
else
{
strncpy(msg->result, "register success", 16);
}
//发送数据
ret = send(rws, &msg->result, sizeof(MSG_t), 0);
if(ret == -1)
{
perror("send");
}
return;
}
// 登录
void process_login(int rws, MSG_t *msg, sqlite3 *sql_db)
{
char sql_buf[512], *errmsg;
sprintf(sql_buf, "SELECT * FROM user WHERE name='%s' AND pass='%s'", msg->name, msg->result);
char **result;//结果指针
int nrow = 0;//满足条件的记录个数
int ncolumn = 0; //每条记录的字段数
int ret =sqlite3_get_table(sql_db, sql_buf, &result, &nrow, &ncolumn, &errmsg);
if (ret != SQLITE_OK)
{
printf("error: %s\n", errmsg);
sqlite3_free(errmsg);//释放错误信息的空间 防止内存泄漏
}
if(0 == nrow) // 结果集没数据,行数nrow=0
{
sprintf(msg->result, "user name: %s passwd: %s is inconsistency", msg->name, msg->result);
}
else
{
strcpy(msg->result, "login success");
}
printf("msg->result: %s\n", msg->result);/
//发送数据
ret = send(rws, &msg->result, sizeof(MSG_t), 0);
if(ret == -1)
{
perror("send");
}
return ;
}
// 查询
void process_search(int rws, MSG_t *msg, sqlite3 *sql_db)
{
char sql_buf[512], *errmsg, *p;
char s[300], time_s[128];
int ret;
FILE *fp = fopen("dict.txt", "r"); // 打开字典
if (fp == NULL)
{
perror("fopen");
strcpy(msg->result, "dict can not open");
ret = send(rws, &msg, sizeof(MSG_t), 0);
if(ret == -1)
{
perror("send");
return ;
}
}
char word[256]; //单词
strcpy(word, msg->result);
int len = strlen(msg->result);//单词长度
while (fgets(s, sizeof(s), fp) != NULL)
{
//比较单词
ret = strncmp(word, s, len);
if (ret == 0 && s[len] == ' ') //相等 且 下一位是空格
{
p = &s[len]; //指向第一个空格
while (*p == ' ')
{
p++;
} //跳到解释的第一个字母
strcpy(msg->result, p); //解释赋值
//解释发送
goto TOcli;
}
else
{
strcpy(msg->result, "no exist");
}
}
TOcli:
//存在-记录-查询单词成功
if (0 != strcmp(msg->result, "no exist"))
{
get_time(time_s); // 当前时间
//使用sqlite3_exec函数调用插入记录
sprintf(sql_buf, "INSERT INTO record VALUES('%s','%s','%s')", msg->name, time_s, word);
int ret = sqlite3_exec(sql_db, sql_buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf("error: %s\n", errmsg);
sqlite3_free(errmsg);//释放错误信息的空间 防止内存泄漏
}
}
//发送数据
ret = send(rws, &msg->result, sizeof(MSG_t), 0);
if(ret == -1)
{
perror("send");
}
return ;
}
//时间
int get_time(char *time_s)
{
time_t t;
time(&t); //秒钟数
struct tm *tp; // tm的结构体
tp = localtime(&t); //将秒钟转化为年月日时分秒的格式
sprintf(time_s, "%d-%d-%d %d:%d:%d", 1900 + tp->tm_year, 1 + tp->tm_mon, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec);
}
//历史
void process_history(int rws, MSG_t *msg, sqlite3 *sql_db)
{
char sql_buf[512], *errmsg;
int ret;
sprintf(sql_buf, "SELECT * from record WHERE name='%s'", msg->name);
ret = sqlite3_exec(sql_db, sql_buf, callback, (void *)&rws, &errmsg);
if (ret != SQLITE_OK)
{
printf("error: %s\n", errmsg);
sprintf(msg->result, "用户名 %s 无记录!!!", msg->name);
sqlite3_free(errmsg);//释放错误信息的空间 防止内存泄漏
}
//回复结束标志
strcpy(msg->result, "OVER");
//发送数据
ret = send(rws, &msg->result, sizeof(MSG_t), 0);
if(ret == -1)
{
perror("send");
}
return ;
}
// callback 回调函数
int callback(void *arg, int ncolumn, char **f_value, char **f_name)
{
//每查询到一条记录 这个函数就会被调用一次!!!!
MSG_t msg;
char buf[512], *errmsg;
int rws = *(int *)arg; // 接收传递参数 强制类型转换
sprintf(msg.result, "%s : %s - %s", f_value[0], f_value[1], f_value[2]); // 时间 | 单词 ;f_value[0]=用户名
//发送数据
int ret = send(rws, &msg.result, sizeof(MSG_t), 0);
if(ret == -1)
{
perror("send");
}
return 0;
}
2.客户端 client.c
代码如下(示例):
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <sqlite3.h>
#include <signal.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <netinet/ip.h>
//信息结构体
typedef struct
{
char type; //选择
char name[64]; //用户名
char result[256]; // password密码 or word单词 or remark返回数据
} MSG_t;
int process_Register(int sockfd, MSG_t *msg);
int process_Login(int sockfd, MSG_t *msg);
int process_Search(int sockfd, MSG_t *msg);
int process_History(int sockfd, MSG_t *msg);
int main(int argc, char *argv[])
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);//创建网络通信接口
if(-1 == sockfd)
{
perror("socket");
return -1;
}
printf("sockfd=%d\n",sockfd);
// 2.填充服务器网络信息结构体
struct sockaddr_in addr;//服务器的地址结构
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;//指定协议族ipv4
addr.sin_port = htons(atoi(argv[2]));//端口号
addr.sin_addr.s_addr = inet_addr(argv[1]);//ip地址 点分式 -> 二进制网络字节序
int ret = connect(sockfd, (struct sockaddr *)&addr, sizeof(addr));//绑定 将服务器的ip和port与sockfd绑定
if(-1 == ret)
{
perror("connect");
close(sockfd);
return -1;
}
printf("connect success\n");
MSG_t msg;
char n;
while (1)
{
printf("\n************************************\n");
printf("* R: 注册 L: 登录 #: 退出 *\n");
printf("************************************\n");
printf("please choose : ");
if(scanf("%c", &n) <= 0)
{
perror("scanf");
exit(-1);
}
switch (n)
{
case 'R': //注册
process_Register(sockfd, &msg);
break;
case 'L'://登录
if( process_Login(sockfd, &msg) ==1 )
goto next;
break;
case '#':
close(sockfd);
exit(0);
default:
break;
}
getchar();
}
next:
while (1)
{
printf("\n************************************\n");
printf("*Q: 查询 H: 历史记录 #: 退出 *\n");
printf("************************************\n");
printf("please choose : ");
getchar();
if(scanf("%c", &n) <= 0)
{
perror("scanf");
exit(-1);
}
switch (n)
{
case 'Q': //查询
process_Search(sockfd, &msg);
break;
case 'H': //历史记录
process_History(sockfd, &msg);
break;
case '#':
close(sockfd);
exit(0);
default:
break;
}
}
close(sockfd);
return 0;
}
int process_Register(int sockfd, MSG_t *msg)
{
msg->type = 'R';
printf("please input user name: ");
scanf("%s", msg->name);
printf("please input user passwd: ");
scanf("%s", msg->result);
int ret = send(sockfd, msg, sizeof(MSG_t), 0);
if(ret <= 0)
{
perror("send");
return -1;
}
ret = recv(sockfd, msg->result, sizeof(MSG_t), 0);
if(ret <= 0)
{
perror("recv");
return -1;
}
printf("%s\n", msg->result);
return 0;
}
int process_Login(int sockfd, MSG_t *msg)
{
msg->type = 'L';
printf("please input user name: ");
scanf("%s", msg->name);
printf("please input user passwd: ");
scanf("%s", msg->result);
int ret = send(sockfd, msg, sizeof(MSG_t), 0);
if(ret <= 0)
{
perror("send");
return -1;
}
ret = recv(sockfd, msg->result, sizeof(MSG_t), 0);
if(ret <= 0)
{
perror("recv");
return -1;
}
printf("msg->result: %s\n", msg->result);
if(strcmp(msg->result, "login success") == 0)
{
return 1;
}
return 0;
}
int process_Search(int sockfd, MSG_t *msg)
{
msg->type = 'Q';
while(1)
{
printf("please input search word: ");
scanf("%s", msg->result);
if(strcmp(msg->result, "$") == 0)
break;
int ret = send(sockfd, msg, sizeof(MSG_t), 0);
if(ret == 0)
{
perror("send");
return -1;
}
ret = recv(sockfd, msg->result, sizeof(MSG_t), 0);
if(ret == -1)
{
perror("recv");
return -1;
}
printf("%s\n", msg->result);
}
return 0;
}
int process_History(int sockfd, MSG_t *msg)
{
msg->type = 'H';
int ret = send(sockfd, msg, sizeof(MSG_t), 0);
if(ret == -1)
{
perror("send");
return -1;
}
while(1)
{
ret = recv(sockfd, msg->result, sizeof(MSG_t), 0);
if(ret == -1)
{
perror("recv");
return -1;
}
if(strcmp(msg->result, "OVER") == 0)
break;
printf("%s\n", msg->result);
}
return 0;
}
四、结果
本文含有隐藏内容,请 开通VIP 后查看