Glib 的事件机制

#+SETUPFILE: ~/Dropbox/Doc/Org_Templates/level-1.org

GLIB 的事件机制比较复杂,设计好多概念和模块,网络上相关的介绍也比较乱, 最好的理解方式就是自己动手敲一遍相关代码,然后做一个总结。

GLib 事件机制介绍

GLib 的事件框架主要有以下组件:

  • GMainLoop : 管理所有可用事件源, 添加初始事件源后,会不断检查来自每个事件源的新事件并分派相关函数处理.
  • GMainContext : 每个时间源创建之后都需要关联到一个 GMainContext 中。
  • GSource : 具体的事件源对象.

GLib 的事件分发机制流程:

  • 创建一个 GMainContext
  • 创建一个 GSource, 指定该 source 的相关元素,如准备事件,检查事件, 分发执行函数等。将需要监控的事件源,回调函数等(如果有,如文件描述符) 添加到source中,并将这个source关联到 GMainContext上。
  • 创建一个 GMainLoop,并将 GMainContext 关联到这个loop上。
  • 运行 loop。

此后 GMainLoop 将会按照我们设定的条件周而复始的开始处理事件,如对于文件读写, 当有文件可读/可写时候,调用相关的回调函数读写等。

Demo

下面的 demo 展示从事件创建 -> 添加到context -> 关联到loop,运行的基本流程

#include <glib.h>
#include <stdio.h>
#include <string.h>

typedef struct DemoSource {
    GSource source;
    char *data;
} DemoSource;

/**
 * 如果 GSourceFuncs::prepare() 返回 TRUE, 则 GSourceFuncs::dispatch 立即执行
 * 如果 GSourceFuncs::prepare() 返回 FALSE:
 *   --> 等待 p_timeout 时间之后:
 *     --> 如果 GSourceFuncs::check() 返回 TRUE, 则 GSourceFuncs::dispatch 立即执行
 *     --> 如果 GSourceFuncs::check() 返回 FALSE, 继续进入 GSourceFuncs::prepare()
 */
static gboolean source_prepare(GSource *source, gint *p_timeout)
{
    static int i = 0;
    if (i++ % 4 == 0) {
        printf("%s:%d\n", __FUNCTION__, __LINE__);
        return TRUE;
    }

    printf("%s:%d\n", __FUNCTION__, __LINE__);
    *p_timeout = 1000;
    return FALSE;
    return FALSE;
}

static gboolean source_check(GSource *source)
{
    printf("%s:%d\n", __FUNCTION__, __LINE__);
    return TRUE;
}

static gboolean source_dispatch(GSource *source, GSourceFunc callback,
                                       gpointer user_data)
{
    printf("%s:%d\n", __FUNCTION__, __LINE__);
    return TRUE;
}

static GSourceFuncs source_funcs = {
    .prepare = source_prepare,
    .check = source_check,
    .dispatch = source_dispatch,
};

int main(int argc, char *argv[])
{
    char *data = "demo source";
    GMainContext *main_context;

    GSource *source = g_source_new(&source_funcs, sizeof(DemoSource));
    DemoSource *demo_source = (DemoSource *)source;
    demo_source->data = data;

    main_context = g_main_context_new();
    g_source_attach(source, main_context);
    g_source_unref(source);

    GMainLoop *loop = g_main_loop_new(main_context, FALSE);
    g_main_loop_run(loop);
    g_main_loop_unref(loop);

    return 0;
}

对于文件事件源,类似的处理,只不过多了文件是否可读写的相关操作。

#include <glib.h>
#include <stdio.h>
#include <string.h>

typedef struct DemoSource {
    GSource source;
    GIOChannel *channel;
    GPollFD fd;
} DemoSource;

/**
 * 对文件类型的source,GSourceFuncs::prepare() 返回 -1,
 * 然后在 GSourceFuncs::check() 中检查是否读写事件已经就绪.
 * 如果 check 检查到就绪,dispatch 调用callback执行。
 */
static gboolean source_prepare(GSource *source, gint *p_timeout)
{
    printf("%s:%d\n", __FUNCTION__, __LINE__);

    *p_timeout = -1;
    return FALSE;
}

static gboolean source_check(GSource *source)
{
    DemoSource *demo_source = (DemoSource *)source;

    if (demo_source->fd.revents != demo_source->fd.events) {
        return FALSE;
    }

    return TRUE;
}

static gboolean source_dispatch(GSource *source, GSourceFunc callback,
                                gpointer user_data)
{
    if (callback) {
        callback(source);
    }

    return TRUE;
}

static GSourceFuncs source_funcs = {
    .prepare = source_prepare,
    .check = source_check,
    .dispatch = source_dispatch,
};

gboolean watch(gpointer user_data)
{
    char *buf = NULL;
    gsize len = 0;
    DemoSource *source = (DemoSource *)user_data;

    g_io_channel_read_line(source->channel, &buf, &len, NULL, NULL);
    if (len > 0) {
        printf("%s", buf);
    }

    g_free(buf);
    return TRUE;
}

int main(int argc, char *argv[])
{
    GMainContext *main_context;

    GSource *source = g_source_new(&source_funcs, sizeof(DemoSource));
    DemoSource *demo_source = (DemoSource *)source;

    demo_source->channel = g_io_channel_new_file("/tmp/test.txt", "r", NULL);
    demo_source->fd.fd = g_io_channel_unix_get_fd(demo_source->channel);
    demo_source->fd.events = G_IO_IN;
    g_source_add_poll(source, &demo_source->fd);
    g_source_set_callback(source, watch, NULL, NULL);

    main_context = g_main_context_new();
    g_source_attach(source, main_context);
    g_source_unref(source);

    GMainLoop *loop = g_main_loop_new(main_context, FALSE);
    g_main_loop_run(loop);
    g_main_loop_unref(loop);

    return 0;
}

一般使用中直接使用 g_io_create_watch/g_io_add_watch 就可以简化相关代码

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦