TCP云词典项目是一个基于TCP协议的网络应用程序,旨在提供在线的词典查询功能。用户可以通过客户端与服务器建立连接,并在客户端输入要查询的单词,服务器将返回相应的释义和解释,而且应该支持多个客服端。
项目主要分为客户端和服务器两部分,其主要功能如下:
客户端功能:
服务器功能:
项目使用C语言编写,并涉及以下关键技术点:
客户端功能:
服务器功能:
通信协议:
数据存储:
异常处理和错误处理:
//srever_head.h 文件
#ifndef _HEAD_H_
#define _HEAD_H_
#include
#include /* See NOTES */
#include
#include
#include
#include
typedef struct mes
{
int zero_one;
int type;
char name[32];
char passwd[32];
char text[128];
} MSG_t;
enum Type
{
enroll,
login,
quit_login,
search,
history,
quit_search
};
typedef struct node
{
int fd;
char word[32];
struct node *next;
} snode;
MSG_t msg;
int enroll_server(int, MSG_t, sqlite3 *);//服务器处理注册
int login_server(int, MSG_t, sqlite3 *);//服务器处理登录
int search_server(int, MSG_t, sqlite3 *, char *);//服务器处理查找
int add_word(int, MSG_t, snode *);//添加单词到链表,用户客户查询历史记录
void enoll_feedback(int, sqlite3 *);//注册反馈
void login_feedback(int, sqlite3 *);//登录反馈
void search_feedback(int, sqlite3 *,snode *);//查找反馈
void history_feedback(int,snode *);//历史记录反馈
int show_history(snode *);
sqlite3 *mydtbase();//用户数据库
sqlite3 *dictionary();//词典数据库
snode *create_link();//存储客户客户查询单词的链表
#endif
//server_head.c 文件
#include
#include "server_head.h"
#include
#include /* See NOTES */
#include
#include
#include
#include
#include
#include
#include
#include
#include
int enroll_server(int acceptfd, MSG_t msg, sqlite3 *db)//注册函数
{
char buf[128] = {};
char *errmsg = NULL;
sprintf(buf, "insert into dic values(\"%s\",\"%s\");", msg.name, msg.passwd);
if (sqlite3_exec(db, buf, NULL, NULL, &errmsg))
{
printf("sqlist3_exec insert is err: %s\n", errmsg);
return -1;//注册失败返回值
}
return 0;//注册成功返回值
}
int login_server(int acceptfd, MSG_t msg, sqlite3 *db)//处理登录函数,返回值-1,代表失败;成功返回零
{
char **result;
int hang, lie;
char *errmsg = NULL;
char buf[128] = {};
sprintf(buf, "select * from dic where name=\"%s\" and passwd=\"%s\";", msg.name, msg.passwd);//查找用户表,查找对应的账号密码
if (sqlite3_get_table(db, buf, &result, &hang, &lie, &errmsg))
{
printf("sqlist3_get_table is err:%s\n", errmsg);
return -1;//查找失败代表没有注册或者密码错误
}
if (hang == 0 & lie == 0)
return -1;//查找行列值为零代表没有注册或者密码错误
return 0;//查找成功
}
int search_server(int acceptfd, MSG_t msg, sqlite3 *dicdb, char *explain)//查找单词函数
{
char **result;
int hang, lie;
char *errmsg = NULL;
char buf[128] = {};
sprintf(buf, "select * from dict where word=\"%s\";", msg.text);
if (sqlite3_get_table(dicdb, buf, &result, &hang, &lie, &errmsg))
{
printf("sqlist3_get_table is err:%s\n", errmsg);
return -1;
}
if (hang == 0 & lie == 0)
return -1;
if (hang != 1)
return -1;
strcpy(explain, result[3]);//拷贝到传入的数组里面。
return 0;
}
void enoll_feedback(int acceptfd, sqlite3 *db)//注册反馈函数
{
msg.zero_one = 0;//代表只能处理第一界面的输入
msg.type = enroll;
if (enroll_server(acceptfd, msg, db))//判断是否存在该用户
{
strcpy(msg.text, "Registration failed, user already exists");
send(acceptfd, &msg, sizeof(msg), 0);//返回错误信息
}
else
{
strcpy(msg.text, "enroll is success");
send(acceptfd, &msg, sizeof(msg), 0);//返回成功信息
}
}
void login_feedback(int acceptfd, sqlite3 *db)//登录反馈函数
{
if (login_server(acceptfd, msg, db))//判断账号密码合法性
{
strcpy(msg.text, "Login failed, please check your account or password");
msg.zero_one = 0;//登录失败,依然只能处理第一界面的输入
send(acceptfd, &msg, sizeof(msg), 0);
}
else
{
strcpy(msg.text, "login is success");
msg.zero_one = 1;//登录成功,代表可以进入第二界面,也就是查询单词或查询历史记录界面
msg.type = login;
send(acceptfd, &msg, sizeof(msg), 0);
}
}
void search_feedback(int acceptfd, sqlite3 *dicdb, snode *H)//查找反馈函数
{
char explain[128];
add_word(acceptfd, msg, H);
msg.type = search;
msg.zero_one = 1;
if (search_server(acceptfd, msg, dicdb, explain))//判断词典数据库是否存在该单词
{
strcpy(msg.text, "Search failed, you can use abbreviations");
send(acceptfd, &msg, sizeof(msg), 0);//返回错误信息
}
else
{
strcpy(msg.text, explain);//返回单词信息
send(acceptfd, &msg, sizeof(msg), 0);
}
}
void history_feedback(int acceptfd, snode *H)//反馈历史记录函数
{
msg.type = history;
msg.zero_one = 1;//代表只能处理第二界面的输入
snode *p = H;
if (H->next == NULL)
{
strcpy(msg.text, "You haven't searched for records yet!");//链表没节点,代表没有历史记录
send(acceptfd, &msg, sizeof(msg), 0);
}
while (p->next)
{
p = p->next;
strcpy(msg.text, p->word);
if(p->fd==acceptfd)//只反馈给自己
send(acceptfd, &msg, sizeof(msg), 0);
}
memset(msg.text, 0, sizeof(msg.text));//最后一次发送空,用于客户端判断是否发送结束,结束做相应的处理,打印帮助信息,否则一直打印查找记录
send(acceptfd, &msg, sizeof(msg), 0);
}
sqlite3 *dictionary()//词典数据库函数
{
sqlite3 *db = NULL;
int ret = sqlite3_open("./dict.db", &db);
if (ret != 0)
{
printf("sqlite3_open is err : %s\n", sqlite3_errmsg(db));
return NULL;
}
printf("sqlite3_open is success\n");
char *errmsg = NULL;
if (sqlite3_exec(db, "create table if not exists dict (word char,explain char);", NULL, NULL, &errmsg))
{
printf("sqlite3_exec create table is err: %s\n", errmsg);
return NULL;
}
printf("create word table is success\n");
return db;
}
sqlite3 *mydtbase()//用户信息数据库函数
{
sqlite3 *db = NULL;
int ret = sqlite3_open("./mydata.db", &db);
if (ret != 0)
{
printf("sqlite3_open is err : %s\n", sqlite3_errmsg(db));
return NULL;
}
printf("sqlite3_open is success\n");
char *errmsg = NULL;
if (sqlite3_exec(db, "create table if not exists dic (name char primary key,passwd char);", NULL, NULL, &errmsg))//如果不存咋创建,存在就打开,并且设置key值
{
printf("sqlite3_exec create table is err: %s\n", errmsg);
return NULL;
}
printf("create client table is success\n");
return db;
}
snode *create_link()//头节点函数(存储查找记录的链表)
{
snode *H;
H = (snode *)malloc(sizeof(snode));
if (H == NULL)
return NULL;
memset(H, 0, sizeof(snode));
H->next = NULL;
return H;
}
int add_word(int acceptdfd, MSG_t msg, snode *H)//加入单词记录
{
if (H == NULL)
{
perror("H is NULL");
return -1;
}
snode *p;
p = (snode *)malloc(sizeof(snode));
p->fd=acceptdfd;//保存文件描述符的值到节点
strcpy(p->word, msg.text);//存储单词的信息到节点上
p->next = H->next;
H->next = p;
return 0;
}
int show_history(snode *H)
{
if (H == NULL)
return -1;
}
//server.c 文件
#include
#include /* See NOTES */
#include
#include
#include
#include
#include
#include
#include
#include
#include "server_head.h"
#include
#include
MSG_t msg;
int main(int argc, char *argv[])
{
if (argc != 2)//参数判断
{
printf("Usage : %s \n", argv[0]);
return -1;
}
int sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket is err:");
return -1;
}
struct sockaddr_in addr, clientaddr;//定义两个结构体存值
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(atoi(argv[1]));
int addrlen = sizeof(addr);
if (bind(sockfd, (struct sockaddr *)&addr, addrlen))
{
perror("bind err:");
return -1;
}
if (listen(sockfd, 5))
{
perror("listen err:");
return -1;
}
struct pollfd fds[20];//采取poll函数
fds[0].fd = sockfd;
fds[0].events = POLLIN;
sqlite3 *db = mydtbase();//调用函数
sqlite3 *dicdb = dictionary();//调用函数
snode *H = create_link();//调用函数
int sum = 1;
while (1)
{
int ret = poll(fds, sum, -1);//循环监听
if (ret < 0)
{
printf("poll err:");
break;
}
else if (ret > 0)
{
for (int i = 0; i < sum; i++)
{
if (fds[i].revents == POLLIN)
{
if (fds[i].fd == sockfd)//加入新的客户端连接
{
int acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen);
if (acceptfd < 0)
{
perror("accept err:");
break;
}
printf("client%d ip :%s port :%d\n", acceptfd, inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
fds[sum].fd = acceptfd;//加入新的文件描述符
fds[sum].events = POLLIN;//加入事件
sum++;
break;
}
else
{
int ret = recv(fds[i].fd, &msg, sizeof(msg), 0);
if (ret < 0)
{
perror("recv err:");
break;
}
else if (ret == 0)//断开连接,修改监听的对象
{
close(fds[i].fd);
printf("client%d quit\n", fds[i].fd);
fds[i] = fds[sum - 1];
i--;
sum--;
}
else//判断接收到的信息的类型,做对应的事情
{
if (msg.type == enroll)
enoll_feedback(fds[i].fd, db);//注册反馈函数
else if (msg.type == login)
login_feedback(fds[i].fd, db);//登录反馈函数
else if (msg.type == search)
search_feedback(fds[i].fd, dicdb, H);//查找反馈函数
else if (msg.type == history)
history_feedback(fds[i].fd, H);//历史记录反馈函数
}
}
}
}
}
}
return 0;
}
//client.h 文件
#ifndef _CLIENT_H_
#define _CLIENT_H_
#include
#include /* See NOTES */
#include
#include
#include
#include
typedef struct mess
{
int zero_one;//代表当前处在的第几界面,第一界面是注册,登录等,第二界面是查找单词,历史记录等
int type;//消息类型
char name[32];
char passwd[32];
char text[128];
} MSG_tt;
enum Type
{
enroll,//注册
login,//登录
search,//查找
history,//历史记录
};
MSG_tt msg;
struct sockaddr_in addr;
void start_jm();//第一界面函数
void enroll_client(int);//注册函数
void login_client(int);//登录函数
void login_start();//第二界面函数
void search_client(int);//查找函数
void history_client(int);//历史记录函数
void login_feedback();//登录反馈函数
int if1(int);//三大判断
void if2(int);
void if3(int);
#endif
//client_head.c 文件
#include
#include /* See NOTES */
#include
#include
#include /* superset of previous */
#include
#include
#include
#include
#include
#include
#include
#include "client.h"
void enroll_client(int socketfd)//发送账号密码给服务器,用于服务器判断注册合法性
{
printf("please input your name:");
fgets(msg.name, sizeof(msg.name), stdin);
if (msg.name[strlen(msg.name) - 1] == '\n')
msg.name[strlen(msg.name) - 1] = '\0';
printf("please input your password:");
fgets(msg.passwd, sizeof(msg.passwd), stdin);
if (msg.passwd[strlen(msg.passwd) - 1] == '\n')
msg.passwd[strlen(msg.passwd) - 1] = '\0';
msg.zero_one = 0;//只能在第一界面
msg.type = enroll;
if (send(socketfd, &msg, sizeof(msg), 0) < 0)
{
perror("send err:");
return;
}
}
void login_client(int socketfd)//发送账号密码给服务器,用于服务器判断登录合法性
{
printf("please input your name:");
fgets(msg.name, sizeof(msg.name), stdin);
if (msg.name[strlen(msg.name) - 1] == '\n')
msg.name[strlen(msg.name) - 1] = '\0';
printf("please input your password:");
fgets(msg.passwd, sizeof(msg.passwd), stdin);
if (msg.passwd[strlen(msg.passwd) - 1] == '\n')
msg.passwd[strlen(msg.passwd) - 1] = '\0';
msg.zero_one = 0;//只能在第一界面
msg.type = login;
if (send(socketfd, &msg, sizeof(msg), 0) < 0)
{
perror("send err:");
return;
}
}
void search_client(int socketfd)//将输入的单词发送给客户端
{
printf("please input word:");
fflush(stdout);
fgets(msg.text, sizeof(msg.text), stdin);
if (msg.text[strlen(msg.text) - 1] == '\n')
msg.text[strlen(msg.text) - 1] = '\0';
msg.type = search;
msg.zero_one = 1;
if (send(socketfd, &msg, sizeof(msg), 0) < 0)
{
perror("send err:");
return;
}
}
void history_client(int socketfd)//发送查询历史记录请求给服务器
{
if (msg.text[strlen(msg.text) - 1] == '\n')
msg.text[strlen(msg.text) - 1] = '\0';
msg.type = history;
msg.zero_one = 1;
if (send(socketfd, &msg, sizeof(msg), 0) < 0)
{
perror("send err:");
return;
}
}
void start_jm()//第一界面
{
printf("\n");
printf("*********************************\n");
printf("***1.enroll 2.logon 3.quit***\n");
printf("*********************************\n");
printf("\n");
printf("Please choose num:");
fflush(stdout);//记得刷新缓冲区
}
void login_start()//第二界面
{
printf("\n");
printf("*********************************\n");
printf("**A.search B.history C.quit**\n");
printf("*********************************\n");
printf("\n");
printf("Please choose letter:");
fflush(stdout);//记得刷新缓冲区
}
void login_feedback()//登录反馈函数
{
if (msg.zero_one == 1)
{
printf("name:%s passwd:*** %s\n", msg.name, msg.text);
login_start();//登录成功进入第二界面
}
else
{
printf("%s\n", msg.text);
start_jm();//登陆失败依旧处于第一界面,并且打印错误信息
}
}
int if1(int socketfd)//判断键盘输入
{
if (strncmp(msg.text, "1", 1) == 0)
enroll_client(socketfd);//输入1注册
else if (strncmp(msg.text, "2", 1) == 0)
login_client(socketfd);//输入2登录
else if (strncmp(msg.text, "3", 1) == 0)
return 1;//输入3返回不为零的值,用于调用者判断退出程序
else
{
printf("Please enter according to the rules\n");//错误处理
start_jm();//将处在第一界面
}
return 0;
}
void if2(int socketfd)//判断键盘输入
{
if (strncmp(msg.text, "A", 1) == 0)
search_client(socketfd);//查找单词
else if (strncmp(msg.text, "B", 1) == 0)
history_client(socketfd);//查找历史记录
else if (strncmp(msg.text, "C", 1) == 0)
{
start_jm();//回到第一界面
msg.zero_one = 0;//将值置为0,表示回到第一界面
}
else
{
printf("Please enter according to the rules\n");//错误处理
login_start();//处在第二界面
}
}
void if3(int socketfd)
{
printf("Please enter according to the rules\n");
if (msg.zero_one == 0)
start_jm();//显示第一界面
if (msg.zero_one == 1)
login_start();//显示第二界面
}
//client.c 文件
#include
#include /* See NOTES */
#include
#include
#include /* superset of previous */
#include
#include
#include
#include
#include
#include
#include
#include "client.h"
void *pthread_read(void *arg)//线程处理收到的信息
{
int socketfd = *(int *)arg;
while (1)
{
int ret = recv(socketfd, &msg, sizeof(msg), 0);
if (ret < 0)
return NULL;
//判断收到的消息类型是那种,分别处理对应的事情
if (msg.type == enroll)//
{
printf("name:%s passwd:*** %s\n", msg.name, msg.text);
start_jm();
}
else if (msg.type == login)
login_feedback();//服务器反馈的登录信息
else if (msg.type == search)
{
printf("%s\n", msg.text);//打印单词信息
login_start();
}
else if (msg.type == history)
{
printf("%s\n", msg.text);//打印收到服务器的单词历史记录
if(strlen(msg.text)==0)
{
login_start();//当收到空,代表没有数据了,
}
}
}
pthread_exit(NULL);//结束线程
}
int main(int argc, char const *argv[])
{
int socketfd;
if (argc != 3)
{
printf("Useage:%s \n", argv[0]);
return -1;
}
socketfd = socket(AF_INET, SOCK_STREAM, 0);
if (socketfd < 0)
{
perror("socket err:");
return -1;
}
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_port = htons(atoi(argv[2]));
socklen_t addrlen = sizeof(addr);
if (connect(socketfd, (struct sockaddr *)&addr, addrlen) < 0)
{
perror("connect err:");
return -1;
}
pthread_t tid;
if (pthread_create(&tid, NULL, pthread_read, &socketfd))
{
perror("pthread_create err:");
return -1;
}
start_jm();//开始界面
msg.zero_one = 0;//刚开始处于第一界面
while (1)
{
fgets(msg.text, sizeof(msg), stdin);
msg.text[strlen(msg.text) - 1] = '\0';
if (msg.zero_one == 0)//处于第一界面,处理对应事件
{
if (if1(socketfd))
break;
}
else if (msg.zero_one == 1)//处于第二界面,处理对应事件
if2(socketfd);
else
if3(socketfd);//错误信息
}
printf("Thanks your use\n");
close(socketfd);
return 0;
}
客户端和服务器的通信协议:定义了明确的请求和响应格式,包括数据包的结构、字段含义、编码方式等。确保客户端和服务器能够正确解析和处理通信数据。
异常处理和错误提示:可以合理处理异常情况,例如网络连接失败、数据库访问失败等,给予用户友好的错误提示信息。
数据库设计和优化:设计了适当的数据库模式,确保存储和查询数据的高效性。使用索引、优化查询语句、避免重复数据等方法,提高查询性能。
并发处理和线程安全:采用线程栈存储变量,服务器同时处理多个客户端的请求,可以保证并发访问数据库的线程安全性。(个人采用的poll)
调试信息:测试阶段,编写详细的调试信息,助于快速定位和修复错误。
代码清晰和可维护性:编写清晰、模块化的代码,并且使用良好的注释和命名规范。
用户界面设计:考虑用户友好性和易用性,提供了清晰的指导和操作步骤。界面设计符合用户习惯和视觉美感,提供了良好的用户体验。