uvw 源码阅读
这一部分开始我们再接触一个新库叫做 uvw
,那这个库的话只看介绍就比较令人激动了
uvw
started as a header-only, event based, tiny and easy to use wrapper forlibuv
written in modern C++.Now it's finally available also as a compilable static library.
uvw
最初是一个header only
的库,通过现代 C++ 封装了libuv
的功能,基于事件驱动(event based),精巧且易于使用 现在同时支持编译为static
静态库使用
libuv
的话是属于名气比较高的一个基于事件驱动的异步 I/O 库,和 libevent
, libev
互为替代品,功能的话主要就是负责维护一个队列,那在这个队列上你可以注册自己感兴趣的事件同时添加一个回调函数(比如添加一个定时任务,回调设置为一个需要定期执行的函数),那么当事件发生时(定时任务到期触发)就会调用之前注册的回调函数。这个概念应该比较好理解,尤其是之前对 epoll
有了解的话。
首先还是看一下基本的用例:
这段代码就有很大的信息量了,一起来读一下。首先看 main
函数,通过 uvw::Loop::getDefault
创建了一个事件循环,接着调用 listen
和 conn
并传入 loop
,最后执行 loop->run()
。
暂时跳过创建事件循环的方法,接着看下 listen
和 conn
中做了什么,先来看 listen
:
首先创建了一个 uvw::TCPHandle
,那从类型名字不难猜出是用来处理 tcp 事件的,同时说明 libuv
所处理的事件类型应该是有限制的,那不知道可不可以通过继承某个类去做自定义的 Handle , 接着是 tcp->once<uvw::ListenEvent>();
应该是注册了一个只执行一次的事件并设置回调函数为一个 lambda
函数,也就是在接收到客户端的连接请求后注册两个事件 CloseEvent
和 EndEvent
,接着 accept
客户端请求,并尝试读取客户端 socket。
事件注册完成后通过 bind
设置 IP 和端口并正式开始 listen
,当然这里不太确定机制,也有可能是等到 loop->run
再开始做?
不管怎样我们接着看 conn
的实现:
这里同样是拿了 uvw::TCPHandle
,这样看的话可能 tcp
是单例模式所以拿到的都是同一个?接着也是注册了两个事件,一个是用于错误处理,而另外一个是在连接建立成功后直接写一个 "bc"
然后关闭 socket 的一次性事件。
接着调用 tcp->connect
去尝试连接 4242 端口,这个端口就是我们刚刚监听的端口,那上面理解有一些偏差,两个函数中的 tcp
并不是同一个,这里 listen
实际上扮演了 server
的角色去监听端口并尝试读取内容, conn
则扮演了 client
的角色,主动连接并发送内容。
现在还剩余一个疑问就是 listen
、 connect
和 loop->run()
的执行顺序,这里先行保留。
接着来看看 uvw::Loop::getDefault()
的实现:
这里返回了 std::shared_ptr<Loop>
,那也就不难解释之前传参时候为什么都使用 *loop
了,接着这里使用 static std::weak_ptr<Loop>
做了一个单例的实现,如果 ref
没有指向的对象或者对象已经销毁那么通过 uv_default_loop
生成一个默认事件循环,否则通过 ref.lock()
获取一个 std::shared_ptr<Loop>
返回。
这里的代码存在一点问题,一般我们做一个单例的实现的时候都需要用一些手段确保它是线程安全的,那这里的话就可能出现两个线程同时调用时因为还没有实例化因此两个线程一起进入 if(ref.expired())
分支的情况。如果要确保线程安全的话代码类似这样:
这里我们使用一个锁保证了任务执行的先后顺序,当第一个执行函数的线程实例化 Loop
完成并返回后第二个线程才能尝试获取。
接着这里 uv_default_loop
应该是 libuv
的函数,返回一个 uv_loop_t*
类型的 def
, 接着把这个指针放到 unique_ptr
中去管理却给了一个空的自定义 Deleter,这里应该是要交给其他地方去释放。接着把指针移交给 shared_ptr<Loop>
管理,我们来简单看一下 Loop
的构造函数:
这里就是接受一个 std::unique_ptr<uv_loop_t, Deleter>
类型的指针也就是我们刚刚生成并包装的 def
,直接给到Loop::loop
没有做额外动作。
顺便看一下析构函数:
那这里可以看到也确实是交还给 libuv
去关闭了。
那么本节内容就到这里,下一节我们一起看一下 loop->run()
的实现。
下次再会!