一、项目介绍
在线词典是基于TCP等网络协议族体系完成的一个小型的应用程序,能够完成一些简单的类似于实际生活中的登录,保存用户信息和使用历史记录一系列操作(信息都存放在事先创建好的数据库文件中)。“查询”作为该项目主要的应用价值,其他功能则为了方便用户使用。
二、功能说明
大方向一共四个功能:
注册
登录
查询单词
查询历史记录
三、功能逻辑以及逻辑图
该项目运用多进程实现TCP并发服务器的功能,当用户连接到该项目服务器时,反馈用户三个选项(注册,登录,退出),用户选择“注册”选项时,程序会让用户填写用户信息,并且比较新用户的信息和已经存在的用户的信息,如果已经存在,则会返回最初的三个选项让用户重新选择,否则,向数据库文件存入新用户的信息,再给用户反馈注册成功,并且再返回给用户三个选项(注册,登录,退出),如下图所示:
在用户选择“登录”操作时,服务器会让用户依次输入用户名和密码,用户名和密码都正确则跳转到下一个选项,否则会重新让用户输入。在用户登录成功之后,服务器会给用户发送三个选项(查询,历史记录,退出),用户输入“1”之后,跳转到查询模块,进行查询,该服务器模型可让用户进行任意次数的查询,每次成功查询到的字词都会记录到历史记录中,这些记录可退出查询功能之后,使用“历史记录”功能查看,如下图所示:
最后用户输入“#”则与服务器断开连接,退出在线词典。
项目逻辑图如下:
客户端:
服务器:
四 个人认为项目中比较精髓的代码及逻辑
int do_searchword(int acceptfd, MSG *msg)
{
FILE *fd;
char temp[300] = {0};
char *p=NULL;
int len, ret;
//单词长度
len = strlen(msg->date);
//打开文件
if (NULL == (fd = fopen("dict.txt", "r")))
{
strcpy(msg->date, "open dictionary file error");
if (-1 == send(acceptfd, msg, sizeof(MSG), 0))
{
ERRLOG("send error");
}
}
while (fgets(temp, 300, fd) != NULL)
{
//比较单词
ret = strncmp(msg->date, temp, len);
if (ret == 0 && temp[len] == ' ')
{
p = temp + len;
//移动p让*p指向解释的第一个字符
while (*p == ' ')
{
p++;
}
//将解释的首地址给数组
strcpy(msg->date, p);
fclose(fd);
return 1;
}
}
fclose(fd);
return 0;
}
在查询功能模块调用的do_searchword(int acceptfd, MSG *msg)中,fgets读取每一行内容(字典文件每一行大小不大于300字节),每读取一行就和msg里面的date比较“len”个字符,因为数组下标从0开始,而len是从一开始,所以当strncmp返回0,并且数组temp的temp[len]是' ',说明词典里的单词与用户查询的单词完全匹配(单词与解释之间存在大于零的若干个空格),temp表示数组首地址,加上len,p指针就指向了第一个空格的地址。这之后就让指针移动,直到指向的地址存放的数据不是空格,最后利用strcpy函数,让date数组的指针(数组名)指向p指针所在的地址,因为数组成员地址连续,所以首地址确认后,数组其他成员也确认了,首地址是解释的第一个字符,这样就达到了将解释内容放到msg数据包的目的了。
五 项目代码
客户端:
#include <head.h>
#define A 1
#define B 2
#define C 3
#define D 4
typedef struct
{
int type;
char name[32];
char date[256];
} MSG;
void signal_handler(int signum);
void do_clident(int acceptfd, sqlite3 *my_db);
void do_register(int acceptfd, MSG *msg, sqlite3 *my_db);
void do_login(int acceptfd, MSG *msg, sqlite3 *my_db);
void do_query(int acceptfd, MSG *msg, sqlite3 *my_db);
void do_history(int acceptfd, MSG *msg, sqlite3 *my_db);
int do_searchword(int acceptfd, MSG *msg);
void getdate(char *date);
int history_callback(void *arg, int f_num, char **f_value, char **f_name);
int main(int argc, const char *argv[])
{
int acceptfd;
pid_t pid;
sqlite3 *my_db = NULL;
char *errstr = NULL;
//打开数据库文件
int ret = sqlite3_open("my.db", &my_db);
if (ret != SQLITE_OK)
{
printf("errcode = [%d], errstr = [%s]\n", ret, sqlite3_errmsg(my_db));
exit(-1);
}
printf("打开数据文件成功\n");
//入参合理性检查
if (3 != argc)
{
printf("Usage : %s <IP> <PORT>\n", argv[0]);
exit(-1);
}
//创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
ERRLOG("socket error");
}
//填充服务器网络信息结构体
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t serveraddr_len = sizeof(serveraddr);
// bind绑定
if (-1 == bind(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len))
{
ERRLOG("bind error");
}
//将套接字设置为监听状态
if (-1 == listen(sockfd, 5))
{
ERRLOG("listen error");
}
signal(SIGUSR1, signal_handler);
//定义一个结构体保存客户端信息
struct sockaddr_in clientaddr;
memset(&clientaddr, 0, sizeof(clientaddr));
socklen_t clientaddr_len = sizeof(clientaddr);
while (1)
{
if (-1 == (acceptfd = accept(sockfd, NULL, NULL)))
{
ERRLOG("accept error");
}
if (-1 == (pid = fork()))
{
ERRLOG("fork error");
}
else if (pid == 0)
{
do_clident(acceptfd, my_db);
}
if (pid > 0)
{
close(acceptfd);
}
}
return 0;
}
void signal_handler(int signum)
{
if (signum == SIGUSR1)
{
wait(0);
}
}
void do_clident(int acceptfd, sqlite3 *my_db)
{
MSG msg;
while (recv(acceptfd, &msg, sizeof(MSG), 0) > 0)
{
printf("type = %d\n", msg.type);
printf("date = %s\n", msg.date);
switch (msg.type)
{
case A:
do_register(acceptfd, &msg, my_db);
break;
case B:
do_login(acceptfd, &msg, my_db);
break;
case C:
do_query(acceptfd, &msg, my_db);
break;
case D:
do_history(acceptfd, &msg, my_db);
break;
}
}
printf("client quit\n");
exit(0);
return;
}
void do_register(int acceptfd, MSG *msg, sqlite3 *my_db)
{
char sqlbuf[512] = {0};
char *errmsg = NULL;
//组装sql语句
sprintf(sqlbuf, "INSERT INTO usr VALUES('%s','%s')", msg->name, msg->date);
if (SQLITE_OK != sqlite3_exec(my_db, sqlbuf, NULL, NULL, &errmsg))
{
sprintf(msg->date, "usr[%s] already exist!", msg->name);
}
else
{
strcpy(msg->date, "OK");
}
if (-1 == send(acceptfd, msg, sizeof(MSG), 0))
{
ERRLOG("send error");
}
sqlite3_free(errmsg);
}
void do_login(int acceptfd, MSG *msg, sqlite3 *my_db)
{
char sqlbuf[512] = {0};
char *errmsg = NULL;
char **result = NULL;
int nrow, column;
//组装sql语句
sprintf(sqlbuf, "SELECT * FROM usr WHERE name='%s' AND date='%s' ", msg->name, msg->date);
if (sqlite3_get_table(my_db, sqlbuf, &result, &nrow, &column, &errmsg) != SQLITE_OK)
{
printf("error:%s\n", errmsg);
}
//通过nrow参数判断是否能够查询到疾记录,如果值为0,则查询不到,如果值为非0,则查询到
if (nrow == 0)
{
strcpy(msg->date, "name or password is wrony!!!");
}
else
{
strncpy(msg->date, "OK", 256);
}
sqlite3_free_table(result);
sqlite3_free(errmsg);
if (-1 == send(acceptfd, msg, sizeof(MSG), 0))
{
printf("send error\n");
}
return;
}
void do_query(int acceptfd, MSG *msg, sqlite3 *my_db)
{
char sqlbuf[128] = {0};
char *errmsg;
char word[128] = {0};
char time[128] = {0};
int falg = 0;
strcpy(word, msg->date);
falg = do_searchword(acceptfd, msg);
if (0 == falg)
{
strcpy(msg->date, "the word don't exist");
if (-1 == send(acceptfd, msg, sizeof(MSG), 0))
{
printf("send error");
}
return ;
}
if (1 == falg)
{
getdate(time);
//组装sql语句
sprintf(sqlbuf, "INSERT INTO record VALUES('%s','%s','%s')", msg->name, time, word);
if (sqlite3_exec(my_db, sqlbuf, NULL, NULL, &errmsg) != SQLITE_OK)
{
printf("errmsg:%s\n", errmsg);
sqlite3_free(errmsg);
}
}
if (-1 == send(acceptfd, msg, sizeof(MSG), 0))
{
printf("send error\n");
}
return;
}
void do_history(int acceptfd, MSG *msg, sqlite3 *my_db)
{
char sqlbuf[128] = {0};
char *errmsg;
//查询历史表
sprintf(sqlbuf, "SELECT * FROM record WHERE name='%s'", msg->name);
if (SQLITE_OK != sqlite3_exec(my_db, sqlbuf, history_callback, (void *)&acceptfd, &errmsg))
{
printf("errmsg:%s\n", errmsg);
sqlite3_free(errmsg);
}
//发送结束语
strcpy(msg->date, "**over**");
if (-1 == send(acceptfd, msg, sizeof(MSG), 0))
{
printf("send over error");
}
return;
}
int do_searchword(int acceptfd, MSG *msg)
{
FILE *fd;
char temp[300] = {0};
char *p=NULL;
int len, ret;
//单词长度
len = strlen(msg->date);
//打开文件
if (NULL == (fd = fopen("dict.txt", "r")))
{
strcpy(msg->date, "open dictionary file error");
if (-1 == send(acceptfd, msg, sizeof(MSG), 0))
{
ERRLOG("send error");
}
}
while (fgets(temp, 300, fd) != NULL)
{
//比较单词
ret = strncmp(msg->date, temp, len);
if (ret == 0 && temp[len] == ' ')
{
p = temp + len;
//移动p让*p指向解释的第一个字符
while (*p == ' ')
{
p++;
}
//将解释的首地址给数组
strcpy(msg->date, p);
fclose(fd);
return 1;
}
}
fclose(fd);
return 0;
}
void getdate(char *date)
{
time_t t;
struct tm *tp;
time(&t);
tp = localtime(&t);
sprintf(date, "%04d-%02d-%02d %02d:%02d:%02d", 1900 + tp->tm_year, 1 + tp->tm_mon, tp->tm_mday,\
tp->tm_hour, tp->tm_min, tp->tm_sec);
}
int history_callback(void *arg, int f_num, char **f_value, char **f_name)
{
int acceptfd;
MSG msg;
acceptfd = *(int *)arg;
sprintf(msg.date, "%s :%s", f_value[1], f_value[2]);
if (-1 == send(acceptfd, &msg, sizeof(MSG), 0))
{
ERRLOG("send error");
}
return 0;
}
服务器:
#include <head.h>
#define A 1
#define B 2
#define C 3
#define D 4
typedef struct
{
int type;
char name[32];
char date[256];
} MSG;
void do_register(int sockfd, MSG *msg);
int do_login(int sockfd, MSG *msg);
void do_query(int sockfd, MSG *msg);
void do_history(int sockfd, MSG *msg);
int main(int argc, const char *argv[])
{
//入参合理性检查
if (3 != argc)
{
printf("Usage : %s <IP> <PORT>\n", argv[0]);
exit(-1);
}
// 1.创建流式套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
ERRLOG("socket error");
}
// 2.填充服务器的网络信息结构体
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t serveraddr_len = sizeof(serveraddr);
// 3.尝试与服务器建立连接
if (-1 == connect(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len))
{
ERRLOG("connect error");
}
printf("与服务器建立连接成功..\n");
int n;
MSG msg;
while (1)
{
printf("****************************************\n");
printf("*1: register 2: login 3: quit *\n");
printf("****************************************\n");
printf("plese choose:");
if (scanf("%d", &n) <= 0)
{
printf("输入有误\n");
return -1;
}
switch (n)
{
case 1:
do_register(sockfd, &msg);
break;
case 2:
if (do_login(sockfd, &msg) == 1)
{
goto next;
}
break;
case 3:
close(sockfd);
exit(0);
default:
break;
}
}
next:
while(1)
{
printf("****************************************\n");
printf("*1: query 2: history 3: quit *\n");
printf("****************************************\n");
printf("plese choose:");
if (scanf("%d", &n) <= 0)
{
printf("输入有误\n");
return -1;
}
switch (n)
{
case 1:
do_query(sockfd, &msg);
break;
case 2:
do_history(sockfd, &msg);
break;
case 3:
close(sockfd);
exit(0);
}
}
return 0;
}
void do_register(int sockfd, MSG *msg)
{
//确认操作码
msg->type = A;
//输入用户名和密码
printf("请输入用户名:");
scanf("%s", msg->name);
printf("请输入密码:");
scanf("%s", msg->date);
//收发数据
if (-1 == send(sockfd, msg, sizeof(MSG), 0))
{
ERRLOG("send error");
}
if (-1 == recv(sockfd, msg, sizeof(MSG), 0))
{
ERRLOG("recv error");
}
printf("register [%s]:[%s]\n", msg->name, msg->date);
}
int do_login(int sockfd, MSG *msg)
{
msg->type = B;
printf("请输入用户名:");
scanf("%s", msg->name);
printf("请输入密码:");
scanf("%s", msg->date);
if(-1 ==send(sockfd,msg,sizeof(MSG),0)){
ERRLOG("send error");
}
if(-1 ==recv(sockfd,msg,sizeof(MSG),0)){
ERRLOG("recv error");
}
if(0 == strncmp(msg->date,"OK",3)){
printf("login:OK\n");
return 1;
}
printf("login:[%s]\n",msg->date);
return 0;
}
void do_query(int sockfd, MSG *msg)
{
//确认操作码
msg->type = C;
puts("-----------------------------");
//输入要查询的信息
while (1)
{
printf("请输入要查询的信息(输入*退出):");
scanf("%s", msg->date);
//输入#就退出查询
if (0 == strcmp(msg->date, "*"))
{
break;
}
//与服务器进行交流
if (-1 == send(sockfd, msg, sizeof(MSG), 0))
{
printf("send error");
}
if (-1 == recv(sockfd, msg, sizeof(MSG), 0))
{
printf("recv error");
}
//打印服务器返回的信息
printf(":%s\n", msg->date);
}
return;
}
void do_history(int sockfd, MSG *msg)
{
//确认操作码
msg->type = D;
if (-1 == send(sockfd, msg, sizeof(MSG), 0))
{
printf("send error");
}
while (1)
{
if (-1 == recv(sockfd, msg, sizeof(MSG), 0))
{
printf("recv error");
}
if (0 == strcmp(msg->date, "**over**"))
{
break;
}
printf("%s\n", msg->date);
}
}
head.h
#ifndef __HEAD_H__
#define __HEAD_H__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <sys/wait.h>
#include <dirent.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/un.h>
#include <sqlite3.h>
#define ERRLOG(msg) \
do { \
printf("%s %s %d\n", __FILE__, __func__, __LINE__);\
perror(msg); \
exit(-1); \
} while (0)
#endif