日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

IT牛人博客聚合

 quasiceo 2014-01-13

libuv 初窺

云風(fēng) 發(fā)表于 2012年01月20日 23:18 | Hits: 1925

過(guò)年了,人都走光了,結(jié)果一個(gè)人活也干不了。所以我便想找點(diǎn)東西玩玩。

今天想試一下 libev 寫點(diǎn)代碼。原本在我那臺(tái) ubuntu 機(jī)器上一點(diǎn)問(wèn)題都沒(méi)有,可在 windows 機(jī)上用 mingw 編譯出來(lái)的庫(kù)一個(gè) backend 都沒(méi)有,基本不可用。然后網(wǎng)上就有同學(xué)推薦我試一下 libuv 。

libuv 是 node.js 作者做的一個(gè)封裝庫(kù),在 unix 環(huán)境整合的 libev ,而在 windows 下用 IOCP 另實(shí)現(xiàn)了一套??雌饋?lái)挺滿足我的玩兒的需求的。所以就試了一下。

這東西沒(méi)有文檔,暫時(shí)沒(méi)看出來(lái)作者有寫文檔的打算,恐怕他是自己用為主。我 google 了一下,就是 github 上有源代碼,.h 文件里有還算比較詳細(xì)的注釋。當(dāng)然最主要是有些 test 程序,可以大概瀏覽其設(shè)計(jì)思路。

編譯倒挺順利,照著 test 寫點(diǎn)小東西也不復(fù)雜。所以我也就逐步開始了解這個(gè)東東了。

老實(shí)說(shuō),對(duì)于一個(gè)別人寫的庫(kù),我愛(ài)用不愛(ài)用主要是考察其 API 設(shè)計(jì)。也就是該怎么用,設(shè)計(jì)的好不好,有沒(méi)有冗余設(shè)計(jì)。文檔什么的其實(shí)不太所謂,反正有代碼可以看嘛。

libuv 大體上我還算滿意,用 C 實(shí)現(xiàn)可以加很多分。不過(guò)有些小細(xì)節(jié)我覺(jué)得還是有點(diǎn)小遺憾的。(這個(gè)遺憾是指,如果我自己來(lái)設(shè)計(jì),絕對(duì)不會(huì)像這個(gè)樣子)

最關(guān)鍵就是接口對(duì) C 結(jié)構(gòu)體布局的依賴性。這點(diǎn)我曾經(jīng)因?yàn)?hiredis windows 版的緣故吐槽過(guò)一次了。以我自己的經(jīng)驗(yàn),似乎大多數(shù) Windows 出身的程序員都有一點(diǎn)這種壞習(xí)慣。好吧,我也不知道怎么把這點(diǎn)和 windows 聯(lián)系起來(lái)的,純粹感覺(jué)而已。因?yàn)槲易约阂郧白鲈O(shè)計(jì)也有這個(gè)習(xí)慣。

為什么我覺(jué)得這樣不好?

因?yàn)槲矣X(jué)得一個(gè)庫(kù),若想被人當(dāng)成黑盒子去使用,以后也作用黑盒子來(lái)維護(hù),甚至可以用別的盒子去替代它。關(guān)鍵的一點(diǎn)就是接口簡(jiǎn)單。這個(gè)簡(jiǎn)單包括了使用最少的概念、需要最少的知識(shí)去理解它。

文檔通常是對(duì)接口無(wú)法自描述所需知識(shí)的一種補(bǔ)充。對(duì)一些例外的說(shuō)明。而這些當(dāng)然是越少越好。

我傾向于不在對(duì)外接口(對(duì)于 C 庫(kù)來(lái)說(shuō),就是 .h 文件)中定義所用數(shù)據(jù)結(jié)構(gòu)的具體布局。通常只需要一個(gè)名字即可。這個(gè)名字是用來(lái)做強(qiáng)類型約束的。

過(guò)多的結(jié)構(gòu)定義導(dǎo)致了過(guò)多的概念,增加了接口復(fù)雜度。

接口的最小知識(shí)表達(dá)就是用一致的 C 函數(shù)調(diào)用約定。有明確的輸入?yún)?shù)、輸出參數(shù)。對(duì)于接口函數(shù),應(yīng)該是無(wú)全局相關(guān)狀態(tài)的。這不僅僅是為了線程安全,而是可以保證沒(méi)有隱式約定(額外的知識(shí))。

如果某些行為需要用戶設(shè)置或讀取某個(gè)結(jié)構(gòu)體的一個(gè)特定域,我覺(jué)得就是在 C 函數(shù)調(diào)用這一方式外,增加了一種改變模塊行為的接口形式?;蛟S這樣做的成本比 C 函數(shù)調(diào)用要來(lái)的低,但我以為得不償失。

尤其是、你的模塊如果相當(dāng)依賴這種形式:直接對(duì)結(jié)構(gòu)體的特定域賦值的形式來(lái)交換信息。這種依賴可能來(lái)至于你對(duì)性能的追求。那我覺(jué)得一般都是整個(gè)模塊的需求定義出了什么問(wèn)題。一個(gè)獨(dú)立模塊需要解決的問(wèn)題,通常對(duì)外界的信息交換應(yīng)該是低頻的,它應(yīng)該是可以獨(dú)立工作解決更復(fù)雜的問(wèn)題的。而不應(yīng)該是不斷的要求外部告知它新的狀態(tài)變換。

ps. 對(duì)于接口中的結(jié)構(gòu)體定義問(wèn)題。有另一種情況需要區(qū)分開。就是有大量的輸入?yún)?shù)或輸出參數(shù)需要一次性交換時(shí),可以考慮定義一個(gè)結(jié)構(gòu)體來(lái)做。這樣比在 C 函數(shù)調(diào)用前壓一大堆的數(shù)據(jù)去堆棧里要干凈的多。

寫了這么多,我是想說(shuō)說(shuō)我初步閱讀 libuv 代碼的感受。我碰到的第一個(gè)問(wèn)題就是:libuv 用了大量 callback 機(jī)制來(lái)完成異步 IO 的問(wèn)題。而這些 callback 函數(shù)通常都帶有一個(gè)參數(shù)uv_stream_t或uv_req_t等。這個(gè)數(shù)據(jù)表示這次 callback 綁定的數(shù)據(jù) 。

我們知道, C 語(yǔ)言是沒(méi)有原生 closure 支持的。若有的話,closure 應(yīng)是 callback 機(jī)制最價(jià)解決方案。而 C 語(yǔ)言模擬 closure 的方法是用一個(gè) C Function 并攜帶一個(gè) void * ud 。此 ud 即原本應(yīng)該在 closure 中綁定的數(shù)據(jù)塊。

這里,libuv 用的uv_stream_t大致上等同于這個(gè) ud 。

問(wèn)題出來(lái)了。用戶在用這類異步 IO 庫(kù)的時(shí)候,每次 IO 事件都需要綁定的行為需要的數(shù)據(jù)不僅僅是一個(gè) stream 。還需要一些圍繞這個(gè) stream 做的動(dòng)作所需要的一些其它數(shù)據(jù)。

我在閱讀test/echo-server.c時(shí)看到這么一段:

static void after_write(uv_write_t* req, int status) {
  write_req_t* wr;

  if (status) {
    uv_err_t err = uv_last_error(loop);
    fprintf(stderr, "uv_write error: %s\n", uv_strerror(err));
    ASSERT(0);
  }

  wr = (write_req_t*) req;

  /* Free the read/write buffer and the request */
  free(wr->buf.base);
  free(wr);
}

這里用了一次強(qiáng)制轉(zhuǎn)換,把uv_write_t轉(zhuǎn)換為write_req_t。為什么可以這樣干,是因?yàn)閣rite_req_t被定義成:

typedef struct {
  uv_write_t req;
  uv_buf_t buf;
} write_req_t;

這里根據(jù) C 結(jié)構(gòu)布局,req 是第一個(gè)域,所以排在最前面。

這樣做有點(diǎn)晦澀,我只能說(shuō)感覺(jué)不太好。因?yàn)槿绻s定了uv_write接口傳遞的是一個(gè)uv_write_t類型的數(shù)據(jù),這就明顯是利用 C 語(yǔ)言特性來(lái)夾帶私貨了。

如果這是作者推薦的慣用法的話,我則這樣理解:

libuv 其實(shí)在 API 上有個(gè)隱含約定。即回調(diào)函數(shù)的參數(shù)指向的地址偏移量為某個(gè)數(shù)值以后的數(shù)據(jù)是用戶數(shù)據(jù)。這個(gè)數(shù)值為類型的尺寸。這類似 c++ 的繼承。數(shù)據(jù)類型尺寸數(shù)值是編譯時(shí)通過(guò)編譯器來(lái)約定的。

而且,單就現(xiàn)在的用法,我認(rèn)為更嚴(yán)謹(jǐn)?shù)淖龇☉?yīng)該是類似 socket API ,顯式的把傳遞的結(jié)構(gòu)尺寸在函數(shù)接口表達(dá)出來(lái)(參考 socket connect 的接口定義中的第三個(gè)參數(shù) addrlen)。 這樣對(duì)庫(kù)的接口穩(wěn)定有好處。庫(kù)可以知道用戶有可能擴(kuò)展數(shù)據(jù),長(zhǎng)度信息提示了庫(kù),傳入數(shù)據(jù)體的真實(shí)大小。

btw, C++ 在用繼承來(lái)完成類似設(shè)計(jì)時(shí),則依靠了語(yǔ)言對(duì) cast 的約定。C++ 語(yǔ)言的知識(shí)概念太多,很難完成簡(jiǎn)潔的模塊接口約定。在我看來(lái),這直接導(dǎo)致了 C++ 很難設(shè)計(jì)通用庫(kù),而只能設(shè)計(jì)專有框架。

我著一些疑惑閱讀了不少 libuv 里的實(shí)現(xiàn)代碼,尤其是 uv.h 的細(xì)節(jié)。我發(fā)現(xiàn)這樣一個(gè)結(jié)構(gòu)定義。

#define UV_HANDLE_FIELDS   /* read-only */   uv_loop_t* loop;   uv_handle_type type;   /* public */   uv_close_cb close_cb;   void* data;   /* private */   UV_HANDLE_PRIVATE_FIELDS

/* The abstract base class of all handles.  */
struct uv_handle_s {
  UV_HANDLE_FIELDS
};

注意這里有一個(gè) data 域。從我的經(jīng)驗(yàn)判斷,這個(gè)域應(yīng)該就是用來(lái)在一個(gè) handle 上夾帶用戶數(shù)據(jù)的。由于沒(méi)有文檔確認(rèn),我只能從有限的代碼閱讀中來(lái)確認(rèn)我的判斷。我很奇怪沒(méi)有定義一個(gè)明確的 api 出來(lái)綁定用戶數(shù)據(jù)。因?yàn)樵趲?kù)的實(shí)現(xiàn)代碼中也確實(shí)庫(kù)自己用到過(guò)這個(gè)域,所以估計(jì)也不是庫(kù)的使用者可以自由使用的。

當(dāng)然對(duì)應(yīng)的還有幾處類似設(shè)計(jì):

#define UV_REQ_FIELDS   /* read-only */   uv_req_type type;   /* public */   void* data;   /* private */   UV_REQ_PRIVATE_FIELDS

/* Abstract base class of all requests. */
struct uv_req_s {
  UV_REQ_FIELDS
};

還有

struct uv_loop_s {
  UV_LOOP_PRIVATE_FIELDS
  /* list used for ares task handles */
  uv_ares_task_t* uv_ares_handles_;
  /* Various thing for libeio. */
  uv_async_t uv_eio_want_poll_notifier;
  uv_async_t uv_eio_done_poll_notifier;
  uv_idle_t uv_eio_poller;
  /* Diagnostic counters */
  uv_counters_t counters;
  /* The last error */
  uv_err_t last_err;
  /* User data - use this for whatever. */
  void* data;
};

這個(gè)struct uv_loop_s的 data 域倒是明確的注釋可以隨便使用了。

話說(shuō)回來(lái),這個(gè)綁定用戶數(shù)據(jù)的需求我在早年閱讀 Windows 的 MFC 實(shí)現(xiàn)時(shí)倒是見過(guò)另外一種解決方案。

Windows 的窗體有一個(gè) SetWindowLong 的 API 可以讓用戶去設(shè)置一個(gè)用戶數(shù)據(jù)。這樣可以方便用戶在用 C++ 封裝的時(shí)候把一個(gè) C++ 對(duì)象指針綁定在窗體 Handle 上。這樣在窗口消息回調(diào)函數(shù)中就可以取回這個(gè)對(duì)象指針。

MFC 封裝這些系統(tǒng) API 時(shí),可能是為了更通用,沒(méi)有占用這個(gè)內(nèi)置域,而是自己建立了一個(gè)全局的映射表。每次窗體消息回調(diào)時(shí),查表來(lái)找到對(duì)應(yīng)的窗體對(duì)象。這種非侵入式的方案,也湊合用吧。就是對(duì)于用 C/C++ 編寫代碼的追求性能的同學(xué)來(lái)說(shuō),或許有些小小不爽。

這就是我初步閱讀 libuv 代碼的一些簡(jiǎn)單看法。當(dāng)然,我覺(jué)得 libuv 是個(gè)很不錯(cuò)的東西,不然我也不會(huì)饒有興致的玩了一晚上。只是由于在這塊投入時(shí)間精力不多,錯(cuò)誤難免。有行家看到,一笑了之吧。

原文鏈接: http://blog./2012/01/libuv.html

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多