avatar

目录
glib-The Main Event Loop

代码地址:glib-The Main Event Loop

API

c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/*
* glib-The Main Event Loop
*/

/* API

GMainLoop *g_main_loop_new(GMainContext *context, //NULL:程序会分配一个默认的Context给GMainLoop
gboolean is_running);


//把GSource加到GMainContext
guint g_source_attach(GSource *source , GMainContext *context);



void g_source_set_callback (GSource *source,
GSourceFunc func,
gpointer data, //要传递给回调函数的数据
GDestroyNotify notify);

GSource * g_source_new (GSourceFuncs *source_funcs, //GSource的接口函数
guint struct_size); //事件源的大小

接口函数如下
struct _GSourceFuncs
{
  gboolean (*prepare)  (GSource    *source,
                        gint       *timeout_);
  gboolean (*check)    (GSource    *source);
  gboolean (*dispatch) (GSource    *source,
                        GSourceFunc callback,
                        gpointer    user_data);
  void     (*finalize) (GSource    *source); // Can be NULL
}

gboolean (*prepare) (GSource *source, gint *timeout_);进入睡眠之前,在g_main_context_prepare里,mainloop调用所有Source的prepare函数,计算最小的timeout时间,该时间决定下一次睡眠的时间。
gboolean (*check) (GSource *source); poll被唤醒后,在 g_main_context_check里,mainloop调用所有Source的check函数,检查是否有Source已经准备好了。如果poll是由于错误或者超时等原因唤醒的,就不必进行dispatch了。
gboolean (*dispatch) (GSource*source, GSourceFunc callback,gpointer user_data); 当有Source准备好了,在 g_main_context_dispatch里,mainloop调用所有Source的dispatch函数,去分发消息。
void (*finalize) (GSource *source); 在Source被移出时,mainloop调用该函数去销毁Source。

*/

主循环(g_main_loop)

c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118

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

GMainLoop *loop;
gint counter = 10;

gboolean callback01(gpointer arg){
g_print(".");
if(--counter ==0){
g_print("\n");
//退出循环
g_main_loop_quit(loop);
//注销定时器
return FALSE;
}
//定时器继续运行
return TRUE;
}

//当stdin有数据可读时被GSource调用的回调函数
gboolean callback02(GIOChannel *channel){
gchar* str;
gsize len;
//从stdin读取一行字符串
g_io_channel_read_line(channel, &str, &len, NULL, NULL);

//去掉回车键()
while(len > 0 && (str[len-1] == '\r' || str[len-1] == '\n'))
str[--len]='\0';
//反转字符串
for(;len;len--)
g_print("%c",str[len-1]);
g_print("\n");
//判断结束符
if(strcasecmp(str, "q") == 0){
g_main_loop_quit(loop);
}
g_free(str);
}

void add_source(GMainContext *context){
GIOChannel* channel;
GSource* source;

//这里我们监视stdin是否可读, stdin的fd默认等于1
channel = g_io_channel_unix_new(1);

//G_IO_IN表示监视stdin的读取状态
source = g_io_create_watch(channel, G_IO_IN);
g_io_channel_unref(channel);

//设置stdin可读的时候调用的回调函数
g_source_set_callback(source, (GSourceFunc)callback02, channel, NULL);

//把GSource附加到GMainContext
g_source_attach(source, context);
g_source_unref(source);
}



/**
* 功能:开启主循环,设置定时任务,在1s钟后退出主循环
* 解析:主线程跑起来后(g_main_loop_run)程序会一直阻塞,所以开启定时任务退出主循环(g_main_loop_quit)
* 主循环退出后,整个程序就结束了。
*/
int main01(){

//- 新建一个glib主循环
loop = g_main_loop_new(NULL, FALSE);

//- 设置定时器,每10毫秒调用callback
g_timeout_add(100,callback01,NULL);

//- 让循环体跑起来
g_main_loop_run(loop);
//g_main_loop_unref(loop);
}


/* event loop
- 所有需要异步操作的地方都可以用event loop
- event loop的这三个基本结构:GMainLoop, GMainContext和GSource
关系:GMainLoop -> GMainContext -> {GSource1, GSource2, GSource3......}
GSource则是具体的各种Event处理逻辑了。可以把GMainContext理解为GSource的容器。
把GSource加到GMainContext呢,则使用函数g_source_attach
*/

/* ref/unref
引用计数是追踪对象生命周期最常用的方法,一方面保证对象在有人使用时不会被销毁,另外一方面又保证不会因为忘记销毁对象而造成内存泄漏。
具有引用计数功能的对象一般都会提供两个函数:ref用于增加引用计数,unref用于减少引用计数,计数为0时销毁对象。
*/

/**
* 功能:从stdin读取字符串,然后反转字符串并输出到屏幕
*/
int main02(){
GMainContext *context;
//新建一个GMainContext
context = g_main_context_new();

//然后把GSource附到这个Context上
add_source(context);

//把Context赋给GMainLoop
loop = g_main_loop_new(context, FALSE);

g_print("input string('q' to quit):\n");

g_main_loop_run(loop);

g_main_loop_unref(loop);
g_main_context_unref(context);

}

事件源(GSource)

c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include <glib.h>
#include <glib/gprintf.h>

GMainLoop *loop;

//自定义的事件源
// 是一个继承 GSource 的结构体,即自定义事件源的结构体的第一个成员是 GSource 结构体,
// 其后便可放置程序所需数据
typedef struct _MySource MySource;
struct _MySource
{
GSource _source;
gchar text[256];
};
static gboolean prepare(GSource *source, gint *timeout)
{
*timeout = 0;

return TRUE;
}
static gboolean check(GSource *source)
{
return TRUE;
}
static gboolean dispatch(GSource *source, GSourceFunc callback, gpointer user_data)
{
MySource *mysource = (MySource *)source;

g_print("%s\n", mysource->text);

return TRUE;
}
static gboolean idle_func(gpointer data)
{
g_print("%s\n", (gchar *)data);

return TRUE;
}
static gboolean timeout_func(gpointer data)
{
static guint i = 0;

i += 1;
g_print ("%d\n", i);

return TRUE;
}

/* Glib事件源
GLib 内部实现了三种类型的事件源,分别是 Timeout, Idle, I/O, 也支持创建自定义的事件源
1. Timeout事件源
g_timeout_add(100,callback,NULL);
2. Idle事件源
3. 自定义事件源:g_source_new(见API)
*/

/**
* 1.1、自定义idle类型事件源:创建一个只会讲“Hello world!”的事件源,并将其添加到主事件循环默认的 GMainContext 中
*/
int main01(){
loop = g_main_loop_new(NULL, TRUE);
GMainContext *context = g_main_loop_get_context(loop);

//实现事件源所规定的接口,主要为 prepare, check, dispatch, finalize 等事件处理函数(回调函数)
GSourceFuncs source_funcs = {prepare, check, dispatch, NULL};
//自定义事件源,实现规定接口
GSource *source = g_source_new(&source_funcs, sizeof(MySource));
//赋值
g_sprintf(((MySource *)source)->text, "Hello world!");
//将事件源绑定在context上
g_source_attach(source, context);
g_source_unref(source);
g_main_loop_run(loop);

g_main_context_unref(context);
g_main_loop_unref(loop);

return 0;
}
/* 上述代码描述:
- g_main_loop_run 函数运行时,会迭代访问 GMainContext 的事件源列表
- g_main_loop_run 通过调用事件源的 prepare 接口并判断其返回值以确定各事件源是否作好准备。
如果各事件源的 prepare 接口的返回值为 TRUE,即表示该事件源已经作好准备,否则表示尚未做好准备。
- 若某事件源尚未作好准备 ,那么 g_main_loop 会在处理完那些已经准备好的事件后再次询问该事件源是否作好准备 ,这一过程是通过调用事件源的 check 接口而实现的
如果事件源依然未作好准备,即 check 接口的返回 FALSE,那么 g_main_loop_run 会让主事件循环进入睡眠状态。
主事件循环的睡眠时间是步骤 a 中遍历时间源时所统计的最小时间间隔 ,例如在 prepare 接口中可以像下面这样设置时间间隔。
static gboolean prepare(GSource *source, gint *timeout)
{
*timeout = 1000; //set time interval one second

return TRUE;
}
- 若事件源 prepare 与 check 函数返回值均为 TRUE,则 g_main_loop_run 会调用事件源的 dispatch 接口,由该接口调用事件源的响应函数。
事件源的响应函数是回调函数,可使用 g_source_set_callback 函数进行设定。在上例中, 我们没有为自定义的事件源提供响应函数。

- 上文自定义的事件源实际是 Idle 类型(低优先级)的,此类事件源,是指那些只有在主事件循环无其他事件源处理时才会被处理的事件源。
GLib 提供了预定义的空闲事件源类型,其用法见下面的示例。
*/

/**
* 1.2、预定义idle事件源:GLib 提供了预定义的空闲事件源类型(idle),其用法见下面的示例。
*/
int main02(){
GMainLoop *loop = g_main_loop_new(NULL, TRUE);
GMainContext *context = g_main_loop_get_context(loop);

g_idle_add(idle_func, "Hello world!");

g_main_loop_run(loop);

g_main_context_unref(context);
g_main_loop_unref(loop);

return 0;
}
/* 上述代码描述:
- idle_func 是 idle 事件源的响应函数,如果该函数返回值为 TRUE,那么它会在主事件循环空闲时重复被执行;
- 如果 idle_func 的返回值为 FALSE,那么该函数在执行一次后,便被主事件循环从事件源中移除。
- g_idle_add 函数内部定义了一个空闲事件源,并将用户定义的回调函数设为空闲事件源的响应函数, 然后将该事件源挂到主循环上下文。

*/

/**
* 2.1、预定义Timeout类事件源:GLib 也提供了预定义的定时器事件源
*/
int main03(){
GMainLoop *loop = g_main_loop_new(NULL, TRUE);
GMainContext *context = g_main_loop_get_context(loop);

g_timeout_add(1000, timeout_func, loop);

g_main_loop_run(loop);

g_main_context_unref(context);
g_main_loop_unref(loop);

return 0;
}
/*
- 如果要自定义定时器类型的事件源,只需让事件源的 prepare 与 check 接口在时间超过所设定的时间间隔时返回 TRUE, 否则返回 FALSE。
*/
c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#include <glib.h>

typedef struct _MySource MySource;

struct _MySource
{
GSource _source;
GIOChannel *channel;
GPollFD fd;
};
static gboolean prepare(GSource *source, gint *timeout)
{
*timeout = -1;
return FALSE;
}
static gboolean check(GSource *source)
{
MySource *mysource = (MySource *)source;

if(mysource->fd.revents != mysource->fd.events)
return FALSE;

return TRUE;
}
static gboolean dispatch(GSource *source, GSourceFunc callback, gpointer user_data)
{
MySource *mysource = (MySource *)source;

if(callback)
callback(mysource->channel);

return TRUE;
}
static void finalize(GSource *source)
{
MySource *mysource = (MySource *)source;

if(mysource->channel)
g_io_channel_unref(mysource->channel);
}
static gboolean watch(GIOChannel *channel)
{
gsize len = 0;
gchar *buffer = NULL;
g_io_channel_read_line(channel, &buffer, &len, NULL, NULL);
if(len > 0)
g_print("%d\n", len-1);
g_free(buffer);

return TRUE;
}
gboolean io_watch(GIOChannel *channel, GIOCondition condition, gpointer data)
{
gsize len = 0;
gchar *buffer = NULL;

g_io_channel_read_line(channel, &buffer, &len, NULL, NULL);
if(len > 0)
g_print("%d\n", len-1);
g_free(buffer);

return TRUE;
}
/**
* 3.1、自定义的 I/O 类型事件源: 接受用户在终端中输入的字符串,并统计输入的字符数量
*/
int main01(){
GMainLoop *loop = g_main_loop_new(NULL, FALSE);
GSourceFuncs funcs = {prepare, check, dispatch, finalize};
GSource *source = g_source_new(&funcs, sizeof(MySource));
MySource *mysource = (MySource *)source;

mysource->channel = g_io_channel_new_file("/machine/test.txt", "r", NULL);
if(mysource->channel == NULL){
g_printerr("fail to read file");
}
mysource->fd.fd = g_io_channel_unix_get_fd(mysource->channel);
mysource->fd.events = G_IO_IN;
g_source_add_poll(source, &mysource->fd);
g_source_set_callback(source, (GSourceFunc)watch, NULL, NULL);
g_source_set_priority(source, G_PRIORITY_DEFAULT_IDLE);
g_source_attach(source, NULL);
g_source_unref(source);

g_main_loop_run(loop);

g_main_context_unref(g_main_loop_get_context(loop));
g_main_loop_unref(loop);

return 0;
}

/**
* 3.2、预定义的 I/O 类型事件源: 可以将上例简化
*/
int main(){
GMainLoop *loop = g_main_loop_new(NULL, FALSE);

g_print("input string: \n");
//fd=1 代表终端输入
GIOChannel* channel = g_io_channel_unix_new(1);
if(channel)
{
g_io_add_watch(channel, G_IO_IN, io_watch, NULL);
g_io_channel_unref(channel);
}

g_main_loop_run(loop);

g_main_context_unref(g_main_loop_get_context(loop));
g_main_loop_unref(loop);

return 0;
}
文章作者: Machine
文章链接: https://machine4869.gitee.io/2018/10/08/15389750260474/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 哑舍
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论