目录

一、前言

二、项目功能

三、程序流程

1、客户端

2、服务器

四、代码实现

1、客户端代码

2、服务器代码

3、Makefile


一、前言

本文学习自【华清远见】的一个开源嵌入式项目在线词典综合实战,涵盖了网络编程、文件I/O、并发程序设计、数据库开发等多方面知识,对想要学习嵌入式开发、锻炼提升编程能力的小伙伴来说是一个非常不错的实战项目。接下来咋们先梳理下项目要实现的具体功能,设计程序流程,最后再开始具体的编程实现

二、项目功能

1、需要基于tcp实现一个供用户操作的客户端;在客户端中有两级菜单,一级菜单中用户可进行注册、登录、退出操作;登录后进入二级菜单,二级菜单中可进行单词查询、历史记录查询、退出操作

2、需要基于tcp实现一个多并发的服务器,用于响应客户端的注册、登录、单词查询、历史记录查询请求,同时需要将用户注册的用户信息(用户名、密码)、单词查询记录(用户名、查询时间、查询单词)分别保存到sqlite3数据库的用户表和记录表中。具体如下:

  • 客服端登录时,在数据库的用户表中匹配注册的用户名和密码,返回登录结果;
  • 客户端查询单词时,在词典的txt文件中查找对应的单词,如若找到则返回对应的注释信息;
  • 客户端查询历史记录时,从数据库的记录表中,将当前登录用户的查询记录(用户名、查询时间、查询单词)信息返回给客户端

3、客户端与服务器之间的交互过程如下:

三、程序流程

1、客户端

2、服务器

四、代码实现

1、客户端代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>#define NAME_LEN 32#define MSGTYPE_R 11 //user - register
#define MSGTYPE_L 22 //user - login
#define MSGTYPE_Q 33 //user - query
#define MSGTYPE_H 44 //user - history// 通信双方的信息结构体
typedef struct {int type;char name[NAME_LEN];char data[256];
}MSG_T;/*
struct sockaddr
{uint8_t        sa_len;sa_family_t    sa_family;char           sa_data[14];
};
/* members are in network byte order */
/*
struct sockaddr_in
{uint8_t        sin_len;sa_family_t    sin_family;in_port_t      sin_port;struct in_addr sin_addr;
#define SIN_ZERO_LEN 8char            sin_zero[SIN_ZERO_LEN];
};struct in_addr
{in_addr_t s_addr;
};
*/// 注册
int do_register(int sockfd, MSG_T *msg)
{printf("register ... \n");memset(msg, 0, sizeof(MSG_T));msg->type = MSGTYPE_R;printf("input name:"); scanf("%s", msg->name); getchar();printf("input passwd:"); scanf("%s", msg->data); getchar();if (send(sockfd, msg, sizeof(MSG_T), 0) < 0){printf("fail to send register msg.\n");return -1;}if (recv(sockfd, msg, sizeof(MSG_T), 0) < 0){printf("fail to register.\n");return -1;}//ok !  或者 user already exist.printf("%s\n", msg->data);return 1;
}// 登录, 必服务器返回 "OK" 才表示登录成功
// 返回值:1 成功,-1 失败,0 其他
int do_login(int sockfd, MSG_T *msg)
{printf("login ...\n");memset(msg, 0, sizeof(MSG_T));msg->type = MSGTYPE_L;printf("input name:"); scanf("%s", msg->name); getchar();printf("input passwd:"); scanf("%s", msg->data); getchar();if (send(sockfd, msg, sizeof(MSG_T), 0) < 0){printf("fail to send register msg.\n");return -1;}if (recv(sockfd, msg, sizeof(MSG_T), 0) < 0){printf("fail to login.\n");return -1;}//登录成功if (strncmp(msg->data, "OK", 3) == 0){printf("login ok! \n");return 1;}else{printf("%s\n", msg->data);}return 0;
}// 单词查询
int do_query(int sockfd, MSG_T *msg)
{printf("query ...\n");msg->type = MSGTYPE_Q;while(1){printf("input word:"); scanf("%s", msg->data); getchar();// 输入是"#"表示退出本次查询if (strncmp(msg->data, "#", 1) == 0)break;//将要查询的单词发送给服务器if (send(sockfd, msg, sizeof(MSG_T), 0) < 0){printf("fail to send.\n");return -1;}//等待服务器,传递回来的单次的注释信息if (recv(sockfd, msg, sizeof(MSG_T), 0) < 0){printf("fail to recv.\n");return -1;}printf("%s\n", msg->data);}return 1;
}// 历史记录查询
int do_history(int sockfd, MSG_T *msg)
{printf("history ...\n");msg->type = MSGTYPE_H;//将消息发送给服务器if (send(sockfd, msg, sizeof(MSG_T), 0) < 0){printf("fail to send.\n");return -1;}while(1){//等待服务器,传递回来的单次的注释信息if (recv(sockfd, msg, sizeof(MSG_T), 0) < 0){printf("fail to recv.\n");return -1;}if (msg->data[0] == '\0')break;//输出历史记录信息    printf("%s\n", msg->data);}return 1;}// ./client 192.168.0.107 10000
// 知识点:
//  1、在一个套接字被创建时,其默认是工作在阻塞模式下,即所有socket相关的数据发送、接收函数都会阻塞,
//  直到数据发送、接收成功,函数才继续执行
//  2、可以通过fcntl来设置成阻塞或非阻塞模式。https://blog.csdn.net/haoyu_linux/article/details/44306993
int main(int argc, const char *argv[])
{int sockfd;struct sockaddr_in server_addr;int input_nbr;MSG_T send_msg;if (argc != 3){printf("usage: %s serverip port\n", argv[0]);return -1;}// 申请socketif ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){perror("fail to socket\n");return -1;}   server_addr.sin_family  = AF_INET;server_addr.sin_addr.s_addr = inet_addr(argv[1]);server_addr.sin_port = htons(atoi(argv[2]));bzero(&(server_addr.sin_zero), sizeof(server_addr.sin_zero));/*连接到服务器*/if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr)) < 0){perror("fail to connect\n");return -1;}while (1){printf("***********************************************\n");printf("*******  1.register   2.login   3.quit  *******\n");printf("***********************************************\n");printf("please choose:");scanf("%d", &input_nbr);getchar();//回收垃圾字符// 一级菜单switch (input_nbr){case 1:do_register(sockfd, &send_msg);break;case 2:if (do_login(sockfd, &send_msg) == 1){goto _login;}break;case 3:close(sockfd);exit(0);break;default:printf("Invalid data cmd. \n");break;}}//二级菜单,登录后进行单词查询
_login:while(1){printf("***********************************************\n");printf("*****1.query_word  2.history_record  3.quit****\n");printf("***********************************************\n");printf("please choose:");input_nbr = 0;scanf("%d", &input_nbr);getchar();//回收垃圾字符switch (input_nbr){case 1:do_query(sockfd, &send_msg);break;case 2:do_history(sockfd, &send_msg);break;case 3:close(sockfd);exit(0);break;default:printf("Invalid data cmd. \n");break;}}return 0;
}

2、服务器代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sqlite3.h>
#include <signal.h>
#include <time.h>#define NAME_LEN 32#define MSGTYPE_R 11 //user - register
#define MSGTYPE_L 22 //user - login
#define MSGTYPE_Q 33 //user - query
#define MSGTYPE_H 44 //user - history#define DATABASE "my.db"// 通信双方的信息结构体
typedef struct {int type;char name[NAME_LEN];char data[256];
}MSG_T;/*
struct sockaddr
{uint8_t        sa_len;sa_family_t    sa_family;char           sa_data[14];
};
/* members are in network byte order */
/*
struct sockaddr_in
{uint8_t        sin_len;sa_family_t    sin_family;in_port_t      sin_port;struct in_addr sin_addr;
#define SIN_ZERO_LEN 8char            sin_zero[SIN_ZERO_LEN];
};struct in_addr
{in_addr_t s_addr;
};
*/// 注册
int do_register(int sockfd, MSG_T *msg, sqlite3 *db)
{char sql[128] = {0};char *errmsg = NULL;//sprintf(sql, "create table if not exists user(name text primary key, passwd text);");sprintf(sql, "insert into user values('%s', '%s');", msg->name, msg->data);if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK){printf("%s\n", errmsg);memset(msg->data, 0, strlen(msg->data));sprintf(msg->data, "user(%s) already exist.!", msg->name);}else{printf("client user(%s) register success\n", msg->name);memset(msg->data, 0, strlen(msg->data));strcpy(msg->data, "register ok !");}//返回应答if (send(sockfd, msg, sizeof(MSG_T), 0) < 0){printf("fail to send\n");return 0;}return 1;
}// 登录, 必服务器返回 "OK" 才表示登录成功
// 返回值:1 成功,-1 失败,0 其他
int do_login(int sockfd, MSG_T *msg, sqlite3 *db)
{char sql[128] = {0};char *errmsg = NULL, **result = NULL;int nrow, ncolumn;//sprintf(sql, "create table if not exists user(name text primary key, passwd text);");//sprintf(sql, "insert into user values('%s', '%s');", msg->name, msg->data);sprintf(sql, "select * from user where name='%s' and passwd='%s';", msg->name, msg->data);if (sqlite3_get_table(db, sql, &result, &nrow, &ncolumn, &errmsg) != SQLITE_OK){printf("%s\n", errmsg);}else{printf("sqlite3 get table ok! \n");}//查询成功,数据库中拥有此用户if (nrow == 1){printf("client user(%s) login success\n", msg->name);memset(msg->data, 0, strlen(msg->data));strcpy(msg->data, "OK");}else //用户名或密码错误{printf("client user(%s) login fail! \n", msg->name);memset(msg->data, 0, strlen(msg->data));strcpy(msg->data, "user or passwd wrong! \n");}//返回应答if (send(sockfd, msg, sizeof(MSG_T), 0) < 0){printf("fail to send\n");return 0;}return 1;}//根据单词从dirt.txt文本中查找对应的注释信息
//返回值: -1 文件打开失败; 0 单词未找到; 1 查找成功
int do_searchword(MSG_T *msg, char *word)
{FILE *fp = NULL;int word_len = strlen(word);char row_data[512] = {'\0'};int res = 0;char *p; //指向注释//打开文件if ((fp = fopen("dict.txt", "r")) == NULL){perror("fail to open dict.txt.\n");return -1;}//打印客户端要查询的单词word_len = strlen(word);printf("%s, len = %d\n", word, word_len );//读取文件 行数据(一行一行读取),对比要查询的单词//如果成功,该函数返回相同的 str 参数。//如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针。//如果发生错误,返回一个空指针。while (fgets(row_data, 512, fp) != NULL){res = strncmp(row_data, word, word_len);//每行对比前word_len个字节if (res != 0)continue;if (row_data[word_len] != ' ') //单词跟注释之间没有空格goto _end;// 找到了单词,跳过所有的空格p = row_data + word_len;while (*p == ' '){p++;}strcpy(msg->data, p);fclose(fp);return 1;}_end:fclose(fp);return 0; //文件对比完,单词未找到
}//获取系统时间
void get_date(char *data)
{time_t rowtime; //typedef long     time_t;
//    struct tm {
//                   int tm_sec;    /* Seconds (0-60) */
//                   int tm_min;    /* Minutes (0-59) */
//                   int tm_hour;   /* Hours (0-23) */
//                   int tm_mday;   /* Day of the month (1-31) */
//                   int tm_mon;    /* Month (0-11) */
//                   int tm_year;   /* Year - 1900 */
//                   int tm_wday;   /* Day of the week (0-6, Sunday = 0) */
//                   int tm_yday;   /* Day in the year (0-365, 1 Jan = 0) */
//                   int tm_isdst;  /* Daylight saving time */
//               };struct tm *info;// rowtime = the number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC)time(&rowtime);//进行时间格式转换info = localtime(&rowtime);sprintf(data, "%d-%d-%d %d:%d:%d", info->tm_year + 1900, info->tm_mon + 1, info->tm_mday,info->tm_hour, info->tm_min, info->tm_sec);printf("get date is : %s\n", data);
}// 单词查询
int do_query(int sockfd, MSG_T *msg, sqlite3 *db)
{char sql[128] = {0};char word[64] = {0};int found = 0;char date[128] = {0};char *errmsg;printf("\n");//显示换行//单词查找strcpy(word, msg->data);found = do_searchword(msg, word);if (found == 1)// 找到了单词,需要将 name,date,word 插入到历史记录表中去{get_date(date);//获取系统时间//sprintf(sql, "insert into user values('%s', '%s');", msg->name, msg->data);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("sqlite3 insert record done.\n");}}else if (found == 0)//没有找到{memset(msg->data, 0, strlen(msg->data));strcpy(msg->data, "Not found!\n");}else if (found == -1)//dict.txt代开失败{memset(msg->data, 0, strlen(msg->data));strcpy(msg->data, "fail to open dict.txt.");}//将查询的结果发送给客户端send(sockfd, msg, sizeof(MSG_T), 0);return 0;}// 得到查询结果,并且需要将历史记录发送给客户端
int history_callback(void* arg,int colCount,char** colValue,char** colName)
{// record :  name, date, word int acceptfd;MSG_T msg;acceptfd = *((int *)arg);sprintf(msg.data, "%s , %s", colValue[1], colValue[2]);send(acceptfd, &msg, sizeof(MSG_T), 0);return 0;
}// 历史记录查询
int do_history(int sockfd, MSG_T *msg, sqlite3 *db)
{char sql[128] = {0};char *errmsg;//查询数据库//会先执行*sql对应的功能命令,然后将结果传递给回调函数,回调函数根据结果再进一步执行//关于回调函数详细可参考https://blog.csdn.net/u012351051/article/details/90382391sprintf(sql, "select * from record where name = '%s'", msg->name);if(sqlite3_exec(db, sql, history_callback,(void *)&sockfd, &errmsg)!= SQLITE_OK){printf("%s\n", errmsg);}else{printf("sqlite3 query record done.\n");}// 所有的记录查询发送完毕之后,给客户端发出一个结束信息msg->data[0] = '\0';send(sockfd, msg, sizeof(MSG_T), 0);return 0;
}int do_client(int acceptfd, sqlite3 *db)
{MSG_T recv_msg;// 默认是阻塞式接收// 如果客户端断开连接 或是 主动发送close关闭连接,recv会返回0// recv的返回值:<0 出错; =0 连接关闭; >0 接收到数据大小while (recv(acceptfd, &recv_msg, sizeof(MSG_T), 0) > 0){switch(recv_msg.type){case MSGTYPE_R:do_register(acceptfd, &recv_msg, db);break;case MSGTYPE_L:do_login(acceptfd, &recv_msg, db);break;case MSGTYPE_Q:do_query(acceptfd, &recv_msg, db);break;case MSGTYPE_H:do_history(acceptfd, &recv_msg, db);break;default:printf("invalid data msg.\n");}}printf("client exit.\n");close(acceptfd);exit(0);return 0;
}// ./server 192.168.0.107 10000
// 知识点:
//  1、在一个套接字被创建时,其默认是工作在阻塞模式下,即所有socket相关的数据发送、接收函数都会阻塞,
//  直到数据发送、接收成功,函数才继续执行
//  2、可以通过fcntl来设置成阻塞或非阻塞模式。https://blog.csdn.net/haoyu_linux/article/details/44306993
int main(int argc, const char *argv[])
{int sockfd, acceptfd;struct sockaddr_in server_addr;struct sockaddr_in client_addr;socklen_t addrlen = sizeof(client_addr);char ipv4_addr[16];//存放电分格式的ip地址 sqlite3 *db = NULL;char *errmsg = NULL;char sql[128] = {0};pid_t pid;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;}printf("sqlite3 open %s success.\n", DATABASE);sprintf(sql, "create table if not exists user(name text primary key, passwd text);");if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK) //创建用户表{printf("%s\n", errmsg);return -1;}memset(sql, 0, sizeof(sql));sprintf(sql, "create table if not exists record(name text, date text, word text);");if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)//创建记录表{printf("%s\n", errmsg);return -1;}/*********************tcp server操作*********************/// 申请socketif ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){perror("fail to socket\n");return -1;}    server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = inet_addr(argv[1]);server_addr.sin_port = htons(atoi(argv[2]));bzero(&(server_addr.sin_zero), sizeof(server_addr.sin_zero));//绑定套接字if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr)) < 0){perror("fail to bind.\n");return -1;}//将套接字设为监听模式if (listen(sockfd, 5) < 0){perror("fail to listen.\n");exit(1);}printf("listen success.\n");//处理僵尸进程signal(SIGCHLD, SIG_IGN);while (1){//接收客户端的连接请求if ((acceptfd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen)) < 0){perror("fail to accept\n");return -1;}if (inet_ntop(AF_INET, &client_addr.sin_addr, ipv4_addr, addrlen) < 0){perror("fail to inet_ntop\n");return -1;}printf("client(%s:%d) is connected!\n", ipv4_addr, htons(client_addr.sin_port));//创建子进程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;
}

3、Makefile

all:client server
.PHONY : allcc = gccclient : client.c$(cc) client.c -o client
server : server.c$(cc) server.c -o server -lsqlite3.PHONY : clean
clean:rm client server

嵌入式学习项目实战 --- 在线词典相关推荐

  1. [嵌入式Linux项目实战开发]基于QT4.7.4的音乐播放器实现与设计【2018年给力项目】

    [嵌入式Linux项目实战开发]基于QT4.7.4的音乐播放器实现与设计[2018年给力项目]是[创科之龙]团队aiku嵌入式视频教程系列制作的现有的音乐播放器. 主要功能实现: 1.新建工程,基类选 ...

  2. [嵌入式Linux项目实战开发]基于QT4.8的仓库管理系统实现功能【2019年给力项目】

    [嵌入式Linux项目实战开发]基于QT4.8的仓库管理系统实现功能[2019年给力项目] 支持导出 excel 表格 支持查看商品操作日志 支持高精度浮点运算 支持同一商品以不同价格入库 该软件已开 ...

  3. 深度学习项目实战-关键点定位视频课程

    课程目标 快速掌握如何使用caffe框架完成一个深度学习的实际项目 适用人群 深度学习爱好者,全民皆可入门 课程简介 深度学习项目实战-关键点定位课程以人脸关键点检测为背景,选择多阶段检测的网络架构, ...

  4. 【PyTorch深度学习项目实战100例】—— 基于ResNet50实现多目标美味蛋糕图像分类 | 第51例

    前言 大家好,我是阿光. 本专栏整理了<PyTorch深度学习项目实战100例>,内包含了各种不同的深度学习项目,包含项目原理以及源码,每一个项目实例都附带有完整的代码+数据集. 正在更新 ...

  5. 【PyTorch深度学习项目实战100例】—— 基于CNN实现书法字体风格识别任务 | 第62例

    前言 大家好,我是阿光. 本专栏整理了<PyTorch深度学习项目实战100例>,内包含了各种不同的深度学习项目,包含项目原理以及源码,每一个项目实例都附带有完整的代码+数据集. 正在更新 ...

  6. 【PyTorch深度学习项目实战100例目录】项目详解 + 数据集 + 完整源码

    前言 大家好,我是阿光. 本专栏整理了<PyTorch深度学习项目实战100例>,内包含了各种不同的深度学习项目,包含项目原理以及源码,每一个项目实例都附带有完整的代码+数据集. 正在更新 ...

  7. 【PyTorch深度学习项目实战100例】—— Python+OpenCV+MediaPipe手势识别系统 | 第2例

    前言 大家好,我是阿光. 本专栏整理了<PyTorch深度学习项目实战100例>,内包含了各种不同的深度学习项目,包含项目原理以及源码,每一个项目实例都附带有完整的代码+数据集. 正在更新 ...

  8. 【PyTorch深度学习项目实战100例】—— 基于聚类算法完成航空公司客户价值分析任务 | 第18例

    前言 大家好,我是阿光. 本专栏整理了<PyTorch深度学习项目实战100例>,内包含了各种不同的深度学习项目,包含项目原理以及源码,每一个项目实例都附带有完整的代码+数据集. 正在更新 ...

  9. 深度学习项目实战(一):猫狗识别

    深度学习项目实战(一):猫狗识别 文章目录 深度学习项目实战(一):猫狗识别 项目背景: 数据读取: 网络架构 卷积神经网络训练 项目背景: 猫狗识别是卷积神经网络的入门实战案例,目的在于计算机可以识 ...

最新文章

  1. rewrite or internal redirection cycle while processing nginx重定向报错
  2. Python---20行代码爬取斗鱼平台房间数据(上)
  3. 2014.12.22 几个有用的oracle正则表达式
  4. CSS基础学习 19.CSS hack
  5. mysql读取求和_MySQL从单独的表中获取求和值
  6. 如何在Appscale下发布自己的应用(一)
  7. 三位数的茎叶图怎么看_贝德玛化妆品生产日期怎么看?贝德玛化妆品保质期怎么看?...
  8. 快速搭建springmvc+spring data jpa工程
  9. 从减少DNS查找来优化网站
  10. 中兴助印尼Smartfren测试大规模MIMO技术
  11. JS 语法糖 0 —— 解构
  12. How to study Watir?
  13. 怎么用ppt去演示html,如何将PPT演示同步发布到浏览器?
  14. 深度学习语音识别方法概述与分析
  15. OpenXML:C#操作PPT文档
  16. Linux网卡驱动分析
  17. 长江学者石照耀剖析精密减速机国产化之路—山坳上的机器人精密减速器
  18. python点滴 1
  19. 剑指Offer——腾讯+360+搜狗校招笔试题+知识点总结
  20. 用C++ 输出[1,100]范围内的所有奇数,每行10个。

热门文章

  1. vb.net 实现图片圆形渐变模糊
  2. UI设计师是一群什么样的人?
  3. UI5-文档-4.5-Controllers
  4. uniapp 微信云开发静态网站和云函数跳转小程序
  5. KMEANS均值聚类和层次聚类:亚洲国家地区生活幸福质量异同可视化分析和选择最佳聚类数...
  6. Could not write JSON: Infinite recursion (StackOverflowError) (through reference chain:
  7. selenium + bs4 +requests 爬取全国电动汽车充电站数据
  8. c++ 箭头符号怎么打_C++编程基础知识二
  9. 微信小程序幽默风和git
  10. 师徒结对活动记录表计算机,幼儿园师徒结对活动记录表