跳转至

文件系统

在 Libuv 中,文件读写等操作是通过 文件系统(File System,简称 FS)API 实现的。Libuv 提供了异步的文件 I/O 操作,能够高效地处理文件读写、文件属性获取、目录操作等任务。

Libuv 的文件系统操作与套接字操作不同。套接字操作使用的是操作系统提供的非阻塞操作,而文件系统操作在内部使用的是阻塞函数(操作系统提供的文件读写函数是阻塞的),这些函数是在线程池中调用的,并且在需要与应用程序交互时通知注册到事件循环的观察者,从而实现非阻塞调用。

核心 API

Libuv 的文件系统 API 主要包括以下函数:

打开文件 uv_fs_open

异步打开文件。

void uv_fs_open(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags, int mode, uv_fs_cb cb);

参数说明:

  • loop:事件循环对象。
  • req:文件系统请求对象。
  • path:文件路径。
  • flags:打开文件的标志(如 O_RDONLYO_WRONLYO_RDWR 等)。
  • mode:文件权限(如 0644)。
  • cb:回调函数,操作完成后调用。

读取文件 uv_fs_read

异步读取文件。

void uv_fs_read(uv_loop_t* loop, uv_fs_t* req, uv_file file, const uv_buf_t bufs[], unsigned int nbufs, int64_t offset, uv_fs_cb cb);

参数说明:

  • loop:事件循环对象。
  • req:文件系统请求对象。
  • file:文件描述符(由 uv_fs_open 返回)。
  • bufs:缓冲区数组。
  • nbufs:缓冲区数量。
  • offset:读取的起始偏移量(-1 表示从当前位置读取)。
  • cb:回调函数。

写入文件 uv_fs_write

异步写入文件。

void uv_fs_write(uv_loop_t* loop, uv_fs_t* req, uv_file file, const uv_buf_t bufs[], unsigned int nbufs, int64_t offset, uv_fs_cb cb);

参数说明:

  • loop:事件循环对象。
  • req:文件系统请求对象。
  • file:文件描述符。
  • bufs:缓冲区数组。
  • nbufs:缓冲区数量。
  • offset:写入的起始偏移量(-1 表示从当前位置写入)。
  • cb:回调函数。

关闭文件 uv_fs_close

异步关闭文件。

void uv_fs_close(uv_loop_t* loop, uv_fs_t* req, uv_file file, uv_fs_cb cb);

参数说明:

  • loop:事件循环说明。
  • req:文件系统请求对象。
  • file:文件描述符。
  • cb:回调函数。

其他常用 API

  • uv_fs_stat:获取文件属性。
  • uv_fs_unlink:删除文件。
  • uv_fs_mkdir:创建目录。
  • uv_fs_readdir:读取目录内容。
  • uv_fs_rename:重命名文件或目录。

Buffer(缓冲区)和 Stream(流)

Libuv 中 Buffer 是用来存储数据的内存区域,通常以 uv_buf_t 类型表示:

typedef struct uv_buf_t {
  char* base; // 指向数据缓冲区的指针
  size_t len; // 缓冲区的大小
} uv_buf_t;

创建 uv_buf_t 的方法一般包括动态分配和静态分配:

  • 静态分配(适合小数据块):

    char buffer[1024];
    uv_buf_t buf = uv_buf_init(buffer, sizeof(buffer)); // uv_buf_init 是一个辅助函数,用于快速初始化 uv_buf_t
    
  • 动态分配(适合大数据块):

    char* dynamic_buffer = malloc(1024);
    uv_buf_t buf = uv_buf_init(dynamic_buffer, 1024);
    

Libuv 中 Buffer 通常配合 Stream 使用,Stream 是 Libuv 中用于表示I/O操作的抽象,uv_stream_t 是所有流类型的基类,包括TCP套接字(uv_tcp_t)、UDP套接字(uv_udp_t)和管道(uv_pipe_t)等。流操作主要包括:

  • 开始读取:uv_read_start,用于启动异步读取操作。
  • 停止读取:uv_read_stop,用于停止读取操作。
  • 写入数据:uv_write,用于将数据写入流。

uv_read_start

启动异步读取操作。当有数据可读时,会调用指定的回调函数。

int uv_read_start(uv_stream_t* stream, uv_alloc_cb alloc_cb, uv_read_cb read_cb);

参数说明:

  • uv_stream_t* stream:要进行读取操作的流对象。
  • uv_alloc_cb alloc_cb:分配缓冲区的回调函数。当有数据可读时,libuv 会调用此回调函数来分配一个缓冲区用于存储读取的数据。
  • 回调函数原型:

    ```c
    void alloc_cb(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf);
    ```
    
    • handle:当前流的句柄。
    • suggested_size:建议的缓冲区大小。
    • buf:指向 uv_buf_t 的指针,用于存储分配的缓冲区。
  • uv_read_cb read_cb:读取数据的回调函数。当读取操作完成时,libuv 会调用此回调函数。

  • 回调函数原型:

    ```c
    void read_cb(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf);
    ```
    
    • stream:当前流对象。
    • nread:实际读取的字节数。如果为负值,表示读取失败或到达文件末尾(UV_EOF)。
    • buf:指向 uv_buf_t 的指针,包含读取的数据。

当运行成功时返回 0,失败时返回错误码(负值)。

uv_read_stop

停止当前流的读取操作。如果流正在读取数据,调用此函数会立即停止读取。

int uv_read_stop(uv_stream_t* stream);

参数说明:

  • uv_stream_t* stream:要停止读取操作的流对象。

当运行成功时返回 0,失败时返回错误码(负值)。

uv_write

将数据写入流。这是一个异步操作,当写入完成时会调用指定的回调函数。

int uv_write(uv_write_t* req, uv_stream_t* stream, const uv_buf_t bufs[], unsigned int nbufs, uv_write_cb cb);

参数说明:

  • uv_write_t* req:写入请求对象,用于跟踪写入操作的状态。
  • uv_stream_t* stream:要写入数据的流对象。
  • const uv_buf_t bufs[]:包含要写入数据的缓冲区数组。
  • unsigned int nbufs:缓冲区数组的大小。
  • uv_write_cb cb:写入完成的回调函数。当写入操作完成时,libuv 会调用此回调函数。
  • 回调函数原型:

    ```c
    void write_cb(uv_write_t* req, int status);
    ```
    
    • req:写入请求对象。
    • status:写入操作的状态。如果为 0,表示成功;如果为负值,表示失败。

当运行成功时返回 0。失败时返回错误码(负值)。

使用示例

简单异步读写操作

以下是一个完整的示例,展示了如何使用 Libuv 异步读取文件并写入到另一个文件:

#include <stdio.h>
#include <stdlib.h>
#include <uv.h>

void on_read(uv_fs_t* req);
void on_write(uv_fs_t* req);

uv_fs_t open_req;
uv_fs_t read_req;
uv_fs_t write_req;
uv_fs_t close_req;

uv_buf_t iov;
char buffer[1024];

void on_open(uv_fs_t* req) {
  if (req->result < 0) {
    fprintf(stderr, "Error opening file: %s\n", uv_strerror(req->result));
    return;
  }
  printf("File opened successfully.\n");

  // 读取文件
  iov = uv_buf_init(buffer, sizeof(buffer));
  uv_fs_read(uv_default_loop(), &read_req, req->result, &iov, 1, -1, on_read);
}

void on_read(uv_fs_t* req) {
  if (req->result < 0) {
    fprintf(stderr, "Error reading file: %s\n", uv_strerror(req->result));
    return;
  }
  printf("File read successfully.\n");

  // 关闭读取的文件
  uv_fs_close(uv_default_loop(), &close_req, open_req.result, NULL);

  // 打开目标文件
  uv_fs_open(uv_default_loop(), &open_req, "output.txt", O_WRONLY | O_CREAT,
             0644, on_write);
}

void on_write(uv_fs_t* req) {
  if (req->result < 0) {
    fprintf(stderr, "Error opening output file: %s\n",
            uv_strerror(req->result));
    return;
  }
  printf("Output file opened successfully.\n");

  // 写入数据
  uv_fs_write(uv_default_loop(), &write_req, req->result, &iov, 1, -1, NULL);
  printf("Data written to output file.\n");

  // 关闭目标文件
  uv_fs_close(uv_default_loop(), &close_req, req->result, NULL);
}

int main() {
  // 初始化事件循环
  uv_loop_t* loop = uv_default_loop();

  // 打开文件
  uv_fs_open(loop, &open_req, "input.txt", O_RDONLY, 0, on_open);

  // 运行事件循环
  uv_run(loop, UV_RUN_DEFAULT);

  // 清理请求
  uv_fs_req_cleanup(&open_req);
  uv_fs_req_cleanup(&read_req);
  uv_fs_req_cleanup(&write_req);
  uv_fs_req_cleanup(&close_req);

  return 0;
}