uvw 源碼閱讀
這一部分開始我們再接觸一個新庫叫做 uvw
,那這個庫的話只看介紹就比較令人激動了
uvw
started as a header-only, event based, tiny and easy to use wrapper forlibuv
written in modern C++.Now it's finally available also as a compilable static library.
uvw
最初是一個header only
的庫,通過現(xiàn)代 C++ 封裝了libuv
的功能,基于事件驅(qū)動(event based),精巧且易于使用 現(xiàn)在同時支持編譯為static
靜態(tài)庫使用
libuv
的話是屬于名氣比較高的一個基于事件驅(qū)動的異步 I/O 庫,和 libevent
, libev
互為替代品,功能的話主要就是負責維護一個隊列,那在這個隊列上你可以注冊自己感興趣的事件同時添加一個回調(diào)函數(shù)(比如添加一個定時任務(wù),回調(diào)設(shè)置為一個需要定期執(zhí)行的函數(shù)),那么當事件發(fā)生時(定時任務(wù)到期觸發(fā))就會調(diào)用之前注冊的回調(diào)函數(shù)。這個概念應(yīng)該比較好理解,尤其是之前對 epoll
有了解的話。
首先還是看一下基本的用例:
這段代碼就有很大的信息量了,一起來讀一下。首先看 main
函數(shù),通過 uvw::Loop::getDefault
創(chuàng)建了一個事件循環(huán),接著調(diào)用 listen
和 conn
并傳入 loop
,最后執(zhí)行 loop->run()
。
暫時跳過創(chuàng)建事件循環(huán)的方法,接著看下 listen
和 conn
中做了什么,先來看 listen
:
首先創(chuàng)建了一個 uvw::TCPHandle
,那從類型名字不難猜出是用來處理 tcp 事件的,同時說明 libuv
所處理的事件類型應(yīng)該是有限制的,那不知道可不可以通過繼承某個類去做自定義的 Handle , 接著是 tcp->once<uvw::ListenEvent>();
應(yīng)該是注冊了一個只執(zhí)行一次的事件并設(shè)置回調(diào)函數(shù)為一個 lambda
函數(shù),也就是在接收到客戶端的連接請求后注冊兩個事件 CloseEvent
和 EndEvent
,接著 accept
客戶端請求,并嘗試讀取客戶端 socket。
事件注冊完成后通過 bind
設(shè)置 IP 和端口并正式開始 listen
,當然這里不太確定機制,也有可能是等到 loop->run
再開始做?
不管怎樣我們接著看 conn
的實現(xiàn):
這里同樣是拿了 uvw::TCPHandle
,這樣看的話可能 tcp
是單例模式所以拿到的都是同一個?接著也是注冊了兩個事件,一個是用于錯誤處理,而另外一個是在連接建立成功后直接寫一個 "bc"
然后關(guān)閉 socket 的一次性事件。
接著調(diào)用 tcp->connect
去嘗試連接 4242 端口,這個端口就是我們剛剛監(jiān)聽的端口,那上面理解有一些偏差,兩個函數(shù)中的 tcp
并不是同一個,這里 listen
實際上扮演了 server
的角色去監(jiān)聽端口并嘗試讀取內(nèi)容, conn
則扮演了 client
的角色,主動連接并發(fā)送內(nèi)容。
現(xiàn)在還剩余一個疑問就是 listen
、 connect
和 loop->run()
的執(zhí)行順序,這里先行保留。
接著來看看 uvw::Loop::getDefault()
的實現(xiàn):
這里返回了 std::shared_ptr<Loop>
,那也就不難解釋之前傳參時候為什么都使用 *loop
了,接著這里使用 static std::weak_ptr<Loop>
做了一個單例的實現(xiàn),如果 ref
沒有指向的對象或者對象已經(jīng)銷毀那么通過 uv_default_loop
生成一個默認事件循環(huán),否則通過 ref.lock()
獲取一個 std::shared_ptr<Loop>
返回。
這里的代碼存在一點問題,一般我們做一個單例的實現(xiàn)的時候都需要用一些手段確保它是線程安全的,那這里的話就可能出現(xiàn)兩個線程同時調(diào)用時因為還沒有實例化因此兩個線程一起進入 if(ref.expired())
分支的情況。如果要確保線程安全的話代碼類似這樣:
這里我們使用一個鎖保證了任務(wù)執(zhí)行的先后順序,當?shù)谝粋€執(zhí)行函數(shù)的線程實例化 Loop
完成并返回后第二個線程才能嘗試獲取。
接著這里 uv_default_loop
應(yīng)該是 libuv
的函數(shù),返回一個 uv_loop_t*
類型的 def
, 接著把這個指針放到 unique_ptr
中去管理卻給了一個空的自定義 Deleter,這里應(yīng)該是要交給其他地方去釋放。接著把指針移交給 shared_ptr<Loop>
管理,我們來簡單看一下 Loop
的構(gòu)造函數(shù):
這里就是接受一個 std::unique_ptr<uv_loop_t, Deleter>
類型的指針也就是我們剛剛生成并包裝的 def
,直接給到Loop::loop
沒有做額外動作。
順便看一下析構(gòu)函數(shù):
那這里可以看到也確實是交還給 libuv
去關(guān)閉了。
那么本節(jié)內(nèi)容就到這里,下一節(jié)我們一起看一下 loop->run()
的實現(xiàn)。
下次再會!