日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

libuv 初窥--转

發(fā)布時間:2023/12/13 综合教程 34 生活家
生活随笔 收集整理的這篇文章主要介紹了 libuv 初窥--转 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

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

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

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

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

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

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

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

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

為什么我覺得這樣不好?

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

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

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

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

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

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

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

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


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

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

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

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

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

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
", 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)?code>write_req_t被定義成:

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

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

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

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

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

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

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


我著一些疑惑閱讀了不少 libuv 里的實(shí)現(xiàn)代碼,尤其是 uv.h 的細(xì)節(jié)。我發(fā)現(xiàn)這樣一個結(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
};

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

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

#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;
};

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


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

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

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


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

總結(jié)

以上是生活随笔為你收集整理的libuv 初窥--转的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。