博客园是个非常好的学习知识的地方,相信有很多人跟我一样,园龄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源码漫谈(一)的更多相关文章

  1. UVW源码漫谈(四)

    十一假期后就有点懒散,好长时间都没想起来写东西了.另外最近在打LOL的S赛.接触LOL时间不长,虽然平时玩的比较少,水平也相当菜,但是像这种大型的赛事有时间还是不会错过的.主要能够感受到选手们对竞技的 ...

  2. UVW源码漫谈(三)

    咱们继续看uvw的源码,这次看的东西比较多,去除底层的一些东西,很多代码都是连贯的,耦合度也比较高了.主要包括下面几个文件的代码: underlying_type.hpp resource.hpp l ...

  3. UVW源码漫谈(番外篇)—— Emitter

    这两天天气凉了,苏州这边连续好几天都是淅淅沥沥的下着小雨,今天天气还稍微好点.前两天早上起来突然就感冒了,当天就用了一卷纸,好在年轻扛得住,第二天就跟没事人似的.在这里提醒大家一下,天气凉了,睡凉席的 ...

  4. UVW源码漫谈(二)

    前一篇发布出来之后,我看着阅读量还是挺多的,就是评论和给意见的一个都没有,或许各位看官就跟我一样,看帖子从不回复,只管看就行了.毕竟大家都有公务在身,没太多时间,可以理解.不过没关系,我是不是可以直接 ...

  5. 【转】tars源码漫谈第1篇------tc_loki.h (牛逼哄哄的loki库)

    loki库是C++模板大牛Andrei写的, 里面大量运用模板的特性, 而tc_loki.h借用了loki库的部分代码, 形成了一个基本的文件tc_loki.h, 来看看: #ifndef __TC_ ...

  6. 漫谈可视化Prefuse(六)---改动源码定制边粗细

    可视化一路走来,体会很多:博客一路写来,收获颇丰:代码一路码来,思路越来越清晰.终究还是明白了一句古话:纸上得来终觉浅,绝知此事要躬行. 跌跌撞撞整合了个可视化小tool,零零碎碎结交了众多的志同道合 ...

  7. Alink漫谈(二) : 从源码看机器学习平台Alink设计和架构

    Alink漫谈(二) : 从源码看机器学习平台Alink设计和架构 目录 Alink漫谈(二) : 从源码看机器学习平台Alink设计和架构 0x00 摘要 0x01 Alink设计原则 0x02 A ...

  8. Alink漫谈(十六) :Word2Vec源码分析 之 建立霍夫曼树

    Alink漫谈(十六) :Word2Vec源码分析 之 建立霍夫曼树 目录 Alink漫谈(十六) :Word2Vec源码分析 之 建立霍夫曼树 0x00 摘要 0x01 背景概念 1.1 词向量基础 ...

  9. Alink漫谈(十七) :Word2Vec源码分析 之 迭代训练

    Alink漫谈(十七) :Word2Vec源码分析 之 迭代训练 目录 Alink漫谈(十七) :Word2Vec源码分析 之 迭代训练 0x00 摘要 0x01 前文回顾 1.1 上文总体流程图 1 ...

  10. Alink漫谈(十八) :源码解析 之 多列字符串编码MultiStringIndexer

    Alink漫谈(十八) :源码解析 之 多列字符串编码MultiStringIndexer 目录 Alink漫谈(十八) :源码解析 之 多列字符串编码MultiStringIndexer 0x00 ...

随机推荐

  1. Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露

    一:背景 1. 讲故事 前面跟大家分享过一篇 C# 调用 C代码引发非托管内存泄露 的文章,这是一个故意引发的正向泄露,这一篇我们从逆向的角度去洞察引发泄露的祸根代码,这东西如果在 windows 上 ...

  2. 万字长文手把手教你实现MicroPython/Python发布第三方库

    MicroPython/Python 发布第三方库 原文链接: FreakStudio的博客 摘要 文章讲解内容包括第三方库文件说明和组织.开源许可协议选择.通过black模块.Flake8模块和预提 ...

  3. 【Abaqus】Composite Layup建模

    abaqus 的3个复合材料建模途径: 传统的material->section->orientation->step->job的建模方式 Composite Layup建模方 ...

  4. python excel 数据整理:如何删除重复的记录

    data = frame.drop_duplicates(subset='', keep='first', inplace='') drop_duplicates用法:subset='需要去重复的列名 ...

  5. 理解Python中的元类(metaclass)

    类也是对象 在理解元类之前,你需要先掌握Python中的类.Python中类的概念借鉴于Smalltalk,这显得有些奇特.在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段.在Pytho ...

  6. Ansible创建逻辑卷

    环境: 受控主机清单文件: [dev] 192.168.10.129 [all:vars] ansible_ssh_user=root ansible_ssh_pass=123 磁盘: 受控主机需要存 ...

  7. bp靶场业务逻辑漏洞

    修改商品价格 目标是修改价格后低价买下这件夹克,再home这个页面的第一个商品就是 点击Add to cart,抓包,发现price的值可以该,改成000发现不行,0.00也不行,改成10后发现购物车 ...

  8. [源码系列:手写spring] IOC第十二节:FactoryBean

    内容介绍 在 Spring 框架中,FactoryBean 是一个特殊的工厂类接口,用于创建和管理复杂的 bean 对象.它允许你自定义 bean 的创建逻辑,并且可以在 bean 创建过程中执行一些 ...

  9. 关于er图的几个工具

    建立数据库包括其他的er图,这个太重要了.因为这关于效率和清晰思路. 但是目前感觉好用的还是ER/Studio.如果不差银子还是建议用这一款.真的好方便. 1.正向逆向工程非常顺利和快捷. 2.物理模 ...

  10. Cursor预测程序员行业倒计时:CTO应做好50%裁员计划

    提供AI咨询+AI项目陪跑服务,有需要回复1 前两天跟几个业内同学做了一次比较深入的探讨,时间从15.00到21.00,足足6个小时! 其中有个问题特别有意思:从ChatGPT诞生到DeepSeek爆 ...