libuv学习笔记(一)

前言

学网络I/O的时候难免会碰到这样或那样的异步IO库,比如libevent、libev、libuv,看完UNP之后动手写过几个简单的小玩意,总感觉网络底层的那些函数使用起来好麻烦,一个接一个地man起来也挺费劲,于是学习这些成熟网络I/O库的想法应运而生。

初看这些库的简介感觉都差不多,原理和poll/select/epoll等都大同小异,无非是在不同平台上面封装了一层API,不过真想把他们用起来还是没那么容易的,下面就记录一下我学习libuv的一些过程。

最开始看的是libevent,顺便把前面的I/O阻塞非阻塞同步非同步等知识复习了一遍,当我看到bufferevent的时候就看不进去了。。。(菜才是原罪),正好看到这三个库的区别的一个文章,顺便摘录一点下来:

以下对比部分来自:https://blog.csdn.net/lijinqi1987/article/details/71214974

Libevent、libev、libuv三个网络库,都是c语言实现的异步事件库Asynchronousevent library)

异步事件库本质上是提供异步事件通知(Asynchronous Event Notification,AEN)的。异步事件通知机制就是根据发生的事件,调用相应的回调函数进行处理。

事件(Event):事件是异步事件通知机制的核心,比如fd事件、超时事件、信号事件、定时器事件。有时候也称事件为事件处理器(EventHandler),这个名称更形象,因为Handler本身表示了包含处理所需数据(或数据的地址)和处理的方法(回调函数),更像是面向对象思想中的称谓。

事件循环(EventLoop):等待并分发事件。事件循环用于管理事件。

对于应用程序来说,这些只是异步事件库提供的API,封装了异步事件库跟操作系统的交互,异步事件库会选择一种操作系统提供的机制来实现某一种事件,比如利用Unix/Linux平台的epoll机制实现网络IO事件,在同时存在多种机制可以利用时,异步事件库会采用最优机制。

对比下三个库:

libevent :名气最大,应用最广泛,历史悠久的跨平台事件库;

libev :较libevent而言,设计更简练,性能更好,但对Windows支持不够好;

libuv :开发node的过程中需要一个跨平台的事件库,他们首选了libev,但又要支持Windows,故重新封装了一套,linux下用libev实现,Windows下用IOCP实现;

优先级、事件循环、线程安全维度的对比


特性


libevent


libev


libuv


优先级


激活的事件组织在优先级队列中,各类事

件默认的优先级是相同的,可以通过设置

事件的优先级使其优先被处理


也是通过优先级队列来管理激活的时间,

也可以设置事件优先级


没有优先级概念,按照固定的顺序访

问各类事件


事件循环


event_base用于管理事件


激活的事件组织在优先级队列中,各类事件默认的优先级是相同的,

可以通  过设置事件的优先级   使其优先被处理


线程安全


event_base和loop都不是线程安全的,一个event_base或loop实例只能在用户的一个线程内访问(一般是主线程),注册到event_base或者loop的event都是串行访问的,即每个执行过程中,会按照优先级顺序访问已经激活的事件,执行其回调函数。所以在仅使用一个event_base或loop的情况下,回调函数的执行不存在并行关系

代码学习过程(代码注释):

看到好像还是libuv用的人比较多,而且速度比较好,因此打算学习libuv。

大致把libuv的API和User Guide看了一遍之后感觉还是稀里糊涂,感觉还是直接看源码比较好,先把官方文档里面的例子好好熟悉一下:

这是一个简单的tcp-echo-server回射服务器:

tcp-echo-server.c:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <uv.h>
 5
 6 #define DEFAULT_PORT 9877//默认端口
 7 #define DEFAULT_BACKLOG 128//TCP等待连接队列最大值
 8
 9 uv_loop_t *loop;//loop结构指针
10 struct sockaddr_in addr;//ipv4地址结构
11
12 typedef struct {
13     uv_write_t req;
14     uv_buf_t buf;
15 } write_req_t;
16
17 void free_write_req(uv_write_t *req) {//释放资源
18     write_req_t *wr = (write_req_t*) req;
19     free(wr->buf.base);
20     free(wr);
21 }
22
23 void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {//内存分配回调函数,buff指针用于返回相应缓冲地址!!!
24     buf->base = (char*) malloc(suggested_size);//堆上创建buff
25     buf->len = suggested_size;
26 }
27
28 void echo_write(uv_write_t *req, int status) {//status返回write的结果
29     if (status) {
30         fprintf(stderr, "Write error %s\n", uv_strerror(status));
31     }
32     free_write_req(req);
33 }
34
35 void echo_read(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf) {//这些参数都是libuv库为回调函数传递的,其中nread表示当前读到的字节数,buff指向缓冲区
36     if (nread > 0) {
37         write_req_t *req = (write_req_t*) malloc(sizeof(write_req_t));//write_req_t这结构有点多余吧。。直接write_req_t不行么?
38         req->buf = uv_buf_init(buf->base, nread);//复制缓冲区数据(从一个到另一个)
39         uv_write((uv_write_t*) req, client, &req->buf, 1, echo_write);//当该连接能写的时候异步调用
40                                         //其中&req->buf指明了缓冲区的地址,1表示如果存在uv_buf_t数组,数组的元素个数
41                                         //echo_write是uv_write_cb类型回调指针,当write完成后调用。void (*uv_write_cb)(uv_write_t* req, int status)
42         return;
43     }
44     if (nread < 0) {//出错
45         if (nread != UV_EOF)//UV_EOF不一定是0,具体见文档
46             fprintf(stderr, "Read error %s\n", uv_err_name(nread));
47         uv_close((uv_handle_t*) client, NULL);
48     }
49
50     free(buf->base);//释放缓冲区
51 }
52
53 void on_new_connection(uv_stream_t *server, int status) {//这里的status参数就是库传给我们的状态参数,指示当前connect的状态(是否能连接,是否出错等)
54     if (status < 0) {//<0表示出错
55         fprintf(stderr, "New connection error %s\n", uv_strerror(status));
56         // error!
57         return;
58     }
59
60     uv_tcp_t *client = (uv_tcp_t*) malloc(sizeof(uv_tcp_t));//新建uv_tcp_t进行连接
61     uv_tcp_init(loop, client);//简介之后也把这个tcp流绑定在loop上
62     if (uv_accept(server, (uv_stream_t*) client) == 0) {//连接
63         uv_read_start((uv_stream_t*) client, alloc_buffer, echo_read);//连接完成后在event loop准备读取,这也是个异步回调
64                         //等到这个事件发生(能读),异步回调才会开始。
65                         //alloc_buffer参数这里第一次碰到,其回调函数格式为void (*uv_alloc_cb)(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf)
66                         //该函数负责为当前行为分配缓冲区,在当前事件发生之后运行,先进行缓冲区分配工作,在这个回调函数中libuv会向你提供一个suggested_size作为缓冲区大小的建议值
67                         //我们需要做的是在堆上分配uv_buf_t这样的数据结构,并把buf指针指向该地址!(这个理解很关键。。。应该是这样吧?:))
68                         //echo_read是另一个uv_read_cb类型的回调函数,会在libuv完成read之后调用(时间点重要)。
69     }
70     else {
71         uv_close((uv_handle_t*) client, NULL);//出现错误,关闭
72     }
73 }
74
75 int main() {
76     loop = uv_default_loop();//使用默认loop
77
78     uv_tcp_t server;//tcp_t结构,这是在栈上分配
79     uv_tcp_init(loop, &server);//算是把tcp绑定在了loop上面
80
81     uv_ip4_addr("0.0.0.0", DEFAULT_PORT, &addr);//ip和端口直接获得sockaddr_in结构。。要是自己写要好几个函数
82
83     uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0);//绑定tcp连接和地址
84     int r = uv_listen((uv_stream_t*) &server, DEFAULT_BACKLOG, on_new_connection);//开始监听,loop上面对于server的event监听正式开始
85                                 //这里的几个参数才是关键:这里可以把uv_tcp_t当成uv_stream_t的子类(进行了结构体的扩展),所以这里可以使用强制类型转换绑定的流
86                                 //on_new_connection作为一个回调函数(有固定格式,库会有参数传递),当有连接可以connect的时候进行调用,从参数来看调用时connect还没完成!
87     if (r) {
88         fprintf(stderr, "Listen error %s\n", uv_strerror(r));//libuv错误处理函数
89         return 1;
90     }
91     return uv_run(loop, UV_RUN_DEFAULT);//开始event loop
92 }

感觉把每个函数的调用时间搞清楚,把所有参数传递的过程搞清楚libuv库也就搞清楚了。

第一次看这种这么多回调函数的代码时感觉真的是不舒服,但一步一步想通之后就感觉好多了,关键要搞清楚libuv库到底为我们提供了什么。

原文地址:https://www.cnblogs.com/J1ac/p/9038581.html

时间: 2024-08-02 22:48:36

libuv学习笔记(一)的相关文章

libuv学习笔记(24)

libuv学习笔记(24) 线程相关数据结构与函数(2) 数据结构 typedef union {//读写锁 struct { unsigned int num_readers_; CRITICAL_SECTION num_readers_lock_; HANDLE write_semaphore_; } state_; /* TODO: remove me in v2.x. */ struct { SRWLOCK unused_; } unused1_; /* TODO: remove me

Kestrel Web 服务器学习笔记

前言: ASP.NET Core 已经不是啥新鲜的东西,很多新启的项目都会首选 Core 做开发: 而 Kestrel 可以说是微软推出的唯一真正实现跨平台的 Web 服务器了: Kestrel 利用一个名为 KestrelEngine 的网络引擎实现对请求的监听.接收和响应: Ketrel 之所以具有跨平台的特质,源于 KestrelEngine 是在一个名为 libuv 的跨平台网络库上开发的: Kestrel is a cross-platform web server for ASP.N

Linux下ASP.NET5开发工具与部署环境搭建 (学习笔记)

1.说明 由于在“古董机”上进行实践,只能安装系统是ubuntu-15.04-desktop-i386 (x86 32位桌面系统,建议你装64位的) 本想在此介绍时进行一些截图或录制视频,但对Linux系统操作不是很熟,再加上系统特别“卡”, 连汉字输入都不方便(比如这篇文章敲得费劲呀),实在是杯具,只好放弃! 以下内容,有些啰嗦,抱歉!(其实也是我学习笔记,记详细点,时间长了,不怕忘.) 以下资料来源主要参考:https://docs.asp.net和https://github.com/as

vector 学习笔记

vector 使用练习: /**************************************** * File Name: vector.cpp * Author: sky0917 * Created Time: 2014年04月27日 11:07:33 ****************************************/ #include <iostream> #include <vector> using namespace std; int main

Caliburn.Micro学习笔记(一)----引导类和命名匹配规则

Caliburn.Micro学习笔记(一)----引导类和命名匹配规则 用了几天时间看了一下开源框架Caliburn.Micro 这是他源码的地址http://caliburnmicro.codeplex.com/ 文档也写的很详细,自己在看它的文档和代码时写了一些demo和笔记,还有它实现的原理记录一下 学习Caliburn.Micro要有MEF和MVVM的基础 先说一下他的命名规则和引导类 以后我会把Caliburn.Micro的 Actions IResult,IHandle ICondu

jQuery学习笔记(一):入门

jQuery学习笔记(一):入门 一.JQuery是什么 JQuery是什么?始终是萦绕在我心中的一个问题: 借鉴网上同学们的总结,可以从以下几个方面观察. 不使用JQuery时获取DOM文本的操作如下: 1 document.getElementById('info').value = 'Hello World!'; 使用JQuery时获取DOM文本操作如下: 1 $('#info').val('Hello World!'); 嗯,可以看出,使用JQuery的优势之一是可以使代码更加简练,使开

[原创]java WEB学习笔记93:Hibernate学习之路---Hibernate 缓存介绍,缓存级别,使用二级缓存的情况,二级缓存的架构集合缓存,二级缓存的并发策略,实现步骤,集合缓存,查询缓存,时间戳缓存

本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱好者,互联网技术发烧友 微博:伊直都在0221 QQ:951226918 -----------------------------------------------------------------------------------------------------------------

Activiti 学习笔记记录(三)

上一篇:Activiti 学习笔记记录(二) 导读:上一篇学习了bpmn 画图的常用图形标记.那如何用它们组成一个可用文件呢? 我们知道 bpmn 其实是一个xml 文件

HTML&CSS基础学习笔记8-预格式文本

<pre>标签的主要作用是预格式化文本.被包围在 pre 标签中的文本通常会保留空格和换行符.而文本也会呈现为等宽字体. <pre>标签的一个常见应用就是用来表示计算机的源代码.当然你也可以在你需要在网页中预显示格式时使用它. 会使你的文本换行的标签(例如<h>.<p>)绝不能包含在 <pre> 所定义的块里.尽管有些浏览器会把段落结束标签解释为简单地换行,但是这种行为在所有浏览器上并不都是一样的. 更多学习内容,就在码芽网http://www.