这个小项目是一个网络编程学习过程中一个阶段性检测项目,过程中可以有效检测我们对于服务器客户端搭建的能力,以及一些bug查找能力。项目的一个简单讲解我发在了b站上,没啥心得,多练就好。
https://t.bilibili.com/865244702526406675?share_source=pc_native
数据库创建及导入单词表
#include <stdio.h>`
`#include <sqlite3.h>`
`#include <string.h>`
`#include <stdlib.h>`
`#include <unistd.h>`
`#include <sys/types.h>`
`#include <sys/stat.h>`
`#include <fcntl.h>`
`int` `main(int argc,` `char` `const` `*argv[])`
`{`
`// 1.打开或创建数据库`
` sqlite3 *db =` `NULL;`
`int rc;`
`if` `(sqlite3_open("./word.db",` `&db)` `!= SQLITE_OK)//打开或创建库`
`{`
`printf("sqlite3_open err:%s\n",` `sqlite3_errmsg(db));`
`return` `-1;`
`}`
`printf("sqlite3_open success\n");`
`// 2.创建表`
`char` `*errmsg =` `NULL;//返回创建数据库表的错误`
`//创建一个两列的单词表,用于存储单词及注释`
`if` `(sqlite3_exec(db,` `"create table if not exists wd1 (word char, annotation char);",` `NULL,` `NULL,` `&errmsg)` `!= SQLITE_OK)`
`{`
`printf("create err: %sn", errmsg);`
`sqlite3_close(db);`
`return` `-1;`
`}`
`//创建一个两列的账户表,用于存储账户名及对应密码`
`if` `(sqlite3_exec(db,` `"create table if not exists user (name char, password char);",` `NULL,` `NULL,` `&errmsg)` `!= SQLITE_OK)`
`{`
`printf("create err: %sn", errmsg);`
`sqlite3_close(db);`
`return` `-1;`
`}`
`printf("create success\n");`
`// 3.向表中插入数据`
` FILE *fp =` `fopen(argv[1],` `"r");//打开要插入的文件流`
`if` `(fp ==` `NULL)`
`{`
`printf("failed to open file\n");`
`sqlite3_close(db);`
`return` `-1;`
`}`
`char buf[1024];` `//读取的一行`
`char word[32];` `//单词`
`char ant[1024];` `//保存注释`
`while` `(fgets(buf,` `sizeof(buf), fp)` `!=` `NULL)` `//读一行`
`{`
`sscanf(buf,` `"%99[^ ] %256[^\n]", word, ant);` `//将第一个单词放到单词数组中,后面的内容放到注释数组中`
`char sql[1024];//存放命令内容`
`sprintf(sql,` `"insert into wd1 values(\"%s\", \"%s\");", word, ant);` `// 构造插入语句,将单词及注释插入到单词表中`
` rc =` `sqlite3_exec(db, sql,` `NULL,` `NULL,` `&errmsg);`
`if` `(rc != SQLITE_OK)`
`{`
`printf("insert err: %s\n", errmsg);`
`return` `-1;`
`}`
`}`
`fclose(fp);//关闭文件描述符`
`// 5.关闭数据库连接`
`sqlite3_close(db);`
`return` `0;`
`}`
`
头函数及传输协议
#ifndef __HEAD_H__`
`#define __HEAD_H__//防止重包含`
`#include <sys/socket.h>`
`#include <netinet/in.h>`
`#include <netinet/ip.h>`
`#define N 32`
`enum type_t //运行命令`
`{`
` R=4, //register注册`
` L, //login登录`
` Q, //query搜索`
` H, //history历史`
`};`
`typedef struct //数据包结构体`
`{`
` int type;//执行命令类型`
` char name[N]; //用户名`
` char data[1024]; //密码或要查询的单词`
`} MSG_t;`
`typedef struct node_t`
`{`
` struct sockaddr_in addr; //ip地址`
` struct node_t *next; //链表下一个地址`
`} list_t;`
`#endif`
`
云词典服务器端
/*服务器创建代码 */`
`#include <stdio.h>`
`#include <sys/types.h> /* See NOTES */`
`#include <sys/socket.h>`
`#include <netinet/in.h>`
`#include <netinet/ip.h> /* superset of previous */`
`#include <arpa/inet.h>`
`#include <unistd.h>`
`#include <stdlib.h>`
`#include <string.h>`
`#include <sqlite3.h>`
`#include <signal.h>`
`#include <sys/select.h>`
`#include <time.h>`
`#include <sys/time.h>`
`#include "head.h"`
`MSG_t msg;`
`int n;`
`sqlite3 *db = NULL; //命令输入`
`char *errmsg = NULL; //错误码`
`int hang, lie; //数据库行和列`
`int k = 0;`
`int Register(sqlite3 *db, int sockfd) //注册函数`
`{`
` char **result = NULL; //数据库返回内容`
` //3.向表中插入数据`
` //(1)执行函数`
` char sq1[128]; //保存命令`
` sprintf(sq1, "select * from user where name = \"%s\";", msg.name); `
` sqlite3_get_table(db, sq1, &result, &hang, &lie, &errmsg);//判断数据库中是否已经存在该账户`
` if (hang != 0)`
` {`
` sprintf(msg.data, "账户已存在;\n");`
` send(sockfd, &msg, sizeof(msg), 0);`
` return -1;`
` }`
` else //成功注册`
` {`
` sprintf(sq1, "insert into user values(\"%s\",\"%s\");", msg.name, msg.data);`
` if (sqlite3_exec(db, sq1, NULL, NULL, &errmsg) == SQLITE_OK) //""需要用\转意,注册成功插入用户表内`
` {`
` sprintf(sq1, "create table if not exists \"%s\" (word char, time char);", msg.name); //每注册一个用户创建一个新表`
` if (sqlite3_exec(db, sq1, NULL, NULL, &errmsg) != SQLITE_OK) //创建新表`
` {`
` printf("create err: %s", errmsg);`
` sqlite3_close(db);`
` return -1;`
` }`
` else`
` {`
` printf("creat %s success\n", msg.name);`
` }`
` sprintf(msg.data, "OK");`
` send(sockfd, &msg, sizeof(msg), 0); //注册成功发送消息`
` memset(msg.data, 0, sizeof(msg.data));`
` return 0;`
` }`
` else //否则插入失败`
` {`
` printf("insert value err;%s\n", errmsg);`
` return -1;`
` }`
` }`
`}`
`//用户登录`
`int loginclient(sqlite3 *db, int sockfd)`
`{`
` char **result = NULL; //数据库返回内容`
` char sq1[128]; //保存命令`
` sprintf(sq1, "select * from user where name = \"%s\";", msg.name);`
` sqlite3_get_table(db, sq1, &result, &hang, &lie, &errmsg); //判断数据库中是否已经存在该账户`
` if (hang != 0) //如果能读出内容则行数不为0`
` {`
` if (strcmp(result[3], msg.data) == 0) //判断密码是否正确`
` {`
` sprintf(msg.data, "OK");`
` send(sockfd, &msg, sizeof(msg), 0);`
` return 0;`
` }`
` else //密码错误`
` {`
` sprintf(msg.data, "password err\n");`
` send(sockfd, &msg, sizeof(msg), 0);`
` return -1;`
` }`
` }`
` else //反之未注册`
` {`
` sprintf(msg.data, "no register\n"); //未注册`
` send(sockfd, &msg, sizeof(msg), 0);`
` return -1;`
` }`
`}`
`//查询单词注释`
`int chatclient(sqlite3 *db, int sockfd) //查询单词函数`
`{`
` char **result = NULL; //数据库返回内容`
` char sq1[128]; //保存命令`
` sprintf(sq1, "select * from wd1 where word = \"%s\";", msg.data);`
` sqlite3_get_table(db, sq1, &result, &hang, &lie, &errmsg); //判断是否查到该单词`
` if (hang != 0)`
` {`
` sprintf(msg.data, "%s", result[3]); //将注释内容发送到客户端`
` send(sockfd, &msg, sizeof(msg), 0); //发送该单词注释`
` time_t th;`
` time(&th); //获取当前时间`
` char times[128];`
` struct tm *ts;`
` ts = localtime(&th); //将当时间转化为标准时间`
` sprintf(times, "%4d-%2d-%2d %2d:%2d:%2d", ts->tm_year + 1900, ts->tm_mon + 1, ts->tm_mday, ts->tm_hour, ts->tm_min, ts->tm_sec);`
` //先将时间放到一个字符串中,再放到命令语句中`
` sprintf(sq1, "insert into \"%s\" values(\"%s\", \"%s\");", msg.name, result[2], times); // 构造插入语句`
` int rc = sqlite3_exec(db, sq1, NULL, NULL, &errmsg); //将查询时间保存到数据库中`
` if (rc != SQLITE_OK)`
` {`
` printf("insert err: %s\n", errmsg);`
` return -1;`
` }`
` return 0;`
` }`
` else //未找到该单词`
` {`
` sprintf(msg.data, "word unfund\n");`
` send(sockfd, &msg, sizeof(msg), 0);`
` return -1;`
` }`
`}`
`int history(sqlite3 *db, int sockfd) //查询历史记录`
`{`
` char **result = NULL; //数据库返回内容`
` char sq1[128]; //保存命令`
` sprintf(sq1, "select * from \"%s\";", msg.name); //查询单词查询历史`
` sqlite3_get_table(db, sq1, &result, &hang, &lie, &errmsg);`
` if (hang != 0)`
` {`
` for (int j = 0; j < hang; j++) //拼接表内内容到字符数组中`
` {`
` strcat(msg.data, result[j * lie + 2]);`
` strcat(msg.data, " "); //两个表内内容间添加空格间隔`
` strcat(msg.data, result[j * lie + 3]);`
` strcat(msg.data, "\n"); //两行间换行`
` }`
` send(sockfd, &msg, sizeof(msg), 0); //发送该单词查询结果`
` return 0;`
` }`
` else if (hang == 0) //历史记录为空`
` {`
` sprintf(msg.data, "history is void");`
` send(sockfd, &msg, sizeof(msg), 0); //发送该单词查询结果`
` return 0;`
` }`
`}`
`int main(int argc, char const *argv[])`
`{`
` // 1.打开或创建数据库`
` int rc;`
` if (sqlite3_open("./word.db", &db) != SQLITE_OK)`
` {`
` printf("sqlite3_open err:%s\n", sqlite3_errmsg(db));`
` return -1;`
` }`
` printf("sqlite3_open success\n"); //打开数据库成功`
` if (argc < 2) //行传参正确`
` {`
` printf("plase input <file><port>\n");`
` return -1;`
` }`
` //1.创建套接字,用于链接`
` int sockfd;`
` int acceptfd; //接收套接字`
` sockfd = socket(AF_INET, SOCK_STREAM, 0);`
` if (sockfd < 0) //容错`
` {`
` perror("socket err");`
` return -1;`
` }`
` //2.绑定 ip+port 填充结构体`
` struct sockaddr_in saddr;`
` saddr.sin_family = AF_INET; //协议族ipv4`
` saddr.sin_port = htons(atoi(argv[1])); //端口号,htons将无符号短整数hostshort从主机字节顺序到网络字节顺序。`
` saddr.sin_addr.s_addr = inet_addr("0.0.0.0"); //ip地址,转化为16进制表示`
` socklen_t len = sizeof(saddr); //结构体大小`
` //bind绑定ip和端口`
` if (bind(sockfd, (struct sockaddr *)&saddr, len) < 0)`
` {`
` perror("bind err");`
` return -1;`
` }`
` //3.启动监听,把主动套接子变为被动套接字`
` if (listen(sockfd, 6) < 0)`
` {`
` perror("listen err");`
` return -1;`
` }`
` //4.创建表`
` fd_set readfds; //原表`
` fd_set tempfds; //创建一个临时表,用来保存新表`
` FD_ZERO(&readfds); //原表置空`
` //5.填表`
` FD_SET(sockfd, &readfds); //将要监测的文件描述符插入到表中`
` int maxfd = sockfd; //表内最大描述符`
` int ret;`
` //6.循环监听 select`
` 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)) //监听是否有客户端链接`
` {`
` //阻塞等待客户端的链接请求`
` acceptfd = accept(sockfd, (struct sockaddr *)&saddr, &len);`
` //获取客户端的ip和端口,(struct sockaddr *)&saddr:用来存放返回的ip,和端口`
` if (acceptfd < 0)`
` {`
` perror("accept err");`
` return -1;`
` }`
` printf("client ip:%s ,port:%d:connect success\n", inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port));`
` //打印已经接入的客户端IP`
` FD_SET(acceptfd, &readfds); //将新接入的客户端文件描述符插入到原表中`
` if (acceptfd > maxfd) //如果新插入的文件描述符大于已知最大文件描述符,则更新表内最大文件描述符`
` {`
` maxfd = acceptfd;`
` }`
` }`
` for (int i = 5; i <= maxfd; i++) //遍历判断是否有信号传输`
` {`
` if (FD_ISSET(i, &tempfds)) //监测客户端文件描述符`
` {`
` int ret = recv(i, &msg, sizeof(msg), 0); //接收的信号`
` if (ret < 0) //接收错误`
` {`
` perror("recv err.");`
` return -1;`
` }`
` else if (ret == 0)`
` {`
` printf("%d client exit\n", i); //客户端退出`
` close(i); //关闭描述符`
` FD_CLR(i, &readfds); //删除文件描述符`
` }`
` else`
` {`
` switch (msg.type)`
` {`
` case R: //注册`
` Register(db, i);`
` break;`
` case L: //登录`
` loginclient(db, i);`
` break;`
` case Q: //搜索`
` chatclient(db, i);`
` break;`
` case H: //历史`
` history(db, i);`
` break;`
` default:`
` break;`
` }`
` }`
` }`
` }`
` }`
` close(sockfd);`
` close(acceptfd);`
` return 0;`
`}`
`
云词典客户端
/*客户端创建代码 */`
`#include <stdio.h>`
`#include <sys/types.h> /* See NOTES */`
`#include <sys/socket.h>`
`#include <netinet/in.h>`
`#include <netinet/ip.h> /* superset of previous */`
`#include <arpa/inet.h>`
`#include <unistd.h>`
`#include <stdlib.h>`
`#include <string.h>`
`#include "head.h"`
`#define N 32`
`MSG_t msg;`
`int n; //命令输入`
`//注册操作`
`void do_register(int sockfd)`
`{`
` while (1)`
` {`
` msg.type = R; //注册状态`
` printf("请输入您的用户名:");`
` scanf("%s", msg.name);`
` getchar(); //回收一个垃圾字符`
` if (memcmp(msg.name, "#", 1) == 0) //注册过程中输入#退出注册页面`
` {`
` printf("退出注册\n");`
` break;`
` }`
` printf("请输入您的密码:");`
` scanf("%s", msg.data);`
` getchar();`
` send(sockfd, &msg, sizeof(msg), 0);`
` recv(sockfd, &msg, sizeof(msg), 0); //接收客户端消息,判断是否注册成功`
` printf("register:%s\n", msg.data);`
` if (memcmp(msg.data, "OK", 2) == 0) //服务器返回的msg.data == OK注册成功`
` {`
` break;`
` }`
` }`
`}`
`//登录操作`
`int do_login(int sockfd)`
`{`
` while (1)`
` {`
` msg.type = L; //登录状态`
` printf("请输入您的用户名:");`
` scanf("%s", msg.name);`
` getchar();`
` if (memcmp(msg.data, "#", 1) == 0) //退出查询页面,先判断退出后发送内容`
` {`
` printf("退出查询\n");`
` msg.type = 0;//当发送给服务器为结束内容,让服务器端读出且无法识别type,继续等待事件`
` memset(msg.data,0,sizeof(msg.data));//将msg.data中的#清空`
` send(sockfd, &msg, sizeof(msg), 0);`
` break;`
` }`
` printf("请输入您的密码:");`
` scanf("%s", msg.data);`
` getchar();`
` send(sockfd, &msg, sizeof(msg), 0);`
` recv(sockfd, &msg, sizeof(msg), 0);`
` printf("login:%s\n", msg.data);`
` if (memcmp(msg.data, "OK", 2) == 0) //服务器返回的msg.data=OK登录成功`
` {`
` return 1;`
` }`
` }`
`}`
`//查询操作`
`void do_query(int sockfd)`
`{`
` msg.type = Q;`
` while (1)`
` {`
` printf("请输入你要查询的单词内容:");`
` scanf("%s", msg.data);`
` getchar();`
` if (memcmp(msg.data, "#", 1) == 0) //退出查询页面,先判断退出后发送内容`
` {`
` printf("退出查询\n");`
` msg.type = 0;//当发送给服务器为结束内容,让服务器端读出且无法识别type,继续等待事件`
` memset(msg.data,0,sizeof(msg.data));`
` send(sockfd, &msg, sizeof(msg), 0);`
` break;`
` }`
` send(sockfd, &msg, sizeof(msg), 0);`
` recv(sockfd, &msg, sizeof(msg), 0);`
` printf("annotation:%s\n", msg.data); //打印接收的信息/注释`
` }`
`}`
`//查询历史记录`
`void do_history(int sockfd)`
`{`
` // memset(msg.data, 0, sizeof(msg.data));`
` msg.type = H;`
` send(sockfd, &msg, sizeof(msg), 0);`
` recv(sockfd, &msg, sizeof(msg), 0); //接收信息,接一次打一次.`
` printf("%s\n", msg.data);`
`}`
`//主函数`
`int main(int argc, char const *argv[])`
`{`
` if (argc < 3)`
` {`
` printf("plase input <ip><port>\n");`
` return -1;`
` }`
` //1.创建套接字,用于链接`
` int sockfd;`
` sockfd = socket(AF_INET, SOCK_STREAM, 0);`
` if (sockfd < 0)`
` {`
` perror("socket err");`
` return -1;`
` }`
` //文件描述符 0 -> 标准输入 1->标准输出 2->标准出错 3->socket`
` printf("sockfd:%d\n", sockfd);`
` //2.绑定 ip+port 填充结构体`
` struct sockaddr_in saddr;`
` saddr.sin_family = AF_INET; //协议族ipv4`
` saddr.sin_port = htons(atoi(argv[2])); //端口号,htons将无符号短整数hostshort从主机字节顺序到网络字节顺序。`
` saddr.sin_addr.s_addr = inet_addr(argv[1]); //ip地址,转化为16进制表示`
` socklen_t len = sizeof(saddr); //结构体大小`
` //3用于连接服务器;`
` if (connect(sockfd, (struct sockaddr *)&saddr, len) < 0)`
` {`
` perror("connect err");`
` return -1;`
` }`
` int flag = 0;`
` while (1)`
` {`
` printf("*******************************************************\n");`
` printf("* <BUG词典> *\n");`
` printf("* 1: 注册 2: 登录 3: 退出 *\n");`
` printf("*******************************************************\n");`
` printf("请输入命令:");`
` scanf("%d", &n);`
` getchar();`
` switch (n)`
` {`
` case 1:`
` do_register(sockfd);`
` break;`
` case 2:`
` if (do_login(sockfd) == 1)`
` {`
` while (1)`
` {`
` if (flag == 1)`
` {`
` flag = 0;`
` break;`
` }`
` printf("*******************************************************\n");`
` printf("* <BUG词典> *\n");`
` printf("* 1: 查询单词 2: 历史记录 3: 退出 *\n");`
` printf("*******************************************************\n");`
` printf("请输入命令:");`
` scanf("%d", &n);`
` getchar();`
` switch (n)`
` {`
` case 1:`
` do_query(sockfd);`
` break;`
` case 2:`
` printf("search history\n");`
` do_history(sockfd);`
` break;`
` case 3:`
` flag = 1;`
` break;`
` default:`
` break;`
` }`
` }`
` }`
` break;`
` case 3:`
` return 0;`
` default:`
` break;`
` }`
` }`
` close(sockfd);`
` return 0;`
`}`
`