将单词文本文件导入数据库表中
https://editor.csdn.net/md/?articleId=131514974
该项目代码文件:
https://download.csdn.net/download/m0_52857070/87989479
一、项目运行效果展示
1、编译
1.1 服务器端:
1.2 客户端
2、客户端
2.1 注册register
2.2 登录login
2.3 单词查询word(输入wordend退出单词查询)
2.4 历史查询myhistory
2.5 退出查询界面
2.6 退出quit
3、服务器端
二、项目实现流程
1、服务器端
1.1 服务端整体流程图
1.2 服务器端注册流程图
1.3 服务器端登录流程图
2、客户端
2.1客户端流程图
三、代码分析
1、客户端
第一步:创建套接字
第二步:填充服务器端结构体
第三步:连接服务器
第四步:循环输入命令
如果是注册命令,将注册命令发送给服务器,调用注册函数
如果是登录命令,将登录命令发送给服务器,调用登录函数
如果是退出命令,则将退出命令发送给服务器,break跳出死循环,结束进程
1.1 main函数
第一步:
int main(int argc, char const *argv[])
{
COMMUN_T com;
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket err");
return -1;
}
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[2]));
saddr.sin_addr.s_addr = inet_addr(argv[1]);
if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
{
perror("connect err");
return -1;
}
printf("connect ok\n");
while (1)
{
printf("\n请输入命令:注册(register),登录(login),退出(quit):");
fgets(com.type, sizeof(com.type), stdin);
if (com.type[strlen(com.type) - 1] == '\n')
com.type[strlen(com.type) - 1] = '\0';
if (strcmp(com.type, "register") == 0)
{
//printf("register\n");
send(sockfd, &com, sizeof(com), 0);
myregister(sockfd);
}
else if (strcmp(com.type, "login") == 0)
{
//printf("login\n");
send(sockfd, &com, sizeof(com), 0);
mylogin(sockfd);
}
else if (strcmp(com.type, "quit") == 0)
{
//printf("quit\n");
send(sockfd, &com, sizeof(com), 0);
break;
}
else
{
printf("输入的命令有误\n");
}
}
close(sockfd);
return 0;
}
1.2 注册函数
第一步:获取用户名与密码
第二步:将用户名与密码结构体发送给服务器端
第三步:等待接收服务器端发送过来的注册状态
如果收到registersuccess,则注册成功,打印“注册成功”,退出
如果收到registered,则证明已注册,打印“已经注册过”,退出
void myregister(int sockfd)
{
//printf("myregister\n");
char buf[320];
USER_T myuser;
while (1)
{
printf("请输入用户名: ");
fgets(myuser.name, sizeof(myuser.name), stdin);
if (myuser.name[strlen(myuser.name) - 1] == '\n')
myuser.name[strlen(myuser.name) - 1] = '\0';
printf("请输入密码: ");
fgets(myuser.passwd, sizeof(myuser.passwd), stdin);
if (myuser.passwd[strlen(myuser.passwd) - 1] == '\n')
myuser.passwd[strlen(myuser.passwd) - 1] = '\0';
send(sockfd, &myuser, sizeof(myuser), 0);
recv(sockfd, buf, sizeof(buf), 0);
//printf("buf:%s\n", buf);
if (strcmp(buf, "registersuccess") == 0)
{
printf("注册成功\n");
break;
}
else if (strcmp(buf, "registered") == 0)
{
printf("已经注册过\n");
break;
}
else
{
printf("其他未知错误\n");
}
}
}
1.3 登录函数
第一步:获取用户名与密码
第二步:发送给服务器
第三步:等待接收服务器发送过来的登录状态
1)如果收到loginsuccess,说明登录成功,跳出本循环
2)如果收到noregister,说明该用户还没有注册,函数返回
3)如果收到passwderr,说明密码错误,继续输入用户名与密码
第四步:如果登录成功,进入查询界面循环
第五步:输入那种查询方式
1)单词查询word ,则进入单词查询界面,获取要查询的单词,并发送给服务器,等待接收服务器发送过来的单词解释
2)如果历史查询myhistory,进入历史查询界面,接收服务器端发送过来的该用户历史查询记录,直到收到historyend,结束循环接收,退出历史查询
第六步:如果输入#,退出查询界面
void mylogin(int sockfd)
{
//printf("mylogin\n");
char buf[320];
USER_T myuser;
COMMUN_T com;
HISTORY_T his_list;
while (1)
{
printf("请输入用户名: ");
fgets(myuser.name, sizeof(myuser.name), stdin);
if (myuser.name[strlen(myuser.name) - 1] == '\n')
myuser.name[strlen(myuser.name) - 1] = '\0';
printf("请输入密码: ");
fgets(myuser.passwd, sizeof(myuser.passwd), stdin);
if (myuser.passwd[strlen(myuser.passwd) - 1] == '\n')
myuser.passwd[strlen(myuser.passwd) - 1] = '\0';
send(sockfd, &myuser, sizeof(myuser), 0);
recv(sockfd, buf, sizeof(buf), 0);
// printf("buf:%s\n", buf);
if (strcmp(buf, "loginsuccess") == 0)
{
printf("登录成功\n");
break;
}
else if (strcmp(buf, "passwderr") == 0)
{
printf("密码错误\n");
}
else if (strcmp(buf, "noregister") == 0)
{
printf("没有注册\n");
return;
}
}
while (1)
{
printf("\n请输入采取那种查询方式:单词查询(word),历史查询(myhistory) 退出查询界面(#):");
fgets(com.type, sizeof(com.type), stdin);
if (com.type[strlen(com.type) - 1] == '\n')
com.type[strlen(com.type) - 1] = '\0';
// printf("请输入用户名:");
// fgets(com.name, sizeof(com.name), stdin);
// if (com.name[strlen(com.name) - 1] == '\n')
// com.name[strlen(com.name) - 1] = '\0';
send(sockfd, &com, sizeof(com), 0);
//单词查询
if (strcmp(com.type, "word") == 0)
{
// printf("word\n");
while (1)
{
printf("\n请输入要查询的单词\n");
fgets(com.data, sizeof(com.data), stdin);
if (com.data[strlen(com.data) - 1] == '\n')
com.data[strlen(com.data) - 1] = '\0';
send(sockfd, &com, sizeof(com), 0);
if (strcmp(com.data, "wordend") == 0)
break;
recv(sockfd, buf, sizeof(buf), 0);
printf("单词解释:%s\n", buf);
}
}
//历史查询
else if (strcmp(com.type, "myhistory") == 0)
{
// printf("history\n");
while (1)
{
recv(sockfd, &his_list, sizeof(his_list), 0);
if (strcmp(his_list.word, "historyend") == 0)
break;
printf("username:%s time: %s word: %s \n", his_list.name, his_list.usertime, his_list.word);
}
// printf("historyok\n");
}
else if(strcmp(com.type,"#")==0)
break;
else
{
printf("输入的命令有误\n");
}
}
}
2、服务器端
2.1 main函数
第一步:创建数据库
第二步:创建用户注册表、历史查询记录表、单词表(采用另外的程序添加进入的)
第三步:创建套接字
第四步:填充服务器端结构体
第五步:绑定
第六步:监听
第七步:捕捉ctrl+c信号,关闭本进程打开的所有文件、数据库
第八步:创建IO多路复用表
1)如果有连接请求,则调用accept函数,与客户端建立连接,并将生成的通信文件描述符添加到IO多路服用表
2)如果有通信请求,接收客户端发送过来的命令,
如果是注册命令,则调用注册函数
如果是登录命令,则调用登录函数,
如果接收到的是退出命令,则关闭与该客户端同学的文件描述符,并把它从IO多路复用表中删除
int main(int argc, char const *argv[])
{
COMMUN_T com;
//创建库
if (sqlite3_open("./CloudDictionary.db", &db) != 0)
{
fprintf(stderr, "sqlite3_open err:%s\n", sqlite3_errmsg(db));
}
//创建注册表\历史查询表
char *errmsg = NULL;
if (sqlite3_exec(db, "create table user(name char primary key,passwd char);", NULL, NULL, &errmsg) != 0)
{
fprintf(stderr, "sqlite3_exec err:%s\n", sqlite3_errmsg(db));
}
if (sqlite3_exec(db, "create table historySearch(name char,time char,word char);", NULL, NULL, &errmsg) != 0)
{
fprintf(stderr, "sqlite3_exec err:%s\n", sqlite3_errmsg(db));
}
//打开字典文件
//tcp 通信
sockfd = socket(AF_INET, SOCK_STREAM, 0); // 要放到创建数据库前面,数据库会导致acceptfd从6开始
if (sockfd < 0)
{
perror("socket err");
return -1;
}
printf("sockfd:%d\n", sockfd);
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[1]));
saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
{
perror("bind err");
return -1;
}
if (listen(sockfd, 8) < 0)
{
perror("listen err");
return -1;
}
signal(SIGINT, hander); //捕捉ctrl+c信号
FD_ZERO(&readfds);
// FD_SET(0, &readfds);
FD_SET(sockfd, &readfds);
int maxfd = sockfd;
while (1)
{
tempfds = readfds;
int ret = select(maxfd + 1, &tempfds, NULL, NULL, NULL);
if (ret < 0)
{
perror("select err");
return -1;
}
if (FD_ISSET(sockfd, &tempfds))
{
int acceptfd = accept(sockfd, NULL, NULL);
if (acceptfd < 0)
{
perror("acceptfd err");
return -1;
}
printf("acceptfd:%d\n", acceptfd);
FD_SET(acceptfd, &readfds);
if (maxfd < acceptfd)
maxfd = acceptfd;
}
for (int i = sockfd+1; i <= maxfd; i++) //acceptfd从6开始分配的,要是不合适的会报找不到传输节点错误
{
if (FD_ISSET(i, &tempfds))
{
int retebyte = recv(i, &com, sizeof(com), 0);
if (retebyte < 0)
{
perror("recv err");
return -1;
}
else if (retebyte == 0)
{
close(i);
FD_CLR(i, &readfds);
if (maxfd == i)
maxfd--;
}
else
{
if (strcmp(com.type, "register") == 0)
{
// printf("register\n");
myregister(i);
}
else if (strcmp(com.type, "login") == 0)
{
// printf("login\n");
mylogin(i);
}
else if (strcmp(com.type, "quit") == 0)
{
printf("%d 客户端退出\n", i);
close(i);
FD_CLR(i, &readfds);
if (maxfd == i)
maxfd--;
continue;
}
}
}
}
}
close(sockfd);
return 0;
}
2.2 注册函数
第一步:接收客户端发送过来的用户名和密码结构体
第二步:判断该用户名在数据库中是否存在
如果存在,给客户端发送registered,跳出循环
如果不存在,插入成功的话,会给客户端发送registersuccess,跳出循环
注意:
1)这里我在创建用户注册表时,把用户名当成了关键字,如果注册的用户名存在,则系统会报“UNIQUE constraint failed: user.name”,根据这个来判断是否已经注册
2)如果errmsg==NULL的话,说明成功插入,这个需要这样判断,因为NULL不能用strcmp与任何字符串判断,会报段错误
//1\注册
void myregister(int acceptfd)
{
//printf("myregister\n");
char buf[320];
USER_T myuser;
char sql[320];
while (1)
{
int retebyte = recv(acceptfd, &myuser, sizeof(myuser), 0);
if (retebyte < 0)
{
perror("recv err");
return;
}
else if (retebyte > 0)
{
printf("用户%s注册\n", myuser.name);
errmsg = NULL;
sprintf(sql, "insert into user values(\"%s\",\"%s\");", myuser.name, myuser.passwd);
//printf("sql:%s\n", sql);
if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != 0)
{
fprintf(stderr, "sqlite3_exec err:%s\n", errmsg);
// return;
}
// printf("----------------errmsg:%s----------------\n", errmsg);
if (NULL == errmsg)
{
strcpy(buf, "registersuccess");
printf("%s 用户注册成功\n\n", myuser.name);
send(acceptfd, buf, sizeof(buf), 0);
// printf("----------------------bbbbbbb----------\n");
break;
}
else
{
if (strcmp(errmsg, "UNIQUE constraint failed: user.name") == 0) //errmsg没有错误时为NULL,与字符串比较出现段错误
{
//printf("rerf\n");
strcpy(buf, "registered");
send(acceptfd, buf, sizeof(buf), 0);
// printf("--------------------------------aaaaa\n");
break;
}
}
}
}
}
2.3 登录函数
第一步:接收客户端发送过来的用户名与密码结构体
第二步:去数据库用户注册表中查找该用户名,
1)如果返回的行数=1的话,说明该用户已经注册过。再进一步判断密码是否正确。
如果正确的话,则登录成功,给客户端发送“loginsuccess”,跳出这个循环。
如果错误的话,会给客户端发送“passwderr”,继续接收客户端发送过来的用户名与密码
2)如果返回的行数!=1的话,行数返回只能是1或0(用户名是关键字,一个用户名最多一行),说明该用户还没有注册。给客户端发送“noregister”,函数返回。
第三步:如果登录成功,则进入查询界面
第四步:接收客户端发送过来的查询命令
1)如果是单词查询,则接收客户端发送过来的单词,利用函数sqlite3_get_table()去数据库单词表中查找该单词,找到后,把单词意思发送给客户端,并利用sqlite3_exec()把该用户查询记录(用户名、查询时间、查询单词)插入到历史查询记录表中。
如果接收到“wordend”,则结束单词查询
将单词文本文件导入数据库表中
https://editor.csdn.net/md/?articleId=131514974
2)如果是历史查询,则去查找该用户名的历史查询记录表,并把查询记录逐条发送给客户端。发送结束后,发送结束发送标志“historyend”给客户端。
3)如果接收到#,则break跳出这个死循环,退出查询界面
//2\登录
void mylogin(int acceptfd)
{
//printf("mylogin\n");
char buf[320];
USER_T myuser;
COMMUN_T com;
char sql[320];
char **resultp; //在get_table函数中是一维指针数组,数组首地址是二级指针
int nrow; //在get_table函数里自动获取到符合条件的行数
int ncolumn; // 在get_table函数里自动获取到符合条件的列数
int k = 0;
int retebyte;
time_t ti;
struct tm loc_time;
struct tm *my_loctime = &loc_time;
char mytime[256];
HISTORY_T his_list;
while (1)
{
retebyte = recv(acceptfd, &myuser, sizeof(myuser), 0);
if (retebyte < 0)
{
perror("recv err");
return;
}
else if (retebyte > 0)
{
printf("用户%s登录\n", myuser.name);
k = 0; //k登录一次后k=3 ,在自加resultp数组越界,需要重新置0
//1\登录
sprintf(sql, "select * from user where name=\"%s\";", myuser.name);
//printf("sql:%s\n", sql);
if (sqlite3_get_table(db, sql, &resultp, &nrow, &ncolumn, &errmsg) != 0)
{
fprintf(stderr, "select err:%s", errmsg);
return;
}
//printf("nrow:%d\n", nrow);
// for (int i = 0; i < nrow + 1; i++)
// {
// for (int j = 0; j < ncolumn; j++)
// {
// printf("%s ", resultp[k++]);
// }
// putchar(10);
// }
if (nrow == 1)
{
if (strcmp(resultp[3], myuser.passwd) == 0)
{
//需要加\n刷新缓存区
printf("%s 用户登录成功\n\n", myuser.name);
strcpy(buf, "loginsuccess");
send(acceptfd, buf, sizeof(buf), 0);
break;
}
else
{
//printf("user name:%s passwderr\n", myuser.name);
strcpy(buf, "passwderr");
send(acceptfd, buf, sizeof(buf), 0);
}
}
else if (nrow == 0)
{
// printf("user name:%s noregister\n", myuser.name);
strcpy(buf, "noregister");
send(acceptfd, buf, sizeof(buf), 0);
return;
}
}
}
while (1)
{
//2\查询方式
retebyte = recv(acceptfd, &com, sizeof(com), 0);
if (retebyte < 0)
{
perror("recv err");
return;
}
else if (retebyte > 0)
{
if (strcmp(com.type, "word") == 0)
{
//printf("word\n");
while (1)
{
//3\单词查询
retebyte = recv(acceptfd, &com, sizeof(com), 0);
if (retebyte < 0)
{
perror("recv err");
return;
}
else if (retebyte > 0)
{
printf("用户%s单词查询\n\n", myuser.name);
printf("单词查询中\n");
if (strcmp(com.data, "wordend") == 0)
break;
bzero(sql, sizeof(sql));
k = 0;
nrow = 0;
ncolumn = 0;
sprintf(sql, "select * from dictxt where a=\"%s\";", com.data);
//printf("sql:%s\n", sql);
if (sqlite3_get_table(db, sql, &resultp, &nrow, &ncolumn, &errmsg) != 0)
{
fprintf(stderr, "select err:%s", errmsg);
return;
}
// printf("nrow:%d\n", nrow);
// for (int i = 0; i < nrow + 1; i++)
// {
// for (int j = 0; j < ncolumn; j++)
// {
// printf("%s ", resultp[k++]);
// }
// putchar(10);
// }
strcpy(buf, resultp[3]);
send(acceptfd, buf, sizeof(buf), 0);
//写历史查询表
bzero(sql, sizeof(sql));
time(&ti);
my_loctime = localtime(&ti);
sprintf(mytime, "%d-%d-%d %d:%d:%d", my_loctime->tm_year + 1900, my_loctime->tm_mon + 1, my_loctime->tm_mday, my_loctime->tm_hour, my_loctime->tm_min, my_loctime->tm_sec);
// printf("mytime:%s\n", mytime);
sprintf(sql, "insert into historySearch values(\"%s\",\"%s\",\"%s\");", myuser.name, mytime, com.data);
//printf("sql:%s\n", sql);
if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != 0)
{
fprintf(stderr, "sqlite3_exec err:%s\n", errmsg);
printf("sqlite3_exec\n");
// return;
}
// printf("history ok\n");
}
}
}
//4\历史查询
else if (strcmp(com.type, "myhistory") == 0)
{
printf("用户%s历史记录查询\n\n", myuser.name);
bzero(sql, sizeof(sql));
k = 3;
nrow = 0;
ncolumn = 0;
int j;
sprintf(sql, "select * from historySearch where name=\"%s\";", myuser.name);
// printf("sql:%s\n", sql);
if (sqlite3_get_table(db, sql, &resultp, &nrow, &ncolumn, &errmsg) != 0)
{
fprintf(stderr, "select err:%s", errmsg);
return;
}
//printf("nrow:%d\n", nrow);
for (int i = 1; i < nrow + 1; i++)
{
// if (i == 0)
// continue; //把表头去掉,要是i=0,k=3开始,有段错误,不去表头,因为resultp把表头去了,所以多遍历一行,会导致段错误
for (j = 0; j < ncolumn; j++)
{
// printf("%s ", resultp[k++]);
if (j == 0)
strcpy(his_list.name, resultp[k++]); //k从3开始
else if (j == 1)
strcpy(his_list.usertime, resultp[k++]);
else if (j == 2)
strcpy(his_list.word, resultp[k++]);
}
//putchar(10);
send(acceptfd, &his_list, sizeof(his_list), 0);
}
strcpy(his_list.word, "historyend");
send(acceptfd, &his_list, sizeof(his_list), 0);
// printf("historyend\n");
// break;
}
else if(strcmp(com.type,"#")==0)
{
printf("%s 用户退出查询界面\n",myuser.name);
break;
}
}
// printf("end\n");
}
}