博客园是个非常好的学习知识的地方,相信有很多人跟我一样,园龄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. try catch异常捕获工具类

    异常捕获工具类 package com.example.multiThreadTransaction_demo.utils; import lombok.extern.slf4j.Slf4j; imp ...

  2. 961. 重复 N 次的元素

    地址:https://leetcode-cn.com/problems/n-repeated-element-in-size-2n-array/ <?php /** 在大小为 2N 的数组 A ...

  3. linux系统批量查找网站源码并替换字符,查找替换指定内容

    问题描述:维护中需要批量修改代码中某个字符,如果单个打开页面进行修改则效率过低,linux系统下借助grep命令快速查找替换 命令示例1: grep -rl '查找的内容' . | xargs sed ...

  4. 在ubuntu系统下,安装opencv各个版本

    要在Linux系统上安装OpenCV库,你可以通过包管理器(如apt)来安装.以下是详细的步骤,包括如何在/usr/local/lib或/usr/lib/x86_64-linux-gnu目录下安装Op ...

  5. k8s DCGM GPU采集指标项说明

    dcgm-exporter 采集指标项 https://help.aliyun.com/document_detail/433222.html#section-oin-6mf-6j0 指标 解释 dc ...

  6. PostgreSQL configure: error: readline library not found

    前言 安装 PostgreSQL 时报错,以下 configure: error: readline library not found If you have readline already in ...

  7. Docker Hub 镜像加速器——持续更新(2025年3月12日)

    国内从 Docker Hub 拉取镜像有时会遇到困难,此时可以配置镜像加速器.Docker 官方和国内很多云服务商都提供了国内加速器服务. 配置加速地址 Ubuntu 16.04+.Debian 8+ ...

  8. DeepSeek 3FS 与 JuiceFS:架构与特性比较

    近期,DeepSeek 开源了其文件系统 Fire-Flyer File System (3FS),使得文件系统这一有着 70 多年历时的"古老"的技术,又获得了各方的关注.在 A ...

  9. 还原大师-遍历残缺字符串匹配md5杂凑值

    题目: 我们得到了一串神秘字符串:TASC?O3RJMV?WDJKX?ZM,问号部分是未知大写字母, 为了确定这个神秘字符串,我们通过了其他途径获得了这个字串的32位MD5码. 但是我们获得它的32位 ...

  10. 网络设备开局配置生成器(第三次更新) QQ交流群:(4817315)

    下载:链接: https://pan.baidu.com/s/1BIvh3u7VfbaQtBsUOjl1IA?pwd=kgtw 提取码: kgtw 网络设备开局配置生成器(SecureCRT vbs脚 ...