使用gtk+的iochannel进行事件驱动IO操作

来源:互联网 发布:javascript倒计时代码 编辑:程序博客网 时间:2024/06/07 00:27
现代的GUI系统都是基于事件驱动的,其中必有一个事件循环过程来获取和处理事件。gtk也一样,gtk的事件循环过程是由glib提供的,而iochannel是glib中把IO事件集成到事件的一种手段。
iochannel可以把开发者指定的发生在 文件描述符、管道和socket之上的事件转换为glib的内部事件,从而可以在程序中用统一的方法来处理IO事件和用户交互。
iochannel支持的IO事件有 可读、可写、有紧急(urgent)数据到达、出错、挂断。由于iochannel是在 文件描述符、管道和socket 的基础上构建的,所以它提供的方法既包括这三者的共同点,也考虑到了这三者的不同之处。
另外,iochannel还提供了读写 文件描述符、管道和socket的方法。
可以把iochannel看作是glib对这三者的一个抽象。

上面简单介绍了iochannel,(水平不高,说来说去也就几句话:-p)
那么怎样在基于gtk或者glib的程序中加入iochannel呢?
步骤一般为:
1. 创建一个文件描述符。
可以通过打开一个普通文件、创建管道或者打开socket来实现,结果都是得到一个文件描述符。
2. 创建iochannel,设置数据编码。
iochannel通过如下函数创建:
GIOChannel* g_io_channel_unix_new (int fd);
例如:
io_channel = g_io_channel_unix_new (fd);
创建之后可以设置数据编码。对于数据编码,我也不太明,一般我把编码设置为NULL,这样在使用iochannel提供的读写函数时就不会对数据进行任何处理。编码设置函数如下:
GIOStatus g_io_channel_set_encoding (GIOChannel *channel, const gchar *encoding, GError **error);
例如,把编码设置为NULL:
g_io_channel_set_encoding (io_channel, NULL, &err);
3. 把你所需要处理的发生文件描述符上的事件加到事件循环中。
通过如下函数把iochannel的指定事件加入到事件循环中:
guint g_io_add_watch (GIOChannel *channel, GIOCondition condition, GIOFunc func, gpointer user_data);
其中,GIOCondition包括G_IO_IN, G_IO_OUT, G_IO_PRI,G_IO_ERR, G_IO_HUP, G_IO_NVAL。可以通过对它们的或运算来同时指定多个事件,当然回调函数应该判断是哪个的事件引起回调。
iochannel的回调函数原型为:
gboolean (*GIOFunc) (GIOChannel *source, GIOCondition condition, gpointer data);
第二个参数便是引起回调的事件的值。

上面第1步的作用就好比建立一个按钮,而第2,3步的作用就好比用g_signal_connect()把一个事件加入事件循环。做好这3个工作,后面还有两个工作:
1. 编写回调函数。
在回调函数中,你可以采用iochannel提供的读写函数,也可以用g_io_channel_unix_get_fd()获得的文件描述符来进行平常的IO操作。
2. 退出事件循环,关闭iochannel。
在程序结束,或者文件描述符已经没用的时候,应该关闭iochannel。在关闭前必须先退出事件循环,用g_source_remove(source_id)完成退出动作。source_id是g_io_add_watch()的返回值。
跟着便可以关闭iochannel了,用g_io_channel_shutdown (io_channel, TRUE, NULL)来完成关闭动作。
关闭后iochannel所占内存还没有释放,用g_io_channel_unref (io_channel)来减少iochannel的参考计数器,使其为0,glib会自动释放该iochannel。

根据你的应用,我的建议是在连接到服务器之后利用socket的文件描述符建立iochannel,并且为除 数据可写(G_IO_OUT)外的其他事件都建立回调函数,加入事件循环。当有数据来时被动读取,发送数据时主动发送。

我写gtk程序,一般是先用glade画出界面,生成代码,然后提取代码,对代码进行修改,让代码符合我的要求和软件设计的要求,然后编写回调函数。
要想不写图形界面方面的代码那是很难的,我的所谓的集成开发环境就是glade+emacs,然后glib, gdk, gtk的参考手册是我最常查阅的,函数太多,记不住,pango的参考手册也用过,但是比较少,atk的好象没用到过。

上面有关iochannel的其实也就之前我的帖子的另一个表述,没什么新的东西。
对进入自己不熟悉的领域,我的意见是多看文档、参考一下简单的代码,然后自己多实践。关键是弄清楚其来龙去脉,计算机的东西不会无中生有,理顺了就明白了。





一个iochannel只能绑定一个socket。
服务器那端,用listen之后的fd (设为listen_fd) 建立一个iochannel。
当有连接来时,iochannel表现为listen_fd可读。在回调函数中accept,得到一个连接fd(设为connect_fd)。然后为每一个connect_fd建立一个iochannel。
简单的说就是listen_fd的回调函数是accept用的;connect_fd的回调函数是读写用的,每个connect_fd的回调函数都一样。
connect_fd关闭后关闭该iochannel。
关于关闭iochannel,可能要用g_idle_add()添加一个垃圾回收函数。
因为不能在connect_fd的回调函数中shutdown该iochannel。
客户端要注意connect的超时时间比较长,可能需要用到线程来解决这个问题。




想实现“在按键事件开始后不断的读串口,直到关断串口的按键事件启动”的话,用while是不可行的,单单加入非阻塞也不行,因为在while循环中你的程序将不会响应按键事件。
多线程是可以解决问题的,不过尽量不要使用。

用iochannel是最合适的。方法大致如下:

1. 以非阻塞方式打开串口,非阻塞是必须的,下面会提到原因。
fd = open("/dev/ttyS0",O_RDWR|O_NONBLOCK,0644);

2. 建立iochannel。
io_channel = g_io_channel_unix_new (fd);
g_io_channel_set_encoding (io_channel, NULL, &err); /* 应该可选 */

3. 把文件描述符可读的事件加入到程序的事件循环中:
source_id = g_io_add_watch (io_channel, G_IO_IN, read_ttyS, NULL);

4. 当这些做好后就可以用 read_ttyS() 来读取串口数据了。
你可以用 read() 来直接读串口数据,也可以用 glib 提供的iochannel读取函数读。
不过要注意的是必须要用循环读到出现没有数据可读以致返回错误时才能结束一次读操作。这是因为内核中有缓冲,要是一次读取没有把全部数据读完的话,本应该 在这次回调中读取的数据就要等到下一次才能读取了。串口的数据流量不大,不用这种处理办法可能也不会有问题,不过还是保险一点好。上面打开串口时使用非阻 塞方式就是为了这里可以把达到的数据完整读完。
原创粉丝点击