Mongoose 笔记系列用于记录学习 Mongoose 的一些内容。
Mongoose 是一个 C/C++ 的网络库。它为 TCP、UDP、HTTP、WebSocket、MQTT 实现了事件驱动的、非阻塞的 API。
项目地址:
https://github.com/cesanta/mongoose
下面学习一下 Mongoose 项目代码中的 sntp-time-sync 示例程序 ,这个示例程序实现了一个简易的 SNTP 客户端,实现与远程 SNTP 服务器同步时间。
示例程序代码如下:
// Copyright (c) 2022 Cesanta Software Limited
// All rights reserved
//
// Synchronize time with remote SNTP server
// A working time() call is required by TLS, so an embedded device without
// a clock that wants to use TLS, must sync time via SNTP.
#include "mongoose.h"
// The UNIX epoch of the boot time. Initially, we set it to 0. But then after
// SNTP response, we update it to the correct value, which will allow us to
// use time(). Uptime in milliseconds is returned by mg_millis().
static time_t s_boot_timestamp = 0;
// SNTP client connection
static struct mg_connection *s_sntp_conn = NULL;
// On embedded systems, rename to time()
time_t my_time(time_t *tp) {
time_t t = s_boot_timestamp + mg_millis() / 1000;
if (tp != NULL) *tp = t;
return t;
}
// SNTP client callback
static void sfn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_SNTP_TIME) {
int64_t t = *(int64_t *) ev_data;
MG_INFO(("Got SNTP time: %lld ms from epoch", t));
s_boot_timestamp = (time_t) ((t - mg_millis()) / 1000);
} else if (ev == MG_EV_CLOSE) {
s_sntp_conn = NULL;
}
(void) fn_data, (void) c;
}
// Called every 5 seconds. Increase that for production case.
static void timer_fn(void *arg) {
struct mg_mgr *mgr = (struct mg_mgr *) arg;
if (s_sntp_conn == NULL) s_sntp_conn = mg_sntp_connect(mgr, NULL, sfn, NULL);
if (s_sntp_conn != NULL) mg_sntp_request(s_sntp_conn);
}
int main(void) {
struct mg_mgr mgr; // Event manager
mg_mgr_init(&mgr); // Initialise event manager
mg_log_set(MG_LL_DEBUG); // Set log level
mg_timer_add(&mgr, 5000, MG_TIMER_REPEAT | MG_TIMER_RUN_NOW, timer_fn, &mgr);
for (;;) mg_mgr_poll(&mgr, 300); // Infinite event loop
mg_mgr_free(&mgr); // Free manager resources
return 0;
}
下面从main
函数开始分析代码。
定义变量,struct mg_mgr
是用于保存所有活动连接的事件管理器。
struct mg_mgr mgr; // Event manager
初始化一个事件管理器,也就是将上面定义的struct mg_mgr
变量 mgr
中的数据进行初始化。
mg_mgr_init(&mgr); // Initialise event manager
设置 Mongoose 日志记录级别,设置等级为 MG_LL_DEBUG
。
mg_log_set(MG_LL_DEBUG); // Set log level
调用mg_timer_add
设置一个定时器,这会将其添加到事件管理器的内部定时器列表中。其中的参数5000
表示 5000 毫秒,MG_TIMER_REPEAT
表示定时重复调用函数,MG_TIMER_RUN_NOW
表示设置定时器后立即调用,timer_fn
是要调用的函数,&mgr
是要传递的参数。事件管理器将以参数 5000 毫秒的时间间隔调用 timer_fn
函数,并将参数 &mgr
传递给它。
mg_timer_add(&mgr, 5000, MG_TIMER_REPEAT | MG_TIMER_RUN_NOW, timer_fn, &mgr);
然后是事件循环,mg_mgr_poll
遍历所有连接,接受新连接,发送和接收数据,关闭连接,并为各个事件调用事件处理函数。
for (;;) mg_mgr_poll(&mgr, 300); // Infinite event loop
调用 mg_mgr_free
关闭所有连接,释放所有资源。
mg_mgr_free(&mgr); // Free manager resources
下面我们先看下timer_fn
的实现:
// Called every 5 seconds. Increase that for production case.
static void timer_fn(void *arg) {
传入的参数是在main
函数中初始化的事件管理器,用于下面连接 SNTP 服务器。
struct mg_mgr *mgr = (struct mg_mgr *) arg;
如果s_sntp_conn
为NULL
,表示目前还没连接到远程 SNTP 服务器,调用mg_sntp_connect
连接 SNTP 服务器。其中第一个参数是事件管理器,第二个参数用于指定远程 URL,但这里为NULL
,默认 URL 为udp://time.google.com:123
。第三个参数sfn
是事件处理函数。第四个参数是要传递给sfn
的参数,这里没有就为NULL
。
if (s_sntp_conn == NULL) s_sntp_conn = mg_sntp_connect(mgr, NULL, sfn, NULL);
如果s_sntp_conn
不为NULL
,表示目前已连接到远程 SNTP 服务器,调用mg_sntp_request
向 SNTP 服务器发送时间请求。
if (s_sntp_conn != NULL) mg_sntp_request(s_sntp_conn);
}
下面我们看下事件处理函数sfn
的实现:
// SNTP client callback
static void sfn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
判断是否收到MG_EV_SNTP_TIME
事件,如果是则表示收到 SNTP 时间。将 SNTP 时间打印出来,然后使用从服务器获取当前时间t
减去我们的正常运行时间来得到我们的启动时间戳s_boot_timestamp
。其中mg_millis
函数以毫秒为单位返回当前正常运行时间。这里获取的时间t
是 UNIX 纪元时间,也就是记录自 1970 年 1 月 1 日起已经过多少毫秒。
if (ev == MG_EV_SNTP_TIME) {
int64_t t = *(int64_t *) ev_data;
MG_INFO(("Got SNTP time: %lld ms from epoch", t));
s_boot_timestamp = (time_t) ((t - mg_millis()) / 1000);
}
s_boot_timestamp
是一个全局变量,定义如下:
// The UNIX epoch of the boot time. Initially, we set it to 0. But then after
// SNTP response, we update it to the correct value, which will allow us to
// use time(). Uptime in milliseconds is returned by mg_millis().
static time_t s_boot_timestamp = 0;
s_boot_timestamp
用于自己实现的time
函数,这个my_time
函数的使用方式与time
函数一致。s_boot_timestamp
实际上就是保存时间戳的误差值,在获取当前时间的时候将误差值加上得到正确的时间。
// On embedded systems, rename to time()
time_t my_time(time_t *tp) {
time_t t = s_boot_timestamp + mg_millis() / 1000;
if (tp != NULL) *tp = t;
return t;
}
回到刚才的事件处理函数sfn
。
判断是否收到MG_EV_CLOSE
事件,表示连接关闭,将s_sntp_conn
置NULL
。
} else if (ev == MG_EV_CLOSE) {
s_sntp_conn = NULL;
}
(void) fn_data, (void) c;
}
sntp-time-sync 的示例程序代码就都解析完了,下面实际运行一下 sntp-time-sync 程序进行测试验证。
打开示例程序,编译并运行:
pi@raspberrypi:~ $ cd Desktop/study/mongoose/examples/sntp-time-sync/
pi@raspberrypi:~/Desktop/study/mongoose/examples/sntp-time-sync$ make
cc ../../mongoose.c -I../.. -W -Wall -DMG_ENABLE_LINES=1 -o example main.c
./example
6b9aeb9 3 net.c:159:mg_connect 1 0xffffffffffffffff udp://time.google.com:123
6b9aebb 3 net.c:159:mg_connect 2 0xffffffffffffffff udp://8.8.8.8:53
6b9aebc 3 sock.c:146:mg_send 2 0x4 0:0 33 err 0
6b9aebe 1 sntp.c:62:mg_sntp_request 1 wait until resolved
6b9af07 3 sock.c:273:read_conn 2 0x4 snd 0/0 rcv 0/2048 n=97 err=0
6b9af08 3 dns.c:165:dns_cb 1 time.google.com is 216.239.35.12
6b9af0a 3 sock.c:146:mg_send 1 0x5 0:0 48 err 0
然后一直没接收到回复,发现是连不上time.google.com
,估计是在国内连不上。
6c2fa42 3 sock.c:146:mg_send 1 0x5 0:0 48 err 0
6c30e32 3 sock.c:146:mg_send 1 0x5 0:0 48 err 0
6c32223 3 sock.c:146:mg_send 1 0x5 0:0 48 err 0
6c334e7 3 sock.c:146:mg_send 1 0x5 0:0 48 err 0
6c348d8 3 sock.c:146:mg_send 1 0x5 0:0 48 err 0
修改 URL ,改成国内的服务器"udp://cn.pool.ntp.org:123"
。注意,分配给 NTP 的UDP 端口号为 123。
if (s_sntp_conn == NULL) s_sntp_conn = mg_sntp_connect(mgr, "udp://cn.pool.ntp.org:123", sfn, NULL);
重新编译运行:
pi@raspberrypi:~/Desktop/study/mongoose/examples/sntp-time-sync$ make
cc ../../mongoose.c -I../.. -W -Wall -DMG_ENABLE_LINES=1 -o example main.c
./example
72b1970 3 net.c:159:mg_connect 1 0xffffffffffffffff udp://cn.pool.ntp.org:123
72b1970 3 net.c:159:mg_connect 2 0xffffffffffffffff udp://8.8.8.8:53
72b1970 3 sock.c:146:mg_send 2 0x4 0:0 33 err 0
72b1970 1 sntp.c:62:mg_sntp_request 1 wait until resolved
72b19c1 3 sock.c:273:read_conn 2 0x4 snd 0/0 rcv 0/2048 n=97 err=0
72b19c2 3 dns.c:165:dns_cb 1 cn.pool.ntp.org is 162.159.200.1
72b19c2 3 sock.c:146:mg_send 1 0x5 0:0 48 err 0
72b1a5f 3 sock.c:273:read_conn 1 0x5 snd 0/0 rcv 0/2048 n=48 err=0
72b1a60 2 sntp.c:46:sntp_cb 1 got time: 1673005400198 ms from epoch
72b1a62 2 main.c:39:sfn Got SNTP time: 1673005400198 ms from epoch
可以看到成功获取到了时间并且打印了出来。
还可以在程序中添加如下代码,使用my_time
函数获取当前的时间,再使用localtime
函数将时间转换成年月日时分秒的形式:
void convertime(void) {
time_t current_time;
struct tm* now_time;
my_time(¤t_time);
now_time = localtime(¤t_time);
printf("%4.4d年%2.2d月%2.2d日,%2.2d:%2.2d:%2.2d\n", now_time->tm_year+1900, now_time->tm_mon+1, now_time->tm_mday,
now_time->tm_hour, now_time->tm_min, now_time->tm_sec);
}
在程序中就会将转换后的时间打印出来:
72b1a60 2 sntp.c:46:sntp_cb 1 got time: 1673005400198 ms from epoch
72b1a62 2 main.c:39:sfn Got SNTP time: 1673005400198 ms from epoch
2023年01月06日,19:43:19
72b2d2a 3 sock.c:146:mg_send 1 0x5 0:0 48 err 0
72b2dc4 3 sock.c:273:read_conn 1 0x5 snd 0/0 rcv 0/2048 n=48 err=0
72b2dc5 2 sntp.c:46:sntp_cb 1 got time: 1673005405165 ms from epoch
72b2dc7 2 main.c:39:sfn Got SNTP time: 1673005405165 ms from epoch
2023年01月06日,19:43:24
examples/sntp-time-sync
Documentation
rfc2030
本文链接:https://blog.csdn.net/u012028275/article/details/130036376