1. 项目概述
在线词典项目是在linux系统下结合c语言、网络编程和sqlite3数据库等技术来实现的一个在线的英译英单词解释,当客户端成功连接到服务器后,客户端登录账号后输入想查询的单词,服务器端会在词库(网上找的)中寻找这个单词的解释并发送给客户端,从而实现在线查询。服务端同时也会向数据库存储该用户的一个查询记录,方便管理的同时,也方便客户端查询。
2. 实现的功能
用户注册和登录验证,服务器端将用户信息和历史记录保存在数据库中。
客户端输入用户名和密码,服务器端在数据库中查找、匹配,返回查询结果
根据客户端输入的单词在字典文件中搜索,将查询结果返回
历史记录查询
3. 代码实现
3.1 服务器
主函数和一些基本信息:这部分定义了一个双方通信的信息结构体,其中type是R时,表示客户端发来了注册请求;是L时,客户端发来了登录请求;是Q时,客户端发来了查询请求;是H时,客户端发来了观看历史请求。主函数实现的就是打开后台数据库,创建进程,与客户端成功连接后,子进程完成与对端的通信任务,父进程则继续监听与客户端的连接。这样就实现了服务器的一个并发功能,即同时可以连接处理多个客户端。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| #define N 32 #define R 1 #define L 2 #define Q 3 #define H 4 #define DATABASE "my.db"
typedef struct { int type; char name[N]; char data[256]; }MSG;
int main(int argc, const char *argv[]){ int sockfd; struct sockaddr_in serveraddr; MSG msg; int acceptfd; pid_t pid; sqlite3 *db; if(argc != 3){ printf("Usage:%s serverip port.\n",argv[0]); return -1; } if(sqlite3_open(DATABASE, &db) != SQLITE_OK){ printf("%s\n", sqlite3_errmsg(db)); return -1; }else{ printf("open DATABASE success\n"); } sockfd = socket(AF_INET,SOCK_STREAM,0); if(sockfd < 0){ perror("fail to socket...\n"); return -1; } bzero(&serveraddr,sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(atoi(argv[2])); serveraddr.sin_addr.s_addr = inet_addr(argv[1]); int opt = 1; setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))<0){ perror("fail to bind...\n"); return -1; } if(listen(sockfd, 5)<0){ printf("fail to listen...\n"); return -1; } signal(SIGCHLD, SIG_IGN); while(1){ if((acceptfd = accept(sockfd, NULL, NULL)) < 0){ perror("fail to accept"); return -1; } if((pid = fork())<0){ perror("fail to fork"); return -1; } else if(pid == 0){ close(sockfd); do_client(acceptfd, db); } else{ close(acceptfd); } } return 0; }
|
通信函数:当服务器与客户端成功连接后,等待接收客户端发来的信息,通过发来信息的不同请求,服务器执行不同的处理函数,处理完后,继续循环监听接收客户端发的信息,当客户端断开连接后,服务器就会退出监听接收循环,从而退出与该客户端的通信函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| int do_client(int acceptfd, sqlite3 *db){ MSG msg; while(recv(acceptfd, &msg, sizeof(msg), 0) > 0){ printf("type:%d\n", msg.type); switch(msg.type){ case R: do_register(acceptfd, &msg, db); break; case L: do_login(acceptfd, &msg, db); break; case Q: do_query(acceptfd, &msg, db); break; case H: do_history(acceptfd, &msg, db); break; default: printf("Invalid data msg...\n"); } } printf("client exit...\n"); close(acceptfd); exit(0); return 0; }
|
这部分实现的就是处理客户端的注册请求任务,将客户端发送来的用户名和密码存储在后台的数据库中,服务器如果存储失败,则返回给客户端注册失败的信息;存储成功,则返回给客户端ok
,表示注册成功。最后执行完该函数后,会继续监听该客户端的下一个请求任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| void do_register(int acceptfd, MSG *msg, sqlite3 *db){ char *errmsg; char sql[128]; sprintf(sql, "insert into usr values('%s', %s);",msg->name, msg->data); printf("%s\n", sql); if(sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK){ printf("%s\n", errmsg); strcpy(msg->data, "usr name already exist..."); }else{ printf("client register ok!\n"); strcpy(msg->data, "OK!"); } if(send(acceptfd, msg, sizeof(MSG), 0) < 0){ perror("fail to send"); return; } return; }
|
这部分实现的就是处理客户端的登录请求任务,将客户端发送来的用户名和密码在后台数据库中的用户名usr表中查询,如果查询到了,则返回给客户端ok
,表示登录成功;没有查询到,则发送usr/passwd wrong...
给客户端,表示登录失败。最后执行完该函数后,会继续监听该客户端的下一个请求任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| int do_login(int acceptfd, MSG *msg, sqlite3 *db){ char sql[128] = {}; char *errmsg; int nrow; int ncloumn; char **resultp; sprintf(sql, "select * from usr where name = '%s' and pass = '%s';",msg->name,msg->data); 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(msg->data,"OK"); send(acceptfd, msg, sizeof(MSG), 0); return 1; } if(nrow == 0){ strcpy(msg->data,"usr/passwd wrong..."); send(acceptfd, msg, sizeof(MSG), 0); } return 0; }
|
搜索单词函数实现:这部分实现的就是处理客户端的查询单词的请求任务,通过客户端发送来的单词,服务器在词库中进行逐一查询,查询成功就返回单词的注释给客户端,查询失败,就返回给客户端信息Not dound!
。同时,服务器也会在数据库中记录该用户的查询记录(用户名,查询时间,查询单词)。最后执行完该函数后,会继续监听该客户端的下一个请求任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| int do_query(int acceptfd, MSG *msg, sqlite3 *db){ char word[64]; int found = 0; char date[128]; char sql[128]; char *errmsg; strcpy(word, msg->data); found = do_searchword(acceptfd, msg, word); if(found == 1){ get_date(date); sprintf(sql,"insert into record values('%s','%s','%s')",msg->name,date,word); printf("%s\n",sql); if(sqlite3_exec(db,sql,NULL,NULL,&errmsg) != SQLITE_OK){ printf("%s\n",errmsg); return -1; } }else{ strcpy(msg->data, "Not dound!"); } send(acceptfd, msg, sizeof(MSG),0); return 1; }
int do_searchword(int acceptfd, MSG *msg, char word[]){ FILE *fp; char temp[512] = {}; int result; char *p; if((fp = fopen("dict.txt", "r"))==NULL){ perror("fail to fopen...\n"); strcpy(msg->data, "failed to open dict.txt"); send(acceptfd, msg, sizeof(MSG),0); return -1; } int len = strlen(word); printf("%s, len = %d\n", word, len); while(fgets(temp, 512, fp) != NULL){ result = strncmp(temp,word,len); if(result < 0){ continue; } if(result>0 || ((result == 0) && (temp[len]!=' '))){ break; } p = temp + len; while(*p == ' '){ p++; } strcpy(msg->data, p); fclose(fp); return 1; } fclose(fp); return 0; }
int get_date(char *date){ time_t t; struct tm *tp; time(&t); tp = localtime(&t); sprintf(date,"%d-%d-%d %d:%d:%d",tp->tm_year+1900, tp->tm_mon+1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec); return 0; }
|
查询历史记录:这部分实现的就是处理客户端的查询历史记录的请求任务,通过客户端的用户名,服务器在数据库的record表中进行查询该用户产生的历史记录,并逐一发来客户端。最后执行完该函数后,会继续监听该客户端的下一个请求任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| int do_history(int acceptfd, MSG *msg, sqlite3 *db){ char sql[128] = {}; char *errmsg; sprintf(sql,"select * from record where name = '%s'",msg->name); if(sqlite3_exec(db,sql,history_callback,(void *)&acceptfd, &errmsg) != SQLITE_OK){ printf("%s\n",errmsg); }else{ printf("Query record done...\n"); } msg->data[0] = '\0'; send(acceptfd, msg, sizeof(MSG),0); return 0; }
int history_callback(void* arg, int f_num, char** f_value, char** f_name){ int acceptfd = *((int *)arg); MSG msg; sprintf(msg.data, "%s , %s",f_value[1], f_value[2]); send(acceptfd, &msg, sizeof(MSG),0); return 0; }
|
3.2 客户端
主函数和一些基本信息:这部分定义了一个双方通信的信息结构体,其中type是R时,表示用户要求完成注册任务;是L时,表示用户要求完成登录任务;是Q时,表示用户要求完成查询任务;是H时,表示用户要求完成历史查询任务。客户端完成了任务函数后,又会继续等待用户从终端输入请求序号。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| #define N 32 #define R 1 #define L 2 #define Q 3 #define H 4
typedef struct { int type; char name[N]; char data[256]; }MSG;
int main(int argc, const char *argv[]){ int sockfd; struct sockaddr_in serveraddr; MSG msg; if(argc != 3){ printf("Usage:%s serverip port.\n",argv[0]); return -1; } sockfd = socket(AF_INET,SOCK_STREAM,0); if(sockfd < 0){ perror("fail to socket...\n"); return -1; } bzero(&serveraddr,sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(atoi(argv[2])); serveraddr.sin_addr.s_addr = inet_addr(argv[1]); if(connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))<0){ perror("fail to connect...\n"); return -1; } int n; while(1){ printf("*****************************************************************************\n"); printf("* 1.register 2.login 3.quit *\n"); printf("*****************************************************************************\n"); printf("Please choose:"); scanf("%d", &n); getchar(); 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); break; default: printf("Invalid data cmd...\n"); } } next: while(1){ printf("-------------------------------------------------------------------------------\n"); printf("- 1.query_word 2.history_record 3.quit -\n"); printf("-------------------------------------------------------------------------------\n"); printf("Please choose:"); scanf("%d", &n); getchar(); switch(n){ case 1: do_query(sockfd, &msg); break; case 2: do_history(sockfd, &msg); break; case 3: close(sockfd); exit(0); break; default: printf("Invalid data cmd...\n"); } } }
|
以下是对应的功能函数实现:根据用户输入的请求序号,执行对应的处理任务。
这部分负责处理用户的注册请求,用户从终端输入用户名和密码,客户端将该信息发送给服务器,服务器完成成功与否都会发送信息会客户端,客户端打印服务器发送来的信息告知用户。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| int do_register(int sockfd, MSG *msg){ msg->type = R; printf("Input name:"); scanf("%s", msg->name); getchar(); printf("Input passwd:"); scanf("%s",msg->data); if(send(sockfd, msg, sizeof(MSG),0) < 0){ printf("fail to send...\n"); return -1; } if(recv(sockfd, msg, sizeof(MSG), 0) < 0){ printf("fail to recv...\n"); return -1; } printf("%s\n",msg->data); return 0; }
|
这部分负责处理用户的登录请求,用户从终端输入用户名和密码,客户端将这些信息发送给服务器,服务器返回处理的结果。登录成功的话,用户进入二级目录,等待用户请求指令;登录失败的话,用户继续在一级目录,等待用户的请求指令。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| int do_login(int sockfd, MSG *msg){ msg->type = L; printf("Input name:"); scanf("%s", msg->name); getchar(); printf("Input passwd:"); scanf("%s",msg->data); if(send(sockfd, msg, sizeof(MSG),0) < 0){ printf("fail to send...\n"); return -1; } if(recv(sockfd, msg, sizeof(MSG), 0) < 0){ printf("fail to recv...\n"); return -1; } if(strncmp(msg->data, "OK",3) == 0){ printf("Login ok!\n"); return 1; }else{ printf("%s\n",msg->data); } return 0; }
|
这部分负责处理用户的查询单词请求,用户从终端输入要查询的单词,客户端将该单词发送给服务器,服务器在底层进行查询,将查询的结果返回给客户端,客户端向终端打印服务器发送来的查询结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| int do_query(int sockfd, MSG *msg){ msg->type = Q; puts("--------------------"); while(1){ printf("Input word:"); scanf("%s", msg->data); if(strncmp(msg->data,"#",1)==0){ break; } if(send(sockfd, msg, sizeof(MSG),0) < 0){ printf("fail to send...\n"); return -1; } if(recv(sockfd, msg, sizeof(MSG),0) < 0){ printf("fail to recv...\n"); return -1; } printf("%s\n", msg->data); } return 0; }
|
这部分负责处理用户的查询历史记录请求,客户端只需要将请求序号赋值到type,发送给服务器即可,服务器接收到信息是type是H,就会通过该用户的用户名进行历史记录查询,将查询到的结果返回给客户端,客户端向终端打印查询的记录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| int do_history(int sockfd, MSG *msg){ msg->type = H; send(sockfd, msg, sizeof(MSG), 0); while(1){ recv(sockfd, msg, sizeof(MSG), 0); if(msg->data[0] == '\0'){ break; } printf("%s\n",msg->data); } return 0; }
|
4. 执行效果
第一个用户
第二个用户
两个用户产生的数据库中的数据情况
词库的单词情况,大约20000个单词