2008年3月8日 星期六

初探 Glib Programing - I/O 處理事件化『 g_io_channel』

Standard
C語言本身沒什麼困難,只要好好弄懂指標與記憶體位置之間的關係,大體上『應該』就算會 C 語言了。真正令人感到怯步的部份,反倒是藏身其中的各種演算法、資料結構和作業系統機制問題,要經過長年累月的 coding 才能盡數體會。

相信許多人都曾經歷過自己硬幹『串列』的美好時光,造這種基本的資料結構似乎也成為了寫 C 的樂趣之一。但是,你可曾想過哪一天寫 C Program 時,再也不需要自製串列結構、再也不用使用 select() /poll 去操作 I/O、再也不用怕處理 String 遺漏許多安全細節、再也… ,一切『原始』的結構和機制,如果都不用再自己去刻劃,這樣的輕鬆的一刻到來,會是多麼愉快呀!『Glib』的出現,使所有夢想得以成真,讓 C Program 的開發更容易、更簡潔有效率且更穩定安全,如果說 Glib 是 libc 的加強補完計畫也不為過!〔雖然少了很多 coding 的樂趣 :( 〕

Glib 的眾多包裝之中,『I/O 處理事件化』是一個很特別部份,我們可以等待 I/O 的回應而不需要再用到 select() + loop。只要用『 g_io_channel 』設定好 I/O watch 後,讓 Glib 自己觸發 R/W 的事件 handler 就可以了,就如同 gtk 中觸發各種事件一樣的簡單。

基本 I/O 事件的 handler 宣告和設計:

static gboolean
gio_in (GIOChannel *gio, GIOCondition condition, gpointer data)
{
GIOStatus ret;
GError *err = NULL;
gchar *msg;
gsize len;

/* 若 IO HUP,直接返回 */
if (condition & G_IO_HUP)
return FALSE;

/* 讀取 IO 的資料 */
ret = g_io_channel_read_line (gio, &msg, &len, NULL, &err);
if (ret == G_IO_STATUS_ERROR)
g_error ("Error reading: %s\n", err->message);

/* 顯示讀到的資料訊息 */
printf ("Read %u bytes: %s\n", len, msg);

g_free (msg);

/* 返回成功值 */
return TRUE;
}


此 handler 的目的,是當 I/O channel 有資料輸入更新時,g_io_channel 可以呼叫它並讀取資料。舉例來說,假設此 I/O 是個 socket,遠端如果有資料傳過來就會觸發 glib 事件,然後呼叫此 handler。

至於如何設定該 I/O channel 並將 handler 設定為觸發 callback,可參考這樣的寫法:

int main()
{
GMainLoop *loop;
/* IO channel 所需變數 */
GIOChannel *gio;
gsize len;
int sockfd;
struct sockaddr_un sa_un;

loop = g_main_loop_new(NULL, FALSE);

/* 建立 socket */
sockfd = socket(PF_UNIX, SOCK_STREAM, 0);
if (sockfd < 0) {
printf("Cannot create socket!");
return 1;
}

/* 初始化 socket */
bzero(&sa_un, sizeof(sa_un));

/* 配置 socket */
sa_un.sun_family = AF_UNIX;
snprintf(sa_un.sun_path, sizeof(sa_un.sun_path), "/tmp/gio_demo.socket");

/* 連接 socket */
if (connect(sockfd, (struct sockaddr *) &sa_un, sizeof (sa_un)) < 0) {
printf("Cannot connect!");
return 1;
}

/* 使用 socket 的 file description 建立新 g_io_channel */
gio = g_io_channel_unix_new(sockfd);

/* 設定編碼 */
g_io_channel_set_encoding(gio, NULL, NULL);

/*
監聽 IO 事件,設定 gio_in() 為觸發時的 handler
G_IO_IN:當有資料進入 IO
G_IO_HUP:當 IO 已經關閉切斷
*/
g_io_add_watch(gio, G_IO_IN | G_IO_HUP, gio_in, NULL);

g_main_loop_run(loop);
return 0;
}


此範例使用 socket 當做 I/O,除了 socket 之外,凡是只要有 file description 的都可以使用 g_io_channel 做監聽和操作。

過去我們一般都使用 select() 設定 timeout 去等待 socket 的 IO 訊息,再用 fget()、fread()等方式取得訊昔,其程式碼相當繁複也不易表達。使用 g_io_channel 後,一切便豁然開朗,能減少許多 coding 和 debug 的時間。

當然, g_io_channel 的能力不只是這樣,你也可以透過它傳送 messages 輸出到 I/O 之中:

if (g_io_channel_write_chars(gio, "Test Message", -1, &len, NULL)==G_IO_STATUS_ERROR)
g_error("Error writing!");


更多 Glib IO Channel 的資料,可以在官方網站找到:
http://developer.gimp.org/api/2.0/glib/glib-IO-Channels.html

雖然 GTK+ 的種種實在是令人不敢恭維, Glib 卻出乎意料的被設計得相當好,其中各種包裝過後的 API 都非常實用,在效能上也令人無可挑惕,很難想像他們是同門師兄弟。:P

Glib 包括的 API 範圍非常廣,一時間也無法一一列舉,有興趣的人可多多瀏覽『 GLib Reference Manual