UVW代碼漫談(一)

来源:http://www.cnblogs.com/yxfangcs/archive/2017/09/17/7527658.html
-Advertisement-
Play Games

博客園是個非常好的學習知識的地方,相信有很多人跟我一樣,園齡3年,從博客園不知道拷了多少代碼,看了多少博客,自己卻一篇博客都沒寫過。真是罪過。 這次準備寫幾篇關於這個項目源碼的閱讀和理解的文章,大家一起相互學習學習,我可能不會單單就寫源碼一類的東西,還會做很多擴展,比如新的c++的語法,其他的一些工 ...


  博客園是個非常好的學習知識的地方,相信有很多人跟我一樣,園齡3年,從博客園不知道拷了多少代碼,看了多少博客,自己卻一篇博客都沒寫過。真是罪過。

  這次準備寫幾篇關於這個項目源碼的閱讀和理解的文章,大家一起相互學習學習,我可能不會單單就寫源碼一類的東西,還會做很多擴展,比如新的c++的語法,其他的一些工具等等,各位看官不要嫌煩。咱們又不是什麼大牛,遇到文中有歧義,不對之處,請在評論區留言,咱們一起討論,再做改進,避免誤人子弟。

  廢話不多說,現在開始。

 

  最近在看一個項目 uvw 的源碼,可能很多人不知道這個東西。搞過一些網路編程的人應該知道 libuv,uvw 是我在github上找到的一個用c++封裝 libuv 的項目,源代碼作者也在持續更新中。

  簡單的介紹一下:

  libuv:是一個跨平臺的網路庫,具體可以參考博客:http://www.cnblogs.com/haippy/archive/2013/03/17/2963995.html 以及博主的libuv系列的文章。

  uvw:用 c++14 對libuv的封裝,作者應該是個外國人,代碼質量應該沒的說,尤其是註釋,非常詳盡,值得我等菜鳥學習。

     github地址:https://github.com/skypjack/uvw

  

  首先得把代碼搞出來,

  1、直接下載,地址:https://codeload.github.com/skypjack/uvw/zip/master

  2、git clone https://github.com/skypjack/uvw.git

 

  註:文件路徑寫法: ./src/uvw.hpp  當前目錄為代碼根目錄。

  代碼基本上在src文件中,切到src,對,你沒有看錯,全是hpp文件,所以如果你要用這個庫,直接把src拷到你工程里就行了。用起來可以說是非常方便,但是你的工程不要忘了包含libuv的頭文件和鏈接libuv庫。另外uvw對libuv的版本也有限制,可以在github的tag中查看libuv對應的版本,如果你是用方法2,可以用命令”git tag -l“查看。(關於git這個東西,如果有看官還不瞭解的,可以參考菜鳥教程:http://www.runoob.com/git/git-tutorial.html  或者去git官網,有非常詳細的資料)

  

一、先來看看怎麼用

  拷一段代碼(./test/main.cpp):

  1 #include "../src/uvw.hpp"
  2 #include <cassert>
  3 #include <iostream>
  4 #include <memory>
  5 #include <chrono>
  6 
  7 
  8 void listen(uvw::Loop &loop) {
  9     std::shared_ptr<uvw::TcpHandle> tcp = loop.resource<uvw::TcpHandle>();            //創建一個TcpHandle
 10 
 11     tcp->on<uvw::ErrorEvent>([](const uvw::ErrorEvent &, uvw::TcpHandle &) {        //註冊錯誤發生函數,
 12         std::cout << "error " << std::endl;
 13     });
 14 
 15     tcp->once<uvw::ListenEvent>([](const uvw::ListenEvent &, uvw::TcpHandle &srv) {    //註冊監聽事件函數
 16         std::cout << "listen" << std::endl;
 17 
 18         std::shared_ptr<uvw::TcpHandle> client = srv.loop().resource<uvw::TcpHandle>();        //創建一個TcpHandle,用於新的client連接
 19 
 20         client->on<uvw::ErrorEvent>([](const uvw::ErrorEvent &, uvw::TcpHandle &) {            //為client註冊錯誤發生函數
 21             std::cout << "error " << std::endl;
 22         });
 23 
 24         client->on<uvw::CloseEvent>([ptr = srv.shared_from_this()](const uvw::CloseEvent &, uvw::TcpHandle &) {    //註冊client關閉函數
 25             std::cout << "close" << std::endl;
 26             ptr->close();    //這裡當client被關閉時,也會關閉server
 27         });
 28 
 29         srv.accept(*client);    //server accept
 30 
 31         uvw::Addr local = srv.sock();
 32         std::cout << "local: " << local.ip << " " << local.port << std::endl;
 33 
 34         uvw::Addr remote = client->peer();
 35         std::cout << "remote: " << remote.ip << " " << remote.port << std::endl;
 36 
 37         client->on<uvw::DataEvent>([](const uvw::DataEvent &event, uvw::TcpHandle &) {        //註冊client接收數據事件函數
 38             std::cout.write(event.data.get(), event.length) << std::endl;                    //event中已經保存有讀取的數據,可以直接使用
 39             std::cout << "data length: " << event.length << std::endl;
 40         });
 41 
 42         client->on<uvw::EndEvent>([](const uvw::EndEvent &, uvw::TcpHandle &handle) {        //註冊client數據讀取結束函數,當socket沒有數據可讀時會發送該事件
 43             std::cout << "end" << std::endl;
 44             int count = 0;
 45             handle.loop().walk([&count](uvw::BaseHandle &) { ++count; });                    //獲取主loop中活躍的套接字,這裡有server和client兩個
 46             std::cout << "still alive: " << count << " handles" << std::endl;
 47             handle.close();        //關閉client連接
 48         });
 49 
 50         client->read();            //開始讀取數據,這裡和uv_read_start的效果相同, 這裡和上面的註冊事件操作,調用時是不分先後順序的。
 51     });
 52 
 53     tcp->once<uvw::CloseEvent>([](const uvw::CloseEvent &, uvw::TcpHandle &) {
 54         std::cout << "close" << std::endl;
 55     });
 56 
 57     tcp->bind("127.0.0.1", 4242);        //bind,這裡支持IPv4和IPv6,bind為一個模版函數
 58     tcp->listen();                        //listen
 59 }
 60 
 61 
 62 void conn(uvw::Loop &loop) {
 63     auto tcp = loop.resource<uvw::TcpHandle>();                //下麵的基本和listen中類似,不多做註釋
 64 
 65     tcp->on<uvw::ErrorEvent>([](const uvw::ErrorEvent &, uvw::TcpHandle &) {
 66         std::cout << "error " << std::endl;
 67     });
 68 
 69     tcp->once<uvw::WriteEvent>([](const uvw::WriteEvent &, uvw::TcpHandle &handle) {
 70         std::cout << "write" << std::endl;
 71         handle.close();
 72     });
 73 
 74     tcp->once<uvw::ConnectEvent>([](const uvw::ConnectEvent &, uvw::TcpHandle &handle) {
 75         std::cout << "connect" << std::endl;
 76 
 77         auto dataTryWrite = std::unique_ptr<char[]>(new char[1]{ 'a' });        //以下操作為向server發送數據
 78         int bw = handle.tryWrite(std::move(dataTryWrite), 1);
 79         std::cout << "written: " << ((int)bw) << std::endl;
 80 
 81         auto dataWrite = std::unique_ptr<char[]>(new char[2]{ 'b', 'c' });
 82         handle.write(std::move(dataWrite), 2);
 83     });
 84 
 85     tcp->once<uvw::CloseEvent>([](const uvw::CloseEvent &, uvw::TcpHandle &) {
 86         std::cout << "close" << std::endl;
 87     });
 88 
 89     tcp->connect("127.0.0.1", 4242);
 90 }
 91 
 92 void g() {
 93     auto loop = uvw::Loop::getDefault();        //獲取預設事件迴圈
 94     listen(*loop);
 95     conn(*loop);
 96     loop->run();        //開始事件迴圈
 97     loop = nullptr;
 98 }
 99 
100 int main() {
101     g();
102 }

 

  (話說怎麼沒有我喜歡的代碼字體的)

  好像挺長的,這邊結構看上去還算是比較清晰。

 

二、仔細看看

  1、server端操作

    listen()函數基本上包含了所有server端的操作,基本流程就是:

      創建TcpHandle(第9行) --> bind(第57行) --> listen(第58行)

 

    在ListenEvent中,可以看到第18行,又創建了一個TcpHandle  client,用來接收客戶端的連接:

      創建TcpHandle(第18行) --> accept(第29行) --> read(第50行)

 

    除了這些其他的代碼就是事件處理的過程,事件處理都是用的Lambda表達式來寫的,比如:

1 tcp->on<uvw::ErrorEvent>([](const uvw::ErrorEvent &, uvw::TcpHandle &) {        //註冊錯誤發生函數,
2         std::cout << "error " << std::endl;
3     });

    Lambda都有兩個參數:

      {Event}:事件,在代碼中可以看很多事件類型,比如CloseEvent,ConnectEvent等(看名字應該就知道是什麼事件了)

      {Handle}:Source類型,這裡可能還會有 UdpHandle等等libuv中出現的類型。以後看到源碼再談。

    後經運行調試,事件處理匿名函數里的{Handle}和創建的TcpHandle其實是相同的。

 

  2、client端操作

    conn函數里基本就是創建一個TcpHandle,然後調用connect連接到伺服器,其他的就是相關的事件。

    另外就是client的數據發送:

1         std::cout << "connect" << std::endl;
2 
3         auto dataTryWrite = std::unique_ptr<char[]>(new char[1]{ 'a' });        //以下操作為向server發送數據
4         int bw = handle.tryWrite(std::move(dataTryWrite), 1);
5         std::cout << "written: " << ((int)bw) << std::endl;
6 
7         auto dataWrite = std::unique_ptr<char[]>(new char[2]{ 'b', 'c' });
8         handle.write(std::move(dataWrite), 2);

    可以看到調用了兩個數據寫入函數,tryWrite和write,相當於uv_write 和 uv_try_write,我給出作者對tryWrite的註釋:    

/**
     * @brief Queues a write request if it can be completed immediately.
     *
     * Same as `write()`, but won’t queue a write request if it can’t be
     * completed immediately.<br/>
     * An ErrorEvent event will be emitted in case of errors.
     *
     * @param data The data to be written to the stream.
     * @param len The lenght of the submitted data.
     * @return Number of bytes written.
     */
    意思就是tryWrite也會發送數據,但是不會立即完成,也不會保證把數據全部一次性發送完。而write會將沒發送完的數據再次加到loop中等待下次發送。

  3、總結

    可以看出來,作者用大量的Lambda來代替了libuv中的各種回調,相比之下,用Lambda,可讀性增加了很多。

    另外代碼中使用了大量的模板函數來區分事件類型,作者源代碼里應該使用了很多泛型,

 

三、相關知識

  1、Lambda

    Lambda又叫做匿名函數,這是個博客園帖子,可以稍微學習或者回顧一下:http://www.cnblogs.com/langzou/p/5962033.html

    PS:這個匿名函數的英文名,有一堆拼寫:Lamda, Lamba,Lamdba,Ladbda。。。真的是千奇百怪。

      這裡給大家強調一下,雖然名字到底怎麼寫對咱們學習東西沒什麼太大影響,但是本著嚴謹的態度,他的英文名正確拼寫應該是

        Lambda    讀音:lan b(m) da(蘭木達)['læmdə]

      它是‘λ’的音譯,百度百科上也是這個拼寫,在《C++ Primer 第5版》的346頁,也可以看到,所以大家以後不要記錯哦,避免被人笑話了。哈哈。

    

    在第24行中:

1 client->on<uvw::CloseEvent>([ptr = srv.shared_from_this()](const uvw::CloseEvent &, uvw::TcpHandle &) {    //註冊client關閉函數
2              std::cout << "close" << std::endl;
3              ptr->close();    //這裡當client被關閉時,也會關閉server
4          });

    大家有沒有註意到這邊 ptr = srv.shared_from_this() 是個什麼東東?

    Lambda中 [] 不應該是用來捕獲外部變數的嗎,怎麼這邊好像是定義了一個ptr變數,並用shared_from_this()來給它初始化了。但是很明顯這個ptr並沒有參數類型,在上下文中也沒有對ptr的聲明。是不是非常奇怪。

    查閱了大量書籍資料後,在 http://zh.cppreference.com/w/cpp/language/lambda 中發現下麵一段:

 1 帶初始化器的捕獲,行動如同它聲明並顯示捕獲以類型 auto 聲明的變數,變數的聲明性區域是 lambda 表達式體(即它不在其初始化器的作用域中),除了:
 2 若捕獲以複製,則閉包的非靜態數據成員是另一種指代該自動變數的方式。
 3 若捕獲以引用在,則引用變數的生存期在閉包對象的生存期結束時結束。
 4 這用於捕獲僅移動類型,以例如 x = std::move(x) 的捕獲
 5 int x = 4;
 6 auto y = [&r = x, x = x + 1]()->int
 7     {
 8         r += 2;
 9         return x * x;
10     }(); // 更新 ::x 為 6 並初始化 y 為 25 。

    可以看出,用的就是這種帶初始化器的捕獲,這是在c++14中新添加的特性

    在第一行中,可以知道,這種帶初始化器的捕獲會自動將變數聲明為auto類型,並且可以對聲明的變數進行初始化。切記,是初始化。對於上面的例子,如果寫成這樣:

1 int x = 4;
2 auto y = [&r = x, r = x + 1]()->int    //錯誤
3     {
4         r += 2;
5         return x * x;
6     }();    

    是錯誤的。另外如果你不初始化,也會產生編譯錯誤。

    現在再看源代碼第24行的代碼,應該就沒什麼問題了吧?

    在瞭解這個之後我又找到一篇介紹這種捕獲類型的文章:http://blog.csdn.net/big_yellow_duck/article/details/52473055

    大家可以一起學習參考一下,看來我也得買本《Effective Modern C++》來看看了。

 

  2、智能指針

    這我就不介紹了,還是博客園的文章,大家可以學習或者回顧一下:http://www.cnblogs.com/qq329914874/p/6653412.html    

 

四、下一篇

  感謝各位看官還能看到這裡,我的這個文筆不是很好,真是委屈各位了。

  接下來一篇會聊到UVW里的一個基礎類,Emitter

  最近時間不太寬裕,可能要等個幾天。

  


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 我查看了一下微信二維碼的內容是:https://u.wechat.com/XXXXXXXXX這種格式。 我現在想把我們的訂單URL也做成 http://abc.com/orderID這樣子,做成二維碼,別人用手機掃描這個二維碼就可以查看這個訂單的詳細說明。 請問我用ASP.NET如何做到這種格式的U ...
  • 專業定製交友網站開發,交友平臺網站建設,婚戀網站平臺開發建設,可仿百合網,世紀佳緣網,支持電腦版+手機版+微信版+小程式版+APP版,由10年的技術團隊專業定製,需要的朋友可以聯繫我們。 網站採用:PHP+MySQL+thinkPHP框架(可定製) <ignore_js_op> <ignore_js ...
  • 專業定製經驗網站開發,仿百度經驗網站系統開發建設,經驗步驟網站系統平臺開發建設,支持電腦版+手機版+微信版+小程式版+APP版,由10年的技術團隊專業定製,需要的朋友可以聯繫我們。 網站採用:PHP+MySQL+thinkPHP框架(可定製) <ignore_js_op> 伺服器選擇: 伺服器購買地 ...
  • 一、添加 Log4j2 的依賴 當我們引入 log4j2 的時候,需要排除掉 spring-boot-starter-web 中引入的 logging。 二、創建 log4j2.xml ...
  • Hibernatede 一對多映射配置 以公司和員工為例:公司是一,員工是多 第一步 創建兩個實體類,公司和員工 寫核心配置文件hibernate.cfg.xml 寫映射配置文件Company.hbm.xml 和Worker.hbm.xml 第二步 讓兩個實體類之間互相表示 (1)在公司實體類裡面表 ...
  • 概要 golang 的包管理一直沒有官方統一的解決方案,因此也產生了很多非官方的包管理工具。 之前我一直使用的 gb() 能夠很好的隔開各個 golang 工程,當時 gb 創建的工程不太融入已有的 GOPATH 中。 gb 相當於是把工程的目錄作為 GOPATH,並且它的 vendor 目錄也和 ...
  • 1、創建用戶 adduser '用戶名' 2、由root用戶進入到其他用戶的終端 su nannanbin 這裡需要註意,有一些命令可能是root許可權才能使用的,這個時候切換成普通用戶的時候,可能會導致這些命令用不了。 3、ls 展示當前目錄下的文件列表,只是展示文件的名稱而已 當然如果你想要看每個 ...
  • 本文地址:http://www.cnblogs.com/aiweixiao/p/7535351.html 歡迎關註我的微信公眾號哈 “ 程式員的文娛情懷” http://t.cn/RotyZtu 【背景】:由於 file函數是一次性將所有內容讀入記憶體,而php為了防止一些寫的比較糟糕的程式占用太多的 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...