聲明:本文為原創博文,轉載請注明出處。
句柄(handle)代表一種對持有資源的索引,句柄的叫法在window上較多,在unix/linux等系統上大多稱之為描述符,為了抽象不同平台的差異,libuv使用統一的結構封裝了不同平台的實現,接下來就看看這個抽象的過程。由於句柄的實現和系統平台有很大關系,本文只針對unix平台作源碼分析。
一、抽象的開始----封裝、繼承、多態
libuv是用純c語言寫的(排除里面有幾處內聯匯編的用法),怎么還有繼承呢?繼承不都是c++、java、python等這些更高級語言才有的特性嗎?不錯,類似c++這些高級語言,從語言層面就支持了面向對象的三大特性:繼承、封裝與多態,c語言作為一門歷史悠久、簡潔高效的語言,雖然沒有從語言層次提供復雜的對象管理機制,但是通過巧妙的設計也可以寫出面向對象的思想,這在linux內核中體現的淋漓盡致,比如在內核的驅動部分,我們通常在編寫一個字符設備驅動程序時,一定會操作的一個結構體:file_operations(定義在下方),就在一個struct中實現了方法和屬性的封裝,相應的還有其他結構定義充分的利用了“組合”來實現面向對象的“繼承”特性。
1 struct file_operations { 2 struct module *owner; 3 loff_t(*llseek) (struct file *, loff_t, int); 4 ssize_t(*read) (struct file *, char __user *, size_t, loff_t *); 5 ssize_t(*aio_read) (struct kiocb *, char __user *, size_t, loff_t); 6 ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *); 7 ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t, 8 loff_t); 9 int (*readdir) (struct file *, void *, filldir_t); 10 unsigned int (*poll) (struct file *, struct poll_table_struct *); 11 int (*ioctl) (struct inode *, struct file *, unsigned int, 12 unsigned long); 13 .............. 14 }
總結一下,經過上面的論述,用c語言實現面向對象編程,無外乎兩種方法:在struct中通過“組合”實現面向對象的封裝特性,通過“函數指針”來實現對象方法的封裝,通過工廠方法也可以實現“偽多態”的效果。
有了上面的理論基礎,來看看libuv是怎么運用這些特性的吧。
二、抽象基類
libuv中,所有的handle都會有一個共同的抽象基類(這里所說的抽象基類,只是一種稱呼,不要和c++與java中的概念混淆),他就是uv_handle_s,下面來看一下它的定義。
1 /* 所有句柄的抽象基類. */ 2 struct uv_handle_s { 3 UV_HANDLE_FIELDS 4 };
其中,將UV_HANDLE_FIELDS宏展開之后,再次列出:
1 struct uv_handle_s { 2 /* public */ \ 3 void* data; \ //句柄攜帶的數據 4 /* read-only */ \ 5 uv_loop_t* loop; \ //句柄綁定的事件循環 6 uv_handle_type type; \ //句柄類型 7 /* private */ \ 8 uv_close_cb close_cb; \ //句柄close時的回調 9 void* handle_queue[2]; \ //句柄隊列節點 10 union { \ 11 int fd; \ //綁定的真實資源索引 12 void* reserved[4]; \ 13 } u; \ 14 UV_HANDLE_PRIVATE_FIELDS \ 15 };
在正式介紹其成員之前,先將宏UV_HANDLE_PRIVATE_FIELDS也展開(它是一個和平台相關的宏):
struct uv_handle_s { 2 /* public */ \ 3 void* data; \ //句柄攜帶的數據 4 /* read-only */ \ 5 uv_loop_t* loop; \ //句柄綁定的事件循環 6 uv_handle_type type; \ //句柄類型 7 /* private */ \ 8 uv_close_cb close_cb; \ //句柄close時的回調 9 void* handle_queue[2]; \ //句柄隊列節點 10 union { \ 11 int fd; \ //綁定的真實資源索引 12 void* reserved[4]; \ 13 } u; \ 14 \ 15 uv_handle_t* next_closing; \ //下一個要被關閉的句柄 16 unsigned int flags; \ //句柄標識 17 };
以上就是unix平台下的uv_handle_s結構定義,從其成員定義可以看出所有句柄的共性是什么。首先,void *類型的data成員可以用來傳遞任何類型的數據,其上面的注釋“public”表示該數據可被用戶層訪問,而標有“provite”字樣的屬性(成員),則表示不是暴露給用戶使用的,它們只會在libuv內部使用。接下來,有一個uv_loop_t *的指針,從字面上可以看出,這是一個事件循環的指針,在上一篇論述線程池時,已經提及過Reactor線程模型,其中事件循環(loop)就是一個Reactor實例,主要提供了事件的注冊、注銷、dispatch事件的功能,如果你熟悉libevent,它就類似於eventbase,如果你熟悉java中的netty,它就類似於eventloop,好了,這里的loop指針表示這個句柄(handle)是被綁定在哪個事件循環上的;uv_handle_type表示這個句柄的類型,它的取值可以有:UV_TCP、UV_NAMED_PIPE、UV_TTY、UV_UDP、UV_POLL等等;close_cb表示該句柄在關閉時調用的回調函數;handle_queue,作為一個QUEUE節點,會掛載在綁定的loop循環中的handle_queue隊列上;u是一個聯合體,表示句柄綁定的真實的資源索引(真實的句柄或者描述符),但是我們知道,libuv抽象出來的句柄並不一定都有真實的物理資源對應,比如定時器句柄就不沒有一個對應的描述符,因此此時可以使用reserved來占位;next_closing用來將要被關閉的句柄串接成單向鏈表,該鏈表會掛載在綁定的loop上的closing_handles指針上。flags,表示該句柄的狀態,可以為UV_CLOSING、UV_CLOSED、UV_STREAM_READING、UV_STREAM_SHUTTING、UV_STREAM_SHUT、UV_STREAM_READABLE、UV_STREAM_WRITABLE、UV_STREAM_BLOCKING、UV_STREAM_READ_PARTIAL、UV_STREAM_READ_EOF、UV_TCP_NODELAY、UV_TCP_KEEPALIVE、UV_TCP_SINGLE_ACCEPT、UV_HANDLE_IPV6、UV_UDP_PROCESSING。
至此,所有句柄的抽象基類基本上說清楚了,所以其他類型的句柄都是這個基類的直接或者間接子類,那么libuv都定義了哪些句柄類型呢?在uv.h中,可以看到如下定義:
1 /* Handle types. */ 2 typedef struct uv_loop_s uv_loop_t; 3 typedef struct uv_handle_s uv_handle_t; 4 typedef struct uv_stream_s uv_stream_t; 5 typedef struct uv_tcp_s uv_tcp_t; 6 typedef struct uv_udp_s uv_udp_t; 7 typedef struct uv_pipe_s uv_pipe_t; 8 typedef struct uv_tty_s uv_tty_t; 9 typedef struct uv_poll_s uv_poll_t; 10 typedef struct uv_timer_s uv_timer_t; 11 typedef struct uv_prepare_s uv_prepare_t; 12 typedef struct uv_check_s uv_check_t; 13 typedef struct uv_idle_s uv_idle_t; 14 typedef struct uv_async_s uv_async_t; 15 typedef struct uv_process_s uv_process_t; 16 typedef struct uv_fs_event_s uv_fs_event_t; 17 typedef struct uv_fs_poll_s uv_fs_poll_t; 18 typedef struct uv_signal_s uv_signal_t;
句柄的類型大概上分為兩種:普通句柄和流句柄,普通的句柄就類似於信號、文件、定時器等,流式句柄比如代表一個tcp連接、pipe連接以及控制台連接等。為了表達清楚他們之間的關系,我現在以圖示的形式畫了一張偽UML圖。