• 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    

      

  • 相关阅读:
    决策树理解
    堆排序
    glove理解
    PHP图片水印类
    宝塔nginx安装rtmp模块实现推拉流
    nginx安装配置
    结构体,位域,共用体
    指针
    升级mac Catalina版本后无操作权限
    脚本连接linux服务器
  • 原文地址:https://www.cnblogs.com/yxfangcs/p/7527658.html
Copyright © 2020-2023  润新知