UVW源碼漫談(一)


  博客園是個非常好的學習知識的地方,相信有很多人跟我一樣,園齡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

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

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM