UVW源码漫谈(一)

2018-06-17 21:54:33来源:未知 阅读 ()

新老客户大回馈,云服务器低至5折

  博客园是个非常好的学习知识的地方,相信有很多人跟我一样,园龄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

  最近时间不太宽裕,可能要等个几天。

  

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:lintcode 453 将二叉树拆成链表

下一篇:【noip2003】 麦森数