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

 #include "../src/uvw.hpp"
#include <cassert>
#include <iostream>
#include <memory>
#include <chrono> void listen(uvw::Loop &loop) {
std::shared_ptr<uvw::TcpHandle> tcp = loop.resource<uvw::TcpHandle>(); //创建一个TcpHandle tcp->on<uvw::ErrorEvent>([](const uvw::ErrorEvent &, uvw::TcpHandle &) { //注册错误发生函数,
std::cout << "error " << std::endl;
}); tcp->once<uvw::ListenEvent>([](const uvw::ListenEvent &, uvw::TcpHandle &srv) { //注册监听事件函数
std::cout << "listen" << std::endl; std::shared_ptr<uvw::TcpHandle> client = srv.loop().resource<uvw::TcpHandle>(); //创建一个TcpHandle,用于新的client连接 client->on<uvw::ErrorEvent>([](const uvw::ErrorEvent &, uvw::TcpHandle &) { //为client注册错误发生函数
std::cout << "error " << std::endl;
}); client->on<uvw::CloseEvent>([ptr = srv.shared_from_this()](const uvw::CloseEvent &, uvw::TcpHandle &) { //注册client关闭函数
std::cout << "close" << std::endl;
ptr->close(); //这里当client被关闭时,也会关闭server
}); srv.accept(*client); //server accept uvw::Addr local = srv.sock();
std::cout << "local: " << local.ip << " " << local.port << std::endl; uvw::Addr remote = client->peer();
std::cout << "remote: " << remote.ip << " " << remote.port << std::endl; client->on<uvw::DataEvent>([](const uvw::DataEvent &event, uvw::TcpHandle &) { //注册client接收数据事件函数
std::cout.write(event.data.get(), event.length) << std::endl; //event中已经保存有读取的数据,可以直接使用
std::cout << "data length: " << event.length << std::endl;
}); client->on<uvw::EndEvent>([](const uvw::EndEvent &, uvw::TcpHandle &handle) { //注册client数据读取结束函数,当socket没有数据可读时会发送该事件
std::cout << "end" << std::endl;
int count = ;
handle.loop().walk([&count](uvw::BaseHandle &) { ++count; }); //获取主loop中活跃的套接字,这里有server和client两个
std::cout << "still alive: " << count << " handles" << std::endl;
handle.close(); //关闭client连接
}); client->read(); //开始读取数据,这里和uv_read_start的效果相同, 这里和上面的注册事件操作,调用时是不分先后顺序的。
}); tcp->once<uvw::CloseEvent>([](const uvw::CloseEvent &, uvw::TcpHandle &) {
std::cout << "close" << std::endl;
}); tcp->bind("127.0.0.1", ); //bind,这里支持IPv4和IPv6,bind为一个模版函数
tcp->listen(); //listen
} void conn(uvw::Loop &loop) {
auto tcp = loop.resource<uvw::TcpHandle>(); //下面的基本和listen中类似,不多做注释 tcp->on<uvw::ErrorEvent>([](const uvw::ErrorEvent &, uvw::TcpHandle &) {
std::cout << "error " << std::endl;
}); tcp->once<uvw::WriteEvent>([](const uvw::WriteEvent &, uvw::TcpHandle &handle) {
std::cout << "write" << std::endl;
handle.close();
}); tcp->once<uvw::ConnectEvent>([](const uvw::ConnectEvent &, uvw::TcpHandle &handle) {
std::cout << "connect" << std::endl; auto dataTryWrite = std::unique_ptr<char[]>(new char[]{ 'a' }); //以下操作为向server发送数据
int bw = handle.tryWrite(std::move(dataTryWrite), );
std::cout << "written: " << ((int)bw) << std::endl; auto dataWrite = std::unique_ptr<char[]>(new char[]{ 'b', 'c' });
handle.write(std::move(dataWrite), );
}); tcp->once<uvw::CloseEvent>([](const uvw::CloseEvent &, uvw::TcpHandle &) {
std::cout << "close" << std::endl;
}); tcp->connect("127.0.0.1", );
} void g() {
auto loop = uvw::Loop::getDefault(); //获取默认事件循环
listen(*loop);
conn(*loop);
loop->run(); //开始事件循环
loop = nullptr;
} int main() {
g();
}

  (话说怎么没有我喜欢的代码字体的)

  好像挺长的,这边结构看上去还算是比较清晰。

二、仔细看看

  1、server端操作

    listen()函数基本上包含了所有server端的操作,基本流程就是:

      创建TcpHandle(第9行) --> bind(第57行) --> listen(第58行)

    在ListenEvent中,可以看到第18行,又创建了一个TcpHandle  client,用来接收客户端的连接:

      创建TcpHandle(第18行) --> accept(第29行) --> read(第50行)

    除了这些其他的代码就是事件处理的过程,事件处理都是用的Lambda表达式来写的,比如:

 tcp->on<uvw::ErrorEvent>([](const uvw::ErrorEvent &, uvw::TcpHandle &) {        //注册错误发生函数,
std::cout << "error " << std::endl;
});

    Lambda都有两个参数:

      {Event}:事件,在代码中可以看很多事件类型,比如CloseEvent,ConnectEvent等(看名字应该就知道是什么事件了)

      {Handle}:Source类型,这里可能还会有 UdpHandle等等libuv中出现的类型。以后看到源码再谈。

    后经运行调试,事件处理匿名函数里的{Handle}和创建的TcpHandle其实是相同的。

  2、client端操作

    conn函数里基本就是创建一个TcpHandle,然后调用connect连接到服务器,其他的就是相关的事件。

    另外就是client的数据发送:

         std::cout << "connect" << std::endl;

         auto dataTryWrite = std::unique_ptr<char[]>(new char[]{ 'a' });        //以下操作为向server发送数据
int bw = handle.tryWrite(std::move(dataTryWrite), );
std::cout << "written: " << ((int)bw) << std::endl; auto dataWrite = std::unique_ptr<char[]>(new char[]{ 'b', 'c' });
handle.write(std::move(dataWrite), );

    可以看到调用了两个数据写入函数,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行中:

 client->on<uvw::CloseEvent>([ptr = srv.shared_from_this()](const uvw::CloseEvent &, uvw::TcpHandle &) {    //注册client关闭函数
std::cout << "close" << std::endl;
ptr->close(); //这里当client被关闭时,也会关闭server
});

    大家有没有注意到这边 ptr = srv.shared_from_this() 是个什么东东?

    Lambda中 [] 不应该是用来捕获外部变量的吗,怎么这边好像是定义了一个ptr变量,并用shared_from_this()来给它初始化了。但是很明显这个ptr并没有参数类型,在上下文中也没有对ptr的声明。是不是非常奇怪。

    查阅了大量书籍资料后,在 http://zh.cppreference.com/w/cpp/language/lambda 中发现下面一段:

 带初始化器的捕获,行动如同它声明并显示捕获以类型 auto 声明的变量,变量的声明性区域是 lambda 表达式体(即它不在其初始化器的作用域中),除了:
若捕获以复制,则闭包的非静态数据成员是另一种指代该自动变量的方式。
若捕获以引用在,则引用变量的生存期在闭包对象的生存期结束时结束。
这用于捕获仅移动类型,以例如 x = std::move(x) 的捕获
int x = ;
auto y = [&r = x, x = x + ]()->int
{
r += ;
return x * x;
}(); // 更新 ::x 为 6 并初始化 y 为 25 。

    可以看出,用的就是这种带初始化器的捕获,这是在c++14中新添加的特性

    在第一行中,可以知道,这种带初始化器的捕获会自动将变量声明为auto类型,并且可以对声明的变量进行初始化。切记,是初始化。对于上面的例子,如果写成这样:

 int x = ;
auto y = [&r = x, r = x + ]()->int //错误
{
r += ;
return x * x;
}();

    是错误的。另外如果你不初始化,也会产生编译错误。

    现在再看源代码第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

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

  

UVW代码漫谈(一)的更多相关文章

  1. 漫谈PHP代码规范

    前言 虽说PHP是世界上最好的语言,但是写出来的PHP代码却往往不是最美观的.究其原因,可能正式因为PHP简单易上手,适合快速迭代的特性,导致了我们沉浸在迅速完成需求迭代的窃喜中,却忘记了规范性.忽略 ...

  2. UVW源码漫谈(二)

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

  3. UVW源码漫谈(四)

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

  4. UVW源码漫谈(三)

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

  5. HTML5漫谈(7)——如何保护HTML5应用代码

    独家供稿:移动Labs HTML5应用采用的仍然是Javascript(JS).HTML.CSS 等Web语言,因而其代码保护就是这些Web代码的保护,而HTML5应用主要功能一般采用JS实现,因此J ...

  6. 漫谈四种神经网络序列解码模型【附示例代码】 glimpse attention

    漫谈四种神经网络序列解码模型[附示例代码] http://jacoxu.com/encoder_decoder/ [视觉注意力的循环神经网络模型]http://blog.csdn.net/leo_xu ...

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

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

  8. JDK8漫谈——代码更优雅

    简介 lambda表达式,又称闭包(Closure)或称匿名方法(anonymous method).将Lambda表达式引入JAVA中的动机源于一个叫"行为参数"的模式.这种模式 ...

  9. 【道德经】漫谈实体、对象、DTO及AutoMapper的使用

    写在前面 实体和值对象 实体和对象 故常无欲以观其妙,常有欲以观其徼 初始实体和演化实体 代码中的DTO AutoMapper实体转换 后记 实体(Entity).对象(Object).DTO(Dat ...

随机推荐

  1. Git时光机穿梭之工作区和暂存区

    Git和其他版本控制系统如SVN的一个不同之处就是有暂存区的概念. 先来看名词解释. 工作区(Working Directory) 就是你在电脑里能看到的目录,比如我的learngit文件夹就是一个工 ...

  2. hdu--1018--Big Number(斯特林公式)

    Big Number Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total ...

  3. python进阶(4):初始面向对象

    一切皆对象! 面向过程-->面向对象 面向过程:根据业务逻辑从上到下堆叠代码 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可 面向对象:对函数进行分类和封装,让开发“更快更 ...

  4. [入门向选讲] 插头DP:从零概念到入门 (例题:HDU1693 COGS1283 BZOJ2310 BZOJ2331)

    转载请注明原文地址:http://www.cnblogs.com/LadyLex/p/7326874.html 最近搞了一下插头DP的基础知识……这真的是一种很锻炼人的题型…… 每一道题的状态都不一样 ...

  5. Java微信公众平台开发之OAuth2.0网页授权

    根据官方文档点击查看在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的"开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息"的配置选项中,修 ...

  6. 再学ajax--第一天

    今天写这个帖子就是是前几天在学ES6在学到Promise实现AJAX操作时,发现对ajax的一些知识点有些遗忘,所以就回头重新复习了一遍ajax,温故而知新. 主要有从4个方面去复习ajax,分析不透 ...

  7. /dev/null 2>&1解释

    /dev/null 2>&1 解释 crontab内容 :50 18 5-30 * * /script/myscript.sh 1> /dev/null 2>&1 其 ...

  8. MSCI 成份股 清单

    中国加入msci最新消息,MSCI官方6月21日公布将A股纳入新兴市场指数.值得注意的是,今年3月23日,MSCI曾公布了咨询文件,建议只纳入可以通过沪股通和深股通买卖的大盘股,剔除了两地联合上市的公 ...

  9. PageObject设计模式,在selenium自动化测试中的运用

    PageObject设计模式1. Web自动化测试框架(WebTestFramework)是基于Selenium框架且采用PageObject设计模式进行二次开发形成的框架. 2. web测试时,建议 ...

  10. 2、粘包现象(struct模块)

    昨天我们所做的套接字是有漏洞的,它会出现粘包现象,没有发现这个问题的我们今天会进行演示.今天也会稍微讲解一下基于udp的套接字. 一.基于udp的套接字 udp是无链接的,先启动哪一端都不会报错 ud ...