(承接上篇blog)
将温度信息写入数据库
本人在进行此项目开发时使用的数据库是sqlite3,在Linux上可直接通过install命令下载与之相关的命令与C 接口函数库,下载方法已经在开篇的blog中有所讲述。
同多数数据库一样,sqlite3的基本命令无外乎是建表、增删改查等,命令的语法格式与Oracle、sql等主流数据库类似。此项目中对数据库的操作都是利用sqlite3命令完成的,在此列出其中所用到命令的基本格式:
- 建表:
create table if not exists [表名](
[字段名1] [数据类型],
[字段名2] [数据类型],
…); - 插入记录:
insert into [表名]([字段名1], [字段名2], …) values ( [数据1], [数据2], …); - 删除记录:
delete from [表名] where [限定条件]; - 查询记录:
select * from [表名] where [限定条件];(也可以无‘where’和之后的限定条件,此时表示显示表中的所有记录)
若要用C来操作sqlite3,则需调用相应的C sqlite3 接口函数,此项目中只需要掌握两大函数四大函数即可:
-
创建数据库文件:
int sqlite3_open(char *path, sqlite3 **db)
其中,第一个参数是所要创建的数据库文件所在路径,第二个参数是一个sqlite3 句柄的地址,当数据库文件顺利建立时,该函数返回非负整数; -
获取出错信息:
char *sqlite3_errmsg(sqlite3 *db)
参数就只有sqlite3 句柄,返回的是以字符串表示的出错信息; -
关闭句柄
int sqlite3_close(sqlite3 *db)
与sqlite3_open 函数类似,返回的值表示操作是否成功,参数就只有sqlite3 句柄; -
命令执行(sqlite3_exec, sqlite3_get_table)
(1)int sqlite3_exec(sqlite3 *db, char *sql, sqlite3_callback, void *callback_argu, char ** errmsg)
当执行除查询以外的sqlite3 操作时,建议使用该函数,此时第三四个参数直接设为NULL。sql代表要执行的sqlite3命令;sqlite3_callback是一个回调函数,当执行完sqlite3命令后,就会自动调用该函数,如果sqlite3语句有语法错误,错误信息会写入errmsg中。
(2)int sqlite3_get_table(sqlite3 *db, char *sql, char ***result, int *nrow, int *ncolumn, char **errmsg)
此函数建议在使用查询语句时使用。result是一个二维字符数组,用于存储查询结果中所有数据和字段名相应的字符串,使用该函数时,应该传入此变量的地址,字段名和数据对应的字符串按照行优先的原则依次存放在这个二维数组里面;nrow和ncolumn分别是查询结果的行总数和列总数,两者相乘就是字段名和数据的总数,传参时,传入的是两者的地址;要打印出查询结果,需要运用循环和字符串的格式,将结果逐一输出,循环此时由nrow与ncolumn乘积的值而定。
回到此项目中,通过分析,就可以知道:
- 开始记录温度数据前,应该打开一个数据库文件(*.db),并建立一个数据表(table),如果表已存在,则不需建立,此处建议使用sqlite3语句 “create table if not exists …”;
- 直接在调用的sqlite3_exec函数里面使用insert语句,即可实现温度数据上报到数据库中。在调用的sqlite3_get_table函数里面使用select语句,即可实现数据库中记录的查询;
- 温度记录到一定的量之后,就需要回滚,在调用的sqlite3_exec语句里使用delete语句,即可实现记录的删除。在项目里,建议以日期为标准删除过期的温度数据。
细节与异常处理
此模块为整个项目中的细节归纳,和本人的处理经验。
- 一开始,本人是以一个小时为时间间隔,记录温度数据,此方法相比于主动设置时间间隔来说太不灵活。后来本人利用参数解析函数getopt_long,使得程序可以以“时分秒”三个选项来主动设置时间间隔;
- 树莓派上安装的数据库软件可能出现因文件丢失而无法使用的情况,此时就不能完成温度的上报。补偿措施就是另创建一个文本文件作为日志,与数据库同步记录温度信息;
- 程序可能因为捕捉到异常信号而终止、暂停或退出,为了知道其原因,建议新建一个文本文件实时记录程序的运行状态。利用sigaction函数,可以实现异常终止信号的捕捉与另类处理;
- 一般来说,服务器端的程序都是在后台进行执行的,使用daemon(int no_change_dir, int no_close_std)函数即可实现,但是其中的参数必须正确选定,需要考虑程序放在后台执行时,是否要改变工作路径到根目录下(根目录下创建、读写文件需要root权限,无root权限操作时,程序会出错*),是否要关闭标准输出,本人曾在开发时在此栽过大跟头,读者需注意;
- 文本文件调用open函数即可实现回滚:open(FILE_PATH, O_TRUNC),之后文件被清空;
- 本人在项目开发时,记录温度数据离不开写入记录时间,此时需要注意系统时间所在的时区,本人最初发现自己的记录时间比本地时间(CST)晚8小时,后来才查明是因为系统时间是使用的UTC时间(格林尼治时间)。利用结构体tm(需包含头文件time.h),时间的格式可以自己主动设置,获取时间的具体方法,可以通过"man 3 localtime"或"man 3 time"在Linux man手册里面详细获知;
- write函数和dprintf函数,都可以实现将字符串写入文件,但两者的区别在于,write会写入字符串中的NULL字符,因而以vim打开文件后会出现少量乱码,dprintf则不会。本人起初使用的是write函数,优化代码后,所有write函数均改为了dprintf函数;
- 整个项目开发期间,务必请保持路由器正常的公网IP动态分配,以及端口转发功能,若这两方面异常,客户端将无法正常获取温度信息。
此项目的blog三部曲已经完成了。有关此项目的构想和实现,贯穿了本人整个Unix环境高级编程的学习阶段。此项目的顺利完成,也算是本人在物联网开发学习中的一个微小的进步吧。