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上。

 

 原文地址 http://www.hereblog.org/leeray/entry/20070611

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章