glib 中 IO Channels 理解
G_IO_IN | There is data to read. |
G_IO_OUT | Data can be written (without blocking). |
G_IO_PRI | There is urgent data to read. |
G_IO_ERR | Error condition. |
G_IO_HUP | Hung up (the connection has been broken, usually for pipes and sockets). |
G_IO_NVAL | Invalid request. The file descriptor is not open. |
原文:
The GIOChannel data type aims to provide a portable method for using file
descriptors, pipes, and sockets, and integrating them into the
main event loop.
Currently full support is available on Unix platforms, though support for
Windows is only partially complete.
To create a new GIOChannel on Unix systems use g_io_channel_unix_new().
This works for plain file descriptors, pipes and sockets.
Once a GIOChannel has been created, it can be used in a generic manner
with the functions g_io_channel_read(), g_io_channel_write(),
g_io_channel_seek(), and g_io_channel_close().
To add a GIOChannel to the
main event loop
use g_io_add_watch() or g_io_add_watch_full(). Here you specify which events
you are interested in on the GIOChannel, and provide a function to be
called whenever these events occur.
GIOChannel instances are created with an initial reference count of 1.
g_io_channel_ref() and g_io_channel_unref() can be used to increment or
decrement the reference count respectively. When the reference count falls
to 0, the GIOChannel is freed. (Though it isn't closed automatically.)
Using g_io_add_watch() or g_io_add_watch_full() increments a channel's
reference count.
GTK+ contains the convenience function gtk_input_add_full()
which creates a GIOChannel from a file descriptor and adds it to the
main event loop.
The event source can later be removed with gtk_input_remove().
Similar functions can also be found in GDK.
GUI系统都是基于事件驱动的,其中必有一个事件循环过程来获取和处理事件。gtk也一样,gtk的事件循环过程是由glib提供的,而iochannel是glib中把IO事件集成到事件的一种手段。
iochannel可以把开发者指定的发生在 文件描述符、管道和socket之上的事件转换为glib的内部事件,从而可以在程序中用统一的方法来处理IO事件和用户交互。
iochannel支持的IO事件有 可读、可写、有紧急(urgent)数据到达、出错、挂断。由于iochannel是在 文件描述符、管道和socket 的基础上构建的,所以它提供的方法既包括这三者的共同点,也考虑到了这三者的不同之处。
那么怎样在基于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)外的其他事件都建立回调函数,加入事件循环。当有数据来时被动读取,发送数据时主动发送。
一个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读取函数读。
不过要注意的是必须要用循环读到出现没有数据可读以致返回错误时才能结束一次读操作。这是因为内核中有缓冲,要是一次读取没有把全部数据读完的话,本应该
在这次回调中读取的数据就要等到下一次才能读取了。串口的数据流量不大,不用这种处理办法可能也不会有问题,不过还是保险一点好。上面打开串口时使用非阻
塞方式就是为了这里可以把达到的数据完整读完。
5. 当停止读取的事件发生时,回调函数应该做如下工作:
* (1). 退出事件循环: g_source_remove (source_id);
* (2). 关闭 IO_Channel: g_io_channel_shutdown (io_channel, TRUE, NULL);
* (3). 释放 IO_Channel: g_io_channel_unref (io_channel);
关闭 iochannel 操作会把文件描述符关闭。
6. 其他:
打开和关闭 iochannel 的顺序不可变。
要是只想暂时不读文件妙算符,可以只退出事件循环。
不保证退出事件循环后到来的数据是否会在内核中缓存,不保证这段时间内的数据是否全部被缓存,所以当你退出事件循环再加入时要自己检查数据是否是你所需要的。(不保证是因为我没有做过试验)
iochannel不是什么新技术,它的基础是 select / poll,对比一下 g_io_add_watch 提供的事件选项和
select / poll 提供的就清楚了。关于 select / poll 请 man 2 select_tut或者 man 2 poll。
上面提到的方法对其他文件描述符都适用,我之前是把它用在socket上。