libuv库使用

一 概述

  github地址
  官方文档
  libuv是跨平台、轻量级的异步I/O库,由Node.js团队发起和维护。它提供了事件循环、定时器、异步文件和网络操作等功能,使开发者可以方便地处理各种I/O任务。
  libuv提供了一套强大而易用的异步I/O编程接口,在网络编程、文件系统操作、定时器等方面具有广泛的应用场景。由于其开源、跨平台、高效、稳定等优点,被越来越多的开发者采用并集成在自己的项目中。
  无论是在Node.js还是其他项目中,libuv都负责处理底层的事件循环和I/O操作,使开发者能够编写高效且非阻塞的代码。它在不同的操作系统上使用不同的后端实现,如epollkqueueIOCP等,以便充分利用各个平台的特性和性能。
  “uv” 是指"Unicorn Velociraptor"的缩写。"Unicorn Velociraptor"是libuv的创始人 Bert Belder 的幽默取名,没有特殊的技术含义。
  libuv的主要特点包括:

跨平台:libuv可以在多种操作系统上运行,包括Windows、Linux、macOS等,使得开发者无需考虑操作系统的差异性。
异步模型:libuv基于事件驱动模型实现异步I/O,允许应用程序在处理资源紧张、高并发的客户端请求时,不阻塞主线程,提高可伸缩性和响应速度。
网络编程支持:libuv提供了对TCP/UDP以及TLS/SSL等协议的支持,可以轻松实现网络通信功能。
文件系统支持:libuv支持异步文件操作,包括读取、写入、修改、删除等操作,避免文件操作导致的线程阻塞或死锁问题。
定时器支持:libuv提供定时器功能,允许应用程序在一定时间后执行指定的回调函数。
多线程支持:libuv可以创建多个事件循环对象,每个事件循环对象都有自己的I/O线程池,应用程序可以分配不同的任务给不同的事件循环处理。

在这里插入图片描述
  单以 Linux 平台来看,libuv 主要工作可以简单划为两部分:

  • 围绕 epoll,处理那些被 epoll 支持的 IO 操作;
  • 线程池(Thread pool),处理那些不被 epoll 支持的 IO 操作;

二 epoll 简介

  为了追本溯源,我们将从 epoll 开始,简单来说,epoll 是由 Linux 内核提供的一个系统调用(system call),我们的应用程序可以通过它:

  • 告诉系统帮助我们同时监控多个文件描述符;
  • 当这其中的一个或者多个文件描述符的 I/O 可操作状态改变时,我们的应用程序会接收到来自系统的事件提示(event notification);

1 事件循环

  我们通过一小段伪代码来演示使用 epoll 时的核心步骤:

// 创建 epoll 实例
int epfd = epoll_create(MAX_EVENTS);
// 向 epoll 实例中添加需要监听的文件描述符,这里是 `listen_sock`
epoll_ctl_add(epfd, listen_sock, EPOLLIN | EPOLLOUT | EPOLLET);
while(1)
{
  // 等待来自 epoll 的通知,通知会在其中的文件描述符状态改变时
  // 由系统通知应用。通知的形式如下:
  // epoll_wait 调用不会立即返回,系统会在其中的文件描述符状态发生
  // 变化时返回
  // epoll_wait 调用返回后:
  // nfds 表示发生变化的文件描述符数量
  // events 会保存当前的事件,它的数量就是 nfds
  int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
  // 遍历 events,对事件作出符合应用预期的响应
  for (int i = 0; i < nfds; i++)
  {
    // consume events[i]
  }
}

  上面的代码中已经包含了注释,可以大致概括为下图:
在这里插入图片描述
  所以处于 libuv 底层的 epoll 也是有「事件循环」的概念,可见事件循环并不是 libuv 独创。
  提到 epoll,不得不提它的两种触发模式:水平触发(Level-triggered)、边缘触发(Edge-triggered)。不得不提是因为它们关系到 epoll 的事件触发机制,加上名字取得又有些晦涩。

2 水平触发

  这两个术语都源自电子学领域,我们从它们的原始含义开始理解
  首先是水平触发:
在这里插入图片描述

Electrical Concepts

  上图是表示电压变化的时序图,VH 表示电压的峰值,VL 表示电话的谷值。水平触发的含义是,随着时间的变化,只要电压处于峰值,系统就会激活对应的电路(触发)

3 边缘触发

在这里插入图片描述

Electrical Concepts

  上图依然是表示电压变化的时序图,不过激活电路(触发)的条件是电压的改变,即电压由 VH -> VL、VL -> VH 的状态变化,在图中通过边来表示这个变化,即 Rising edgeFalling edge,所以称为 Edge-triggered 即边缘触发。
  我们可以大致理解它们的形式与差别,继续结合下面的 epoll 中的表现进行理解。

4 在 epoll 中

  回到 epoll 中,水平触发和边缘触发作为原始含义的衍生,当然还是具有类似电子学领域中的含义,我们通过一个例子来理解,比如我们有一个 fd(File descriptor) 表示刚建立的客户端连接,随后客户端给我们发送了 5 bytes 的内容。
  如果是水平触发:

  • 我们的应用会被系统唤醒,因为 fd 此时状态变为了可读
  • 我们从系统的缓冲区中读取 1 byte 的内容,并做了一些业务操作
  • 进入到新的一次事件循环,等待系统下一次唤醒
  • 系统继续唤醒我们的应用,因为缓冲区还有未读取的 4 bytes 内容

  如果是边缘触发:

  • 我们的应用会被系统唤醒,因为 fd 此时状态变为了可读
  • 我们从系统的缓冲区中读取 1 byte 的内容,并做了一些业务操作
  • 进入到新的一次事件循环,等待系统下一次唤醒
  • 此时系统并不会唤醒我们的应用,直到下一次客户端发送了一些内容,比如发送了 2 bytes(因为直到下一次客户端发送了请求之前,fd 的状态并没有改变,所以在边缘触发下系统不会唤醒应用)
  • 系统唤醒我们的应用,此时缓冲区有 6 bytes = (4 + 2) bytes

  我们很难将水平触发、边缘触发的字面意思与上面的行为联系起来,好在我们已经预先了解过它们在电子学领域的含义:
  水平触发,因为已经是可读状态,所以它会一直触发,直到我们读完缓冲区,且系统缓冲区没有新的客户端发送的内容;边缘触发对应的是状态的变化,每次有新的客户端发送内容,都会设置可读状态,因此只会在这个时机触发;
  水平触发是 epoll 默认的触发模式,并且 libuv 中使用的也是水平触发。在了解了水平触发和边缘触发的区别后,我们其实就可以猜测 libuv 使用水平触发而不是边缘触发背后的考量:
  如果是边缘触发,在 epoll 的客观能力上,我们不被要求一次读取完缓冲区的内容(可以等到下一次客户端发送内容时继续读取)。但是实际业务中,客户端此时很可能在等待我们的响应(可以结合 HTTP 协议理解),而我们还在等待客户端的下一次写入,因此会陷入死锁的逻辑。由此一来,一次读取完缓冲区的内容几乎就成了边缘触发模式下的必选方式,这就不可避免的造成其他回调的等待时间变长,让 CPU 时间分配在各个回调之间显得不够均匀

5 局限性

  epoll 并不能够作用在所有的 IO 操作上,比如文件的读写操作,就无法享受到 epoll 的便利性,所以 libuv 的工作可以大致概括为:

  • 将各种操作系统上的类似 epoll 的系统调用(比如 Unix 上的 kqueue 和 Windows 上的 IOCP)抽象出统一的 API(内部 API);
  • 对于可以利用系统调用的 IO 操作,优先使用统一后的 API;
  • 对于不支持或者支持度不够的 IO 操作,使用线程池(Thread pool)的方式模拟出异步 API;
  • 最后,将上面的细节封装在内部,对外提供统一的 API。

三 回到 libuv

  回到 libuv,我们将以 event-loop 为主要脉络,结合上文提到的 epoll,以及下面将会介绍到的线程池,继续 libuv 在 Linux 上的实现细节一探究竟。

1 event-loop

  我们将结合源码来回顾一下 event-loop 基本概念
  下面这幅图也取自 libuv 官网,它描述了 event-loop 内部的工作:
在这里插入图片描述
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  

参考

Libuv 之 - 只看这篇是不够的
我应该跟libuv说声对不起,我错怪了libuv
网络库libevent、libev、libuv对比
libev调用epoll路径
网络开发库从libuv说到epoll
libuv线程池和主线程通信原理
libuv中文API手册,中文教程
libuv 源码分析1: loop和poll
有了 libevent、libev、libuv等库,C++的小伙伴还有人在手撸epoll吗
libuv
【libuv高效编程】libuv学习超详细教程8——libuv signal 信号句柄解读
网络开发库从libuv说到epoll

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用libuv时,你需要编写事件循环和处理回调函数。下面是一个简单的示例代码,展示了如何使用libuv来创建一个TCP服务器: ```c #include <stdio.h> #include <uv.h> void on_new_connection(uv_stream_t* server, int status) { if (status < 0) { fprintf(stderr, "New connection error: %s\n", uv_strerror(status)); return; } uv_tcp_t* client = (uv_tcp_t*) malloc(sizeof(uv_tcp_t)); uv_tcp_init(uv_default_loop(), client); if (uv_accept(server, (uv_stream_t*) client) == 0) { uv_read_start((uv_stream_t*) client, on_alloc_buffer, on_receive_data); } else { uv_close((uv_handle_t*) client, NULL); } } void on_alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { *buf = uv_buf_init((char*) malloc(suggested_size), suggested_size); } void on_receive_data(uv_stream_t* client, ssize_t nread, const uv_buf_t* buf) { if (nread > 0) { printf("Received data: %s\n", buf->base); } else if (nread < 0) { fprintf(stderr, "Read error: %s\n", uv_strerror(nread)); } free(buf->base); } int main() { uv_loop_t* loop = uv_default_loop(); uv_tcp_t server; uv_tcp_init(loop, &server); struct sockaddr_in addr; uv_ip4_addr("0.0.0.0", 8080, &addr); uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0); int r = uv_listen((uv_stream_t*)&server, 128, on_new_connection); if (r) { fprintf(stderr, "Listen error: %s\n", uv_strerror(r)); return 1; } return uv_run(loop, UV_RUN_DEFAULT); } ``` 这段代码创建了一个TCP服务器,监听本地的8080端口。当有新的连接建立时,`on_new_connection`回调函数被调用。在回调函数中,我们初始化一个新的uv_tcp_t结构体,并通过调用`uv_accept`来接受连接。如果接受成功,我们调用`uv_read_start`来开始读取客户端发送的数据。 在`on_alloc_buffer`回调函数中,我们分配内存来存储接收到的数据。在`on_receive_data`回调函数中,我们处理接收到的数据。 最后,在主函数中,我们初始化libuv的事件循环,创建并绑定TCP服务器,开始监听连接。最后,通过调用`uv_run`来启动事件循环。 这只是一个简单的示例,libuv还提供了许多其他功能,如异步文件操作、定时器等。你可以根据具体需求使用libuv来编写更复杂的异步I/O应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值