glib 中 IO Channels 理解

來源:互聯網
上載者:User
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上。 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.