
嵌入式操作系统 | uloop 定时器周期任务与网络上报本课程开源地址Giteehttps://gitee.com/fujianxinxi/qianrushixitongyingyongkaifa.git课件、示例代码与验收脚本都在该仓库可直接git clone或下载 ZIP 使用。模块五 · 2 学时 · 环境VMware Ubuntu 20.04一句话概述讲清 uloop 的定时器用法uloop_timeout_set 排一次、回调里再排一次就是周期任务用1 秒心跳 3 秒采集并 UDP 上报的程序把定时器和网络收发放进同一个事件循环一步一步给出命令与运行现象配套课堂练习与课后作业。本节目标会用uloop_timeout_set()排一个定时任务并知道周期任务是怎么实现的会写定时采集 UDP 上报的程序让它和其它任务共用一个事件循环会读懂程序打印的时间节奏心跳和采集为什么是 1:3一、讲解内容1.1 知识讲解uloop 定时器就一个函数。uloop_timeout_set(t, 毫秒)把结构体t排进定时队列到点调用t.cb。注意单位是毫秒1000 1 秒。周期任务 回调里再排一次。uloop 的定时器默认只响一次想让它每 3 秒响一次就在回调末尾再uloop_timeout_set(t, 3000)——这就是周期任务的实现方式嵌入式里的心跳、周期采集、超时重试都这么做。为什么比sleep好。定时器回调之间事件循环还能照常处理网络数据——sleep(3)会把整个程序卡住 3 秒期间来什么包都处理不了。一个循环里可以挂很多任务。本课同时挂两个定时器心跳 1 秒、采集 3 秒和一个 UDP 套接字它们互不干扰谁到点就执行谁。结束条件要写清楚。本课采满 3 次就uloop_end()退出方便课堂观察真实项目里通常一直跑靠CtrlC或 systemd 管理课时 07 学过。1.2 操作步骤一条命令一步每步都有现象第 1 步编译cd~/lab/课时18-uloop定时器 gcc-Wall-Wextra-ouloop_timer uloop_timer.c-lubox现象无输出即成功。第 2 步先启动 Python 接收端终端 A先开python3 udp_receiver.py9999现象UDP 接收端已启动监听 9999 端口等 C 程序上报CtrlC 退出第 3 步终端 B 运行 C 程序后开./uloop_timer127.0.0.19999现象重点看两个节奏心跳每秒一次采集每 3 秒一次采集后立刻上报程序大约 9 秒后自己结束① uloop 初始化完成 ② 上报目标127.0.0.1:9999 ③ 已挂两个定时器心跳 1 秒 / 采集 3 秒 ④ 进入 uloop_run() [心跳] 第 1 次程序还活着uloop 在跑 [心跳] 第 2 次程序还活着uloop 在跑 ★ 第 1 次采集温度 25.0 C → 已通过 UDP 上报 [心跳] 第 3 次程序还活着uloop 在跑 [心跳] 第 4 次程序还活着uloop 在跑 [心跳] 第 5 次程序还活着uloop 在跑 ★ 第 2 次采集温度 25.5 C → 已通过 UDP 上报 [心跳] 第 6 次程序还活着uloop 在跑 [心跳] 第 7 次程序还活着uloop 在跑 [心跳] 第 8 次程序还活着uloop 在跑 ★ 第 3 次采集温度 26.0 C → 已通过 UDP 上报 采集 3 次完成结束事件循环 [心跳] 第 9 次程序还活着uloop 在跑 ⑤ uloop 结束共心跳 9 次采集 3 次终端 APython 接收端的现象——上报的数据真的到了对面第 1 条上报来自 127.0.0.1:33973第 1 次采集温度 25.0 C 第 2 条上报来自 127.0.0.1:33973第 2 次采集温度 25.5 C 第 3 条上报来自 127.0.0.1:33973第 3 次采集温度 26.0 C现象解读① 心跳和采集各自按自己的周期执行说明一个事件循环里可以挂多个定时任务② 第 3 次采集后程序自己结束我们写了uloop_end()③ 采集数据通过 UDP 发出去了Python 那边收到了 3 条。第 4 步改节奏看变化动手改一改把on_collect里的uloop_timeout_set(t, 3000)改成1000重新编译运行——心跳和采集都变成每秒一次把COLLECT_MAX从 3 改成 5就采 5 次再结束。第 5 步收工。终端 A 按CtrlC停掉 Python 接收端。1.3 常见错误与排查现象原因解决只会响一次然后程序就结束了回调里没有再uloop_timeout_set在回调末尾重新排下一次才是周期任务周期明显不对快 10 倍/慢 1000 倍单位搞错毫秒 vs 秒uloop 用毫秒1 秒要写 1000程序一直跑不完忘了结束条件加计数够了调用uloop_end()或按 CtrlCPython 端收不到数据端口不一致、或先关掉了接收端两边端口一致接收端先启动运行时libubox.so: cannot open shared object file没执行sudo ldconfig回课时 15 重新装并sudo ldconfig二、学生练习本节命令速查表命令作用示例gcc … -lubox编译gcc -Wall -Wextra -o uloop_timer uloop_timer.c -luboxpython3 udp_receiver.py 端口启动接收端先开python3 udp_receiver.py 9999./uloop_timer IP 端口运行程序./uloop_timer 127.0.0.1 9999uloop_timeout_set(t, 毫秒)排一个定时任务uloop_timeout_set(beat, 1000)uloop_timeout_cancel(t)取消定时任务uloop_timeout_cancel(beat)编号任务提示预期结果L18.1按步骤跑通先起接收端再跑 C 程序心跳 1 秒 1 次、采集 3 秒 1 次Python 收到 3 条L18.2把采集周期改成 1 秒改uloop_timeout_set(t, 1000)心跳和采集都是每秒一次L18.3改成采 5 次改COLLECT_MAX打印到第 5 次采集才结束L18.4让心跳每 2 秒一次改心跳那个 1000 为 2000心跳节奏变慢采集不受影响L18.5加一个上报失败提示把接收端关掉再运行程序sendto 依然成功UDP 不保证送达这说明 UDP 只管发出去L18.6用date观察真实时间在心跳回调里打印time(NULL)两次心跳时间差约 1 秒三、作业必做完成 L18.1、L18.2提交 C 程序输出截图与 Python 接收端截图并回答① 周期定时任务是怎么实现的② uloop 定时器的单位是什么③ 为什么定时器比sleep()好选做把采集改成读一个真实数据比如/proc/loadavg里的负载值再上报给 Python 端提交截图 文字回答命名课时18-作业截图.png四、本节小结与下节预告本节掌握了 uloop 定时器uloop_timeout_set排任务、回调里再排就是周期任务并做出了定时采集 UDP 上报的完整小程序。下节换到配置用 uci 读写 OpenWrt 风格的配置文件一条命令一条命令地看结果。附录怎么运行 完整代码一个课件就够用先按附录 A 的顺序敲命令服务端/对端先开自己的程序后开再按附录 B 抄代码。行尾请用 LF。附录 A怎么运行注意先后顺序gcc-Wall-Wextra-ouloop_timer uloop_timer.c-luboxpython3 udp_receiver.py9999# 终端A先启动接收端./uloop_timer127.0.0.19999# 终端B心跳 1 秒、采集 3 秒采 3 次后自己结束附录 B文件uloop_timer.c/* * uloop_timer.c —— 课时 18uloop 定时器周期任务 网络上报 * * 编译gcc -Wall -Wextra -o uloop_timer uloop_timer.c -lubox * 运行./uloop_timer 127.0.0.1 9999 * * 两个定时任务跑在同一个事件循环里 * · 心跳定时器每 1 秒打印一次 程序还活着 * · 采集定时器每 3 秒采集一次数据并通过 UDP 上报给 Python 工具 * 采满 3 次后调用 uloop_end() 退出课堂上不用手动 CtrlC现象更清楚。 */#includestdio.h#includestdlib.h#includestring.h#includestdint.h#includeunistd.h#includearpa/inet.h#includesys/socket.h#includelibubox/uloop.h#defineCOLLECT_MAX3/* 采 3 次就结束方便观察 */staticstructuloop_timeoutbeat;/* 1 秒心跳 */staticstructuloop_timeoutcollect;/* 3 秒采集 */staticstructsockaddr_inserver;staticintsock-1;staticintbeat_cnt0;staticintcollect_cnt0;/* 心跳定时器回调里重新排下一次就变成周期任务 */staticvoidon_beat(structuloop_timeout*t){beat_cnt;printf( [心跳] 第 %d 次程序还活着uloop 在跑\n,beat_cnt);uloop_timeout_set(t,1000);}/* 采集采一次、上报一次采满 COLLECT_MAX 次就结束事件循环 */staticvoidon_collect(structuloop_timeout*t){charmsg[128];floattemp25.0fcollect_cnt*0.5f;/* 假装采到的温度 */collect_cnt;snprintf(msg,sizeof(msg),第 %d 次采集温度 %.1f C,collect_cnt,temp);printf(★ %s\n,msg);if(sendto(sock,msg,strlen(msg),0,(structsockaddr*)server,sizeof(server))0){perror(sendto);}else{printf( → 已通过 UDP 上报\n);}if(collect_cntCOLLECT_MAX){printf(采集 %d 次完成结束事件循环\n,COLLECT_MAX);uloop_end();return;}uloop_timeout_set(t,3000);}intmain(intargc,char*argv[]){intport9999;if(argc3){portatoi(argv[2]);}uloop_init();printf(① uloop 初始化完成\n);socksocket(AF_INET,SOCK_DGRAM,0);if(sock0){perror(socket);return1;}memset(server,0,sizeof(server));server.sin_familyAF_INET;server.sin_porthtons((uint16_t)port);inet_pton(AF_INET,argc2?argv[1]:127.0.0.1,server.sin_addr);printf(② 上报目标%s:%d\n,argc2?argv[1]:127.0.0.1,port);beat.cbon_beat;uloop_timeout_set(beat,1000);collect.cbon_collect;uloop_timeout_set(collect,3000);printf(③ 已挂两个定时器心跳 1 秒 / 采集 3 秒\n);printf(④ 进入 uloop_run()\n\n);uloop_run();close(sock);uloop_done();printf(\n⑤ uloop 结束共心跳 %d 次采集 %d 次\n,beat_cnt,collect_cnt);return0;}附录 C文件udp_receiver.py#!/usr/bin/env python3课时 18 测试工具UDP 数据接收端打印 C 程序上报的每条数据 用法python3 udp_receiver.py [端口] # 端口默认 9999CtrlC 退出 importsocketimportsys portint(sys.argv[1])iflen(sys.argv)1else9999ssocket.socket(socket.AF_INET,socket.SOCK_DGRAM)s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)s.bind((0.0.0.0,port))print(fUDP 接收端已启动监听{port}端口等 C 程序上报CtrlC 退出)count0try:whileTrue:data,addrs.recvfrom(1024)count1print(f第{count}条上报来自{addr[0]}:{addr[1]}{data.decode(utf-8,replace)})exceptKeyboardInterrupt:print(f\n接收端退出共收到{count}条)finally:s.close()