C语言TimerListener如何实现事件监听?

99ANYc3cd6
预计阅读时长 24 分钟
位置: 首页 C语言 正文

C 语言标准库(如 ANSI C)中并没有一个叫做 timerListener 的原生类或函数。

c语言timerListener
(图片来源网络,侵删)

timerListener 是一个设计模式编程思想的体现,尤其在事件驱动的编程中非常常见,它的核心思想是:

当一个定时器触发时,程序需要执行一段特定的代码(回调函数),这段代码就是“监听器”(Listener)。

你可以把它想象成设置一个闹钟:

  1. 设置定时器:你告诉闹钟“10分钟后叫我”。
  2. 注册监听器:你告诉闹钟“叫我的时候,就播放《好运来》这首歌”。
  3. 等待:闹钟开始计时。
  4. 触发:10分钟后,闹钟响了,它执行了你注册的操作——播放《好运来》。

在这个比喻里:

c语言timerListener
(图片来源网络,侵删)
  • 定时器:闹钟本身。
  • 监听器:播放《好运来》这首歌这个行为。
  • 回调函数:实现播放《好运来》的那个函数。

在 C 语言中,我们通过函数指针来实现这种“监听器”模式。


核心实现:函数指针

函数指针是 C 语言实现回调机制的关键,它是一个指向函数的指针,通过这个指针可以间接调用该函数。

定义监听器函数类型

我们定义一个函数指针类型,这个类型代表了所有定时器事件监听器应该具有的“签名”(即参数和返回值)。

// 定义一个函数指针类型 TimerListener
// 它接受一个 void* 类型的参数(用于传递自定义数据),返回 void
typedef void (*TimerListener)(void* context);
  • typedef:为一种类型创建一个别名。
  • void (*...):这是一个函数指针。
  • (void* context):这是回调函数的参数。void* 是一个通用指针,非常灵活,可以指向任何类型的数据,这使得我们可以向监听器传递额外的信息,比如定时器的ID、用户数据等。
  • void:这是回调函数的返回值,通常事件处理函数不需要返回值。

创建一个简单的定时器结构体

为了管理定时器,我们通常会定义一个结构体来存储它的状态。

c语言timerListener
(图片来源网络,侵删)
typedef struct {
    int interval_ms;    // 定时间隔(毫秒)
    int is_running;     // 是否正在运行
    TimerListener listener; // 指向监听器函数的指针
    void* user_data;    // 传递给监听器的用户自定义数据
} Timer;

实现定时器的设置和触发逻辑

我们来实现一个简单的定时器管理器,这个管理器会检查每个定时器是否到期,如果到期,就调用它的监听器。

重要提示:以下代码是一个概念性示例,用于演示 timerListener 的工作原理,它使用了 sleep 来模拟时间流逝,这不是一个高效、精确的多线程定时器实现,在实际应用中,你会使用 select, poll, epoll (Linux) 或 kqueue (BSD/macOS) 等I/O多路复用技术,或者专门的多线程定时器库。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // for sleep()
#include <stdbool.h>
// --- 步骤1: 定义监听器函数类型 ---
typedef void (*TimerListener)(void* context);
// --- 步骤2: 创建定时器结构体 ---
typedef struct {
    int interval_ms;
    bool is_running;
    TimerListener listener;
    void* user_data;
} Timer;
// --- 步骤3: 实现一个简单的定时器触发器 ---
// 这个函数会检查所有注册的定时器
void check_and_trigger_timers(Timer* timers, int num_timers) {
    for (int i = 0; i < num_timers; i++) {
        if (timers[i].is_running) {
            // 在真实场景中,这里应该是基于真实时间戳的计算
            // 这里我们用 sleep 来模拟
            printf("Timer %d is running for %d ms...\n", i, timers[i].interval_ms);
            usleep(timers[i].interval_ms * 1000); // usleep 的单位是微秒
            printf("Timer %d triggered! Calling listener...\n", i);
            // 调用注册的监听器函数,并传递 user_data
            if (timers[i].listener) {
                timers[i].listener(timers[i].user_data);
            }
            timers[i].is_running = false; // 触发后停止
        }
    }
}
// --- 步骤4: 定义具体的监听器函数 ---
// 监听器函数1: 打印一条消息
void on_timer_event1(void* context) {
    printf("  -> Listener 1: Hello from Timer! Context: %p\n", context);
}
// 监听器函数2: 打印一个数字
void on_timer_event2(void* context) {
    int* num = (int*)context;
    printf("  -> Listener 2: The magic number is: %d\n", *num);
}
// 监听器函数3: 什么都不做,仅作演示
void on_timer_event3(void* context) {
    (void)context; // 避免未使用参数的警告
    printf("  -> Listener 3: This is a silent timer.\n");
}
int main() {
    // 创建并初始化几个定时器
    Timer timers[3];
    // 定时器1
    timers[0].interval_ms = 1000; // 1秒
    timers[0].is_running = true;
    timers[0].listener = on_timer_event1;
    timers[0].user_data = NULL; // 不需要额外数据
    // 定时器2
    int magic_number = 42;
    timers[1].interval_ms = 2000; // 2秒
    timers[1].is_running = true;
    timers[1].listener = on_timer_event2;
    timers[1].user_data = &magic_number; // 传递一个整数
    // 定时器3
    timers[2].interval_ms = 1500; // 1.5秒
    timers[2].is_running = true;
    timers[2].listener = on_timer_event3;
    timers[2].user_data = (void*)0xDEADBEEF; // 传递一个无意义的指针
    printf("Starting timers...\n");
    // 模拟主循环
    for (int i = 0; i < 3; i++) {
        check_and_trigger_timers(timers, 3);
        // 在真实应用中,这里会是一个阻塞等待,直到有定时器到期或新事件发生
        // 而不是像这样循环检查
    }
    printf("All timers have been processed.\n");
    return 0;
}

如何编译和运行

将上述代码保存为 timer_example.c,然后使用 GCC 编译:

gcc timer_example.c -o timer_example
./timer_example

预期输出

由于 usleep 是阻塞的,并且定时器是顺序执行的,输出会像这样:

Starting timers...
Timer 0 is running for 1000 ms...
Timer 0 triggered! Calling listener...
  -> Listener 1: Hello from Timer! Context: (nil)
Timer 1 is running for 2000 ms...
Timer 1 triggered! Calling listener...
  -> Listener 2: The magic number is: 42
Timer 2 is running for 1500 ms...
Timer 2 triggered! Calling listener...
  -> Listener 3: This is a silent timer.
All timers have been processed.

实际应用场景和高级库

上面的例子很简单,但在实际项目中,定时器需求更复杂:

  1. 高精度:需要纳秒或微秒级的精度。
  2. 高并发:需要同时管理成百上千个定时器。
  3. 非阻塞:定时器检查不能阻塞主线程或主事件循环。
  4. 可取消/重置:能够随时停止或修改一个定时器。

开发者通常会使用成熟的库来处理定时器任务。

在 Linux 环境下:libeventlibuv

这些是事件通知库,它们内部实现了高效的多路复用定时器。

libuv 示例:

libuv 是一个跨平台的支持异步 I/O 的库,它的 uv_timer_t 是实现 timerListener 模式的绝佳范例。

#include <stdio.h>
#include <uv.h>
// 这就是我们的 "timerListener" 函数
void timer_callback(uv_timer_t* handle) {
    printf("Timer callback triggered!\n");
    // 获取用户数据
    int* count = (int*)handle->data;
    (*count)++;
    if (*count >= 5) {
        printf("Timer reached 5 counts, stopping.\n");
        uv_timer_stop(handle);
    }
}
int main() {
    uv_loop_t* loop = uv_default_loop();
    uv_timer_t timer_req;
    int repeat_count = 0;
    // 将用户数据与定时器关联
    timer_req.data = &repeat_count;
    // 初始化定时器
    uv_timer_init(loop, &timer_req);
    // 启动定时器
    // 第一个参数是 loop,第二个是 timer 对象
    // 第三个是延迟时间(毫秒),第四个是重复间隔(0表示只运行一次)
    uv_timer_start(&timer_req, timer_callback, 1000, 1000);
    // 运行事件循环,直到没有更多事件需要处理
    uv_run(loop, UV_RUN_DEFAULT);
    // 清理
    uv_timer_stop(&timer_req);
    uv_close((uv_handle_t*)&timer_req, NULL);
    return 0;
}

在这个 libuv 的例子中:

  • uv_timer_t 是我们的 Timer 结构体。
  • timer_callback 就是我们的 TimerListener 函数。
  • uv_timer_start 用于“注册”定时器和它的“监听器”。
  • uv_run 是主循环,它会非阻塞地等待定时器事件并触发回调。

在嵌入式系统或 RTOS 中

在嵌入式领域,timerListener 模式同样非常普遍,通常由操作系统或硬件抽象层提供定时器服务。

  • FreeRTOS: xTimerCreate() 创建一个软件定时器,并为其指定一个回调函数。
  • Arduino: myTimer.attach(callback, interval_ms) 是典型的 timerListener 应用。

概念 解释 C 语言实现方式
timerListener 一种设计模式,指当定时器触发时被调用的代码块。 函数指针
回调函数 timerListener 的具体实现,包含了定时器到期后要执行的逻辑。 一个符合特定签名的普通 C 函数。
上下文 传递给回调函数的额外数据,用于区分不同定时器或传递状态。 使用 void* 指针作为回调函数的参数。
实际应用 在需要延迟执行、周期性任务或超时处理的场景中。 使用 libuv, libevent 等高级库,或操作系统提供的定时器API。

timerListener 在 C 语言中不是一个具体的“东西”,而是一种通过函数指针实现的强大编程范式,它将“何时执行”(定时器)和“执行什么”(监听器逻辑)分离开来,是构建灵活、模块化事件驱动程序的基础。

-- 展开阅读全文 --
头像
织梦首页为何不生成HTML文件?
« 上一篇 2025-12-21
织梦tagurl修改后404,如何解决?
下一篇 » 2025-12-21

相关文章

取消
微信二维码
支付宝二维码

目录[+]