熟悉的陌生人
Node.js可能无人不知无人不晓,但Node.js底层实现libuv,恐怕认识的同学就不多了。
libuv是一个跨平台的高性能的异步I/O库,也是是一个网络I/O扩展库,是一个高性能事件驱动的程序库,封装了Windows和Unix平台一些底层特性,为开发者提供了统一的API。它主要用于异步I/O,并且随着时间的推移,被广泛用于其他系统,如Luvit
、pyuv
、Julia
等。可以处理大量并发连接,并且可以高效地利用系统资源,例如CPU和内存等。libuv提供了许多功能,例如TCP和UDP套接字、文件操作、DNS解析、线程池等。
重要的类型
uv_fs_t
uv_fs_t是libuv中用于文件系统操作的数据类型,它是一个结构体,用于存储文件系统操作的相关信息。libuv提供了许多文件系统操作的函数,例如uv_fs_open、uv_fs_read、uv_fs_write等,这些函数会接收一个uv_fs_t结构体作为参数,用于存储文件系统操作的状态和信息。
在进行文件系统操作时,libuv会将操作封装成一个任务,然后通过事件循环的方式来执行这个任务。当任务执行完成后,libuv会调用相应的回调函数来处理任务的结果。
一般情况下,libuv的文件系统操作是异步的,也就是说在执行文件系统操作时,程序不会阻塞等待操作完成,而是立即返回,然后在操作完成后调用相应的回调函数。这种异步操作的方式可以使得程序能够更高效地利用系统资源,提高程序的并发性能。回调函数设置为NULL就是同步调用。
uv_dirent_t
uv_dirent_t是libuv中用于表示目录条目的数据类型。它是一个结构体,用于存储目录中的文件或子目录的信息。
uv_dirent_t结构体中包含以下字段:
name
:目录条目的名称。type
:目录条目的类型,可以是UV_DIRENT_FILE、UV_DIRENT_DIR、UV_DIRENT_LINK等。size
:目录条目的大小,仅当type为UV_DIRENT_FILE时才有意义。time
:目录条目的修改时间。
使用uv_dirent_t结构体可以遍历目录中的所有文件和子目录,获取它们的信息并进行相应的处理。libuv提供了uv_fs_scandir函数,用于遍历目录并返回一个uv_dirent_t结构体的数组,可以通过处理这个数组来获取目录中的所有文件和子目录的信息。
uv_dir_t
uv_dir_t是libuv中用于表示目录的数据类型。它是一个结构体,用于存储目录的路径和状态信息。
uv_dir_t结构体中主要包含以下字段:
path
:目录的路径。ptr
:指向目录中第一个目录项的指针。flags
:目录的标志,如UV_DIR_CLOSING等。rdbuf
:目录项缓存区。rdbuf_size
:目录项缓存区的大小。count
:目录项的数量。fd
:目录的句柄。
使用uv_dir_t
结构体可以打开一个目录,并遍历其中的所有文件和子目录。libuv提供了uv_fs_opendir
函数,用于打开一个目录并返回一个uv_dir_t
结构体的指针。然后可以使用uv_dir_read
函数来读取目录中的下一个目录项,获取它们的信息并进行相应的处理。当遍历完目录中的所有文件和子目录后,需要使用uv_dir_close
函数来关闭目录。
开发方案
本文重点是libuv文件系统功能的实验,因为libuv源代码的编译不再描述。有兴起的同学可以参考libuv之64位编译方法和入门示例或使用vcpkg。vcpkg是微软C++团队开发的,适用于C++库的跨平台开源软件包管理器。它极大地简化了Windows、Linux和MacOS上第三方库的下载与配置操作,其优势在于自动下载开源库源代码、一键安装第三方库、源码包的缓存管理和版本管理,可以依需求安装指定的版本、自动检查库的依赖关系并安装其依赖项、无缝集成Visual Studio等。
遍历的实现逻辑
代码如下:
void read_dir(const std::string& zpath)
{
uv_fs_t req_readdir;
int iopen = uv_fs_opendir(NULL, &req_readdir, zpath.c_str(), NULL);
if (iopen != 0)
{
std::cerr << hue::red << "open: " << zpath << ", error code: " << iopen << hue::white_on_black << std::endl;
return;
}
uv_dirent_t dirent;
uv_dir_t* dir = (uv_dir_t*)req_readdir.ptr;
dir->dirents = &dirent;
dir->nentries = 1;
while (uv_fs_readdir(NULL, &req_readdir, dir, NULL))
{
if (dirent.type == UV_DIRENT_DIR)
{
std::string zsub_path = zpath + "\\" + dirent.name;
read_dir(zsub_path);
}
std::cout << "name: " << zpath << "\\" << dirent.name << ", type: " << dirent.type << std::endl;
}
uv_fs_closedir(NULL, &req_readdir, dir, NULL);
}
zpath
:目录的绝对路径,入口参数。此函数是一个递归函数,基本流程如下:
- 打开一个字符串表示的目上录。如失败则打印异常消息后返回。实现发现此类失败通常是失效的软链接。
uv_dirent_t
绑定,准备判断zpath
下级返回的文件是否为目录。- 循环读取目录。
- 如果
uv_dirent_t
告诉我们这是一个目录则拼接一个路径字符串,递归调用自己 - 打印路径信息。
- 如果
- 关闭
req_readdir
,释放资源。
测试方法
CMakeList.txt
cmake_minimum_required(VERSION 3.20)
project(qqserialserver)
set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$<$<CONFIG:Debug>:Debug>)
find_package(libuv CONFIG REQUIRED)
find_path(COLORCONSOLE_INCLUDE_DIRS "colorconsole.hpp")
add_executable(${PROJECT_NAME} main.cpp)
target_include_directories(${PROJECT_NAME} PRIVATE libuv::uv)
target_include_directories(${PROJECT_NAME} PRIVATE ${COLORCONSOLE_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} PRIVATE $<IF:$<TARGET_EXISTS:libuv::uv_a>,libuv::uv_a,libuv::uv>)
main函数
#include <uv.h>
#include <colorconsole.hpp>
#include <cassert>
uv_loop_t* g_loop;
int main(int argc, char** argv)
{
g_loop = uv_default_loop();
uv_run(g_loop, UV_RUN_DEFAULT);
read_dir("C:");
uv_loop_close(g_loop);
return 0;
}