阅读量:2
文章目录
前言
该项目来自华清远见Level 8项目,该项目实现的主要功能为:用户在客户端输入想要查询的单词,客户端会通过socket发送其到服务器。服务器查询数据库,如果有查到相应的释义就将查到的释义经服务器发给客户端。没找到有同样发回对应的提示信息。
该项目能够实现以下功能
- 能够实现用户的注册,注册的信息保存在数据库中
- 能够实现用户登录,通过查询所保存的用户数据库信息,判断是否可以登陆
- 能够实现单词的查询,如果用户选择的是查询模式,就查询数据库并返回相应的释义
- 用户查询的记录将被保存在数据库中,用户如果想查询之前的查询记录,也可以查询到
1 流程解析
1.1 客户端
定义了一个结构体MSG来传输数据,如下图右上角所示,type需要处理的类型,有注册(R:1),登录(L:2),查找(Q:3),历史(H:4)name和data为发送的用户名和数据。主要流程为,首先,创建socket连接,采用的是TCP,然后打印提示词,引导用户选择要注册、登录还是退出。用户输入后,判断用户选项,并根据选项跳转到对应的功能中。
- 在注册模块中,给type类型赋值注册状态,并引导用户输入用户名、密码。然后通过TCP将msg结构体发给服务器,服务器判断type类型,并跳转到相应的功能处,判断该用户名是否存在,如果存在返回already exit给客户端,如果不存在将其加入数据库当中,并返回OK给客户端。
- 在登录模块中,先给type类型赋值登录状态,并引导用户输入用户名、密码。然后通过TCP将msg结构体发给服务器,服务器判断type类型,并跳转到相应的功能处,判断该用户名是否存在,如果不存在返回给客户端usr/pwd wrong,如果存在返回OK给客户端。然后跳转到查询模块。
- 在查询模块中,先是引导用户输入相应的功能,如果是查询单词,则跳到对应的查询模块,同样先给type类型赋值查询状态,然后读取单词,这时候如果输入“#”号则退出。读取到单词后将其发送给服务器,服务器判断type类型,并跳转到相应的功能处,服务器搜寻数据库,如果该单词不存在则返回给客户端not found,如果存在则把释义发给客户端,并获取当地时间,连同查询的单词一并插sqlite3的记录表中,客户端把收到的释义打印出来
- 在查询模块中,先是引导用户输入相应的功能,如果用户是查询历史,则跳到对应的历史模块,同样先给type类型赋值历史状态,然后发送给服务器,服务器判断type类型,并跳转到相应的功能处,服务器搜寻数据库,服务器把查询历史连同时间一并发给客户端
1.2 服务器
GPS模块通过串口将数据发给主控芯片,ATGM336H会一次性返回多条信息,其中信息头的第一个是消息ID,标示着通过什么定位系统的采集的数据。简单的含义如下,具体含义可以查询使用手册
2 sqlite3基础使用
单词的查询通过数据库完成,下面简单介绍其基本使用。
2.1 apt指令直接安装
sudo apt-get install sqlite3 //安装sqlite3 sudo apt-get install libsqlite3-dev // 安装sqlite3编译需要的工具包 sqlite3 -version//用来检查是否安装成功
2.2 sqlite3基本命令
1、创建表
create table table_name(id integer, name char, tel integer); //创建一个表
2、插入数据
insert into table_name values(1, "www", 6666);
3、查询相关数据
select * from table_name; //查询所有数据 select * from table_name where id = //按指定内容查询
4、更新数据
update table_name set name = "change" where id = 866; //
5、删除数据
delete from table_name where id = 666; //删除id为666的所有信息
6、删除表
drop table table_name; //删除整个表
2.4sqlite3 API编程
sqlite3_open()
功能描述:打开一个数据库,如果该数据库不存在,sqlite则会自动创建
int sqlite3_open(const char *dbname,sqlite3 **db)
参数解析:
- 第一个参数是特定文件名(xx.db)
- 第二个参数是sqlite3 **结构体指针,成为数据库句柄(相当于是数据库的描述符)
返回值:成功则返回SQLITE_OK(0),失败则返回其他错误信息(非0值)
sqlite3_exec()
功能描述:编译和执行sql语句,将查询到的结果返回给回调函数callback
int sqlite3_open(const char *dbname,sqlite3 **db)
参数解析:
- 第一个参数是打开的数据库句柄
- 第二个参数是一个字符指针,表示所要执行的sql语句字符串,以’\0’结尾
- 第三个参数是一个回调函数,用来处理查询结果,如果不需要回调则NUL填L(比如insert或delete操作时),一般用于SELECT
- 第四个参数是传给回调函数的指针参数,如果不需要传递,则值填NULL
- 第五个参数是返回错误信息
返回值:成功则返回SQLITE_OK(0),失败则返回其他错误信息(非0值)
sqlite3_get_table()
功能描述:主要是用于以非回调的方式进行SELECT查询
int sqlite3_get_table(sqlite3 *db, const char *zsql, char ***pazResult, int *nrow, int *ncolumn,char **zErrmsg);
参数解析:
- 第一个参数是一个数据库句柄,是打开数据库得到的指针
- 第二个参数是一条sql语句,与sqlite3_exec()中的一样,以’\0’结尾的字符串
- 第三个参数是查询的结果,是一个一维数组,它的内存布局是:字段名称,后面是紧接着每个字段的值(就是列表名,后面跟着一个一个数据)
- 第四个参数是查询出多少条记录(即查出多少行,不包括字段名的那一行)
- 第五个参数是有多少字段(多少列)
- 第六个参数是返回错误信息,这里是指针的指针
返回值:成功则返回SQLITE_OK(0),失败则返回其他错误信息(非0值)
sqlite3_close()
int sqlite3_close(sqlite3 *db) //参数说明:db需要关闭的数据库文件。 //功能描述:关闭之前调用的数据库连接,所有与连接相关的语句都应该在关闭之前完成 //返回值:成功则返回SQLITE_OK(0),失败则返回其他错误信息(非0值),其中如果是查询没有完成,则返回SQLITE_BUSY
3. 本项目中sqlite3开发
linux@linux:~/Desktop/dictionary$ sqlite3 my.db SQLite version 3.8.2 2013-12-06 14:53:30 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> .database seq name file --- --------------- ---------------------------------------------------------- 0 main /home/linux/Desktop/dictionary/my.db sqlite> create table usr(name text, passwd text); sqlite> create table record(name text, date text, word text); sqlite> :q
4. 程序
4. 1客户端代码
client.c
#include <stdio.h> #include <stdlib.h> //socket #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> //sockaddr in this file #include <netinet/in.h> //sockaddr_in in this file #include <arpa/inet.h> #include <string.h> #include <fcntl.h> // for open #include <unistd.h> // for close #define N 32 #define R 1 //user - register #define L 2 //user - login #define Q 3 //user - query #define H 4 //user - history //定义通信双方的结构体 typedef struct client { int type; char name[N]; char data[256]; }MSG; 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)//(msg已经是指针,不用取地址)是大写MSG不是小写,小写是地址(永远4字节) { printf("fail to send.\n"); return -1; } if(recv(sockfd, msg, sizeof(MSG), 0) < 0) { printf("Fail to recv.\n"); return -1; } // ok ! or usr alread exist. printf("%s\n", msg->data); return 0; } 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)//(msg已经是指针,不用取地址)是大写MSG不是小写,小写是地址(永远4字节) { 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)//看看是否为OK,比较3位因为还有\0 { printf("Login ok!\n"); return 1; } else { printf("%s",msg->data); } return 0; } int do_query(int sockfd, MSG *msg) { msg->type = Q; printf("--------------------------\n"); while(1) { printf("Input word:"); scanf("%s",msg->data); getchar(); //输入#返回上级菜单 if(strncmp(msg->data, "#", 1) == 0) { break; } //将要查询的单词发送给服务器 if(send(sockfd, msg, sizeof(MSG),0) < 0) //send { perror("write"); //Fail to send.\n return -1; } //等待接收服务器,传递回来的单词的注释信息 if(recv(sockfd, msg, sizeof(MSG), 0) < 0) //recv { perror("read"); //Fail to recv.\n return -1; } printf("%s\n",msg->data); } return 0; } 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; } // .server 192.168.58.128 10000(端口号) 分别是argv[0],argv[1],argv[2] int main(int argc, const char *argv[]) { int sockfd; //socket handle struct sockaddr_in serveraddr; //网络信息结构体 int n; MSG msg; if (argc != 3) { printf("Usage:%s serverip port.\n", argv[0]); return -1; } //创建套接字 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("fail to socket.\n"); return -1; } bzero(&serveraddr, sizeof(serveraddr)); //Clear the structure serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr(argv[1]); //inet_addr: The dotted decimal string is converted to a binary address //atoi: convert to a int number //htons:Convert native data to network data serveraddr.sin_port = htons(atoi(argv[2])); //将本机数据转换为网络数据,atoi功能将字符串转换为整数 //连接服务器 if(connect(sockfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr)) < 0) { perror("fail to connect"); return -1; } //一级菜单 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"); } } return 0; }
4.2 服务端代码
server.c
//C #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> // for open #include <unistd.h> // for close //socket #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> //sockaddr in this file #include <netinet/in.h> //sockaddr_in in this file #include <arpa/inet.h> #include <sqlite3.h> #include <signal.h> #include <time.h> #define N 32 #define R 1 //user - register #define L 2 //user - login #define Q 3 //user - query #define H 4 //user - history #define DATABASE "my.db" //定义通信双方的结构体 typedef struct client { int type; char name[N]; char data[256]; }MSG; int do_client(int acceptfd,sqlite3 *db); void do_register(int acceptfd, MSG *msg,sqlite3 *db); int do_login(int sacceptfd, MSG *msg,sqlite3 *db); int do_query(int sacceptfd, MSG *msg,sqlite3 *db); int do_history(int acceptfd, MSG *msg,sqlite3 *db); int history_callback(void* arg,int f_num,char** f_value,char** f_name); int do_searchword(int acceptfd, MSG *msg, char word[]); int get_date(char *date); // .server 192.168.58.128 10000(端口号) 分别是argv[0],argv[1],argv[2] int main(int argc, const char *argv[]) { int sockfd; //socket handle struct sockaddr_in serveraddr; //网络信息结构体 int n; sqlite3 *db; int acceptfd; pid_t pid; MSG msg; 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"); } //创建套接字 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("fail to socket.\n"); return -1; } bzero(&serveraddr, sizeof(serveraddr)); //Clear the structure serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr(argv[1]); //inet_addr: The dotted decimal string is converted to a binary address //atoi: convert to a int number //htons:Convert native data to network data serveraddr.sin_port = htons(atoi(argv[2])); //将本机数据转换为网络数据,atoi功能将字符串转换为整数 //绑定(建立套接字与地址联系) if (bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) <0 ) { perror("fail to bind\n"); return -1; } //将套接字设为监听模式 if(listen(sockfd,5) < 0)//监听个数5 { printf("fail to listen"); return -1; } /*SIGCHLD 信号 *当父进程的某个子进程终止时,父进程会收到 SIGCHLD 信号; *当父进程的某个子进程因收到信号而停止(暂停运行)或恢复时,内核也可能向父进程发送该信号。 */ //处理僵尸进程 signal(SIGCHLD,SIG_IGN); //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.\n"); return -1; } else if(pid == 0) { //处理客户端具体的消息(子进程) close(sockfd);//监听套接字不用了,关闭 do_client(acceptfd,db); } else { //接收客户端的请求(父进程) close(acceptfd);//接收套接字不用了,关闭 } } return 0; } 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");//客户端退出就会跳出while循环 close(acceptfd); exit(0); return 0; } void do_register(int acceptfd, MSG *msg, sqlite3 *db) { char *errmsg; char sql[512];//原本视频中是[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 ; } int do_login(int acceptfd, MSG *msg, sqlite3 *db) { char sql[512] = {};//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");//这里把OK放进data里,client.c中才会那么比较 send(acceptfd, msg, sizeof(MSG), 0); return 1; } if(nrow == 0) // 密码或者用户名错误,或者写else就可以 { strcpy(msg->data,"usr/passwd wrong."); send(acceptfd, msg, sizeof(MSG), 0); } return 0; } int do_searchword(int acceptfd, MSG *msg, char word[]) { FILE * fp; int len = 0; 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; } //打印出,客户端要查询的单词 len = strlen(word); printf("%s , len = %d\n", word, len); //读文件,来查询单词 while(fgets(temp, 512, fp) != NULL) //每次读取512字节 { // printf("temp:%s\n", temp); // abandon ab result = strncmp(temp,word,len); if(result < 0) //不相等 { continue; } if(result > 0 || ((result == 0) && (temp[len]!=' '))) //第一个字符串>第二个或者两者相同但后面不是空格,表示在注释里出现的单词 { break; } // 表示找到了,查询的单词result == 0 且 temp[len] == ' '。 p = temp + len; // abandon v.akdsf dafsjkj // printf("found word:%s\n", p); while(*p == ' ') { p++; } // 找到了注释,跳跃过所有的空格 strcpy(msg->data, p); printf("found word:%s\n", msg->data); // 注释拷贝完毕之后,应该关闭文件 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); printf("get date:%s\n", date); return 0; } int do_query(int acceptfd, MSG *msg, sqlite3 *db) { char word[64]; int found = 0; char date[128] = {}; char sql[128] = {}; char *errmsg; //拿出msg结构体中,要查询的单词 strcpy(word, msg->data); found = do_searchword(acceptfd, msg, word); printf("查询一个单词完毕.\n"); // 表示找到了单词,那么此时应该将 用户名,时间,单词,插入到历史记录表中去。 if(found == 1) { // 需要获取系统时间 get_date(date); sprintf(sql, "insert into record values('%s', '%s', '%s')", msg->name, date, word); if(sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK) { printf("%s\n", errmsg); return -1; } else { printf("Insert record done.\n"); } } else //表示没有找到 { strcpy(msg->data, "Not found!"); } // 将查询的结果,发送给客户端 send(acceptfd, msg, sizeof(MSG), 0); return 0; } // 得到查询结果,并且需要将历史记录发送给客户端 int history_callback(void* arg,int f_num,char** f_value,char** f_name) { // record , name , date , word int acceptfd; MSG msg; acceptfd = *((int *)arg); sprintf(msg.data, "%s , %s", f_value[1], f_value[2]); send(acceptfd, &msg, sizeof(MSG), 0); return 0; } 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; }
5. 总结
以上即是本次的内容。详情可以见华清远见视频。