3.文件系统
简单的文件系统读写是通过uv_fs_*函数和uv_fs_t结构实现的。
- 注意:libuv文件系统操作不同于套接字操作。套接字操作使用操作系统提供的非阻塞操作。文件系统操作在内部使用阻塞函数,但是在线程池中调用这些函数,并在需要应用程序交互时通知注册到事件循环中的监视者。
所有文件系统函数都有两种形式——同步和异步。
如果回调为空,同步表单会自动被调用(并阻塞)。函数的返回值是一个libuv错误代码。这通常只对同步调用有用。当传递回调且返回值为0时,将调用异步表单。
读/写文件
文件描述符可以使用
int uv_fs_open(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags, int mode, uv_fs_cb cb)
flags和mode是标准的Unix标志。libuv负责转换为适当的Windows标志。
文件描述符使用
int uv_fs_close(uv_loop_t* loop, uv_fs_t* req, uv_file file, uv_fs_cb cb)
文件系统操作回调具有如下特征:
void callback(uv_fs_t* req);
让我们看看cat的一个简单实现。我们首先注册一个回调函数,用于文件打开时:
uvcat/main.c - 打开一个文件
1 void on_open(uv_fs_t *req) {
2 // The request passed to the callback is the same as the one the call setup
3 // function was passed.
4 assert(req == &open_req);
5 if (req->result >= 0) {
6 iov = uv_buf_init(buffer, sizeof(buffer));
7 uv_fs_read(uv_default_loop(), &read_req, req->result,
8 &iov, 1, -1, on_read);
9 }
10 else {
11 fprintf(stderr, "error opening file: %s\n", uv_strerror((int)req->result));
12 }
13 }
uv_fs_t的结果字段是uv_fs_open回调时的文件描述符。如果文件成功打开,我们就开始读文件。
uvcat/main.c - 读回调
1 void on_read(uv_fs_t *req) {
2 if (req->result < 0) {
3 fprintf(stderr, "Read error: %s\n", uv_strerror(req->result));
4 }
5 else if (req->result == 0) {
6 uv_fs_t close_req;
7 // synchronous
8 uv_fs_close(uv_default_loop(), &close_req, open_req.result, NULL);
9 }
10 else if (req->result > 0) {
11 iov.len = req->result;
12 uv_fs_write(uv_default_loop(), &write_req, 1, &iov, 1, -1, on_write);
13 }
14 }
在read调用的情况下,你应该传递一个初始化的缓冲区,在read回调触发之前填充数据。uv_fs_*操作几乎直接映射到某些POSIX函数,因此在这种情况下,EOF通过result为0表示。在流或管道的情况下,UV_EOF常量将作为状态传递。
在这里您可以看到编写异步程序时的一个常见模式。uv_fs_close()调用同步执行。通常一次性的任务,或者作为启动或关闭阶段的一部分完成的任务是同步执行的,因为我们感兴趣的是快速I/O,当程序正在进行它的主要任务和处理多个I/O源。对于单独任务,性能差异通常可以忽略不计,并且可能导致更简单的代码。
使用uv_fs_write()编写文件系统也很简单。您的回调将在写入完成后触发。在我们的例子中,回调只是驱动下一个读取。因此,读和写是通过回调同步进行的。
uvcat/main.c - 写回调
1 void on_write(uv_fs_t *req) {
2 if (req->result < 0) {
3 fprintf(stderr, "Write error: %s\n", uv_strerror((int)req->result));
4 }
5 else {
6 uv_fs_read(uv_default_loop(), &read_req, open_req.result, &iov, 1, -1, on_read);
7 }
8 }
- 警告:由于文件系统和磁盘驱动器的性能配置方式,“成功”的写操作可能还没有提交到磁盘。
我们在main()中设置多米诺:
uvcat/main.c
1 int main(int argc, char **argv) {
2 uv_fs_open(uv_default_loop(), &open_req, argv[1], O_RDONLY, 0, on_open);
3 uv_run(uv_default_loop(), UV_RUN_DEFAULT);
4
5 uv_fs_req_cleanup(&open_req);
6 uv_fs_req_cleanup(&read_req);
7 uv_fs_req_cleanup(&write_req);
8 return 0;
9 }
- 警告:uv_fs_req_cleanup()函数必须在文件系统请求时被调用,以释放libuv中的内部内存分配。
文件系统操作
所有标准文件系统操作(如unlink、rmdir、stat)都是异步支持的,并且具有直观的参数顺序。它们遵循与读/写/打开调用相同的模式,在uv_fs_t中返回结果。结果字段。完整的列表:
文件系统操作列表
int uv_fs_close(uv_loop_t* loop, uv_fs_t* req, uv_file file, uv_fs_cb cb);
int uv_fs_open(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags, int mode, uv_fs_cb cb);
int 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);
int uv_fs_unlink(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb);
int 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);
int uv_fs_copyfile(uv_loop_t* loop, uv_fs_t* req, const char* path, const char* new_path, int flags, uv_fs_cb cb);
int uv_fs_mkdir(uv_loop_t* loop, uv_fs_t* req, const char* path, int mode, uv_fs_cb cb);
int uv_fs_mkdtemp(uv_loop_t* loop, uv_fs_t* req, const char* tpl, uv_fs_cb cb);
int uv_fs_rmdir(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb);
int uv_fs_scandir(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags, uv_fs_cb cb);
int uv_fs_scandir_next(uv_fs_t* req, uv_dirent_t* ent);
int uv_fs_opendir(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb);
int uv_fs_readdir(uv_loop_t* loop, uv_fs_t* req, uv_dir_t* dir, uv_fs_cb cb);
int uv_fs_closedir(uv_loop_t* loop, uv_fs_t* req, uv_dir_t* dir, uv_fs_cb cb);
int uv_fs_stat(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb);
int uv_fs_fstat(uv_loop_t* loop, uv_fs_t* req, uv_file file, uv_fs_cb cb);
int uv_fs_rename(uv_loop_t* loop, uv_fs_t* req, const char* path, const char* new_path, uv_fs_cb cb);
int uv_fs_fsync(uv_loop_t* loop, uv_fs_t* req, uv_file file, uv_fs_cb cb);
int uv_fs_fdatasync(uv_loop_t* loop, uv_fs_t* req, uv_file file, uv_fs_cb cb);
int uv_fs_ftruncate(uv_loop_t* loop, uv_fs_t* req, uv_file file, int64_t offset, uv_fs_cb cb);
int uv_fs_sendfile(uv_loop_t* loop, uv_fs_t* req, uv_file out_fd, uv_file in_fd, int64_t in_offset, size_t length, uv_fs_cb cb);
int uv_fs_access(uv_loop_t* loop, uv_fs_t* req, const char* path, int mode, uv_fs_cb cb);
int uv_fs_chmod(uv_loop_t* loop, uv_fs_t* req, const char* path, int mode, uv_fs_cb cb);
int uv_fs_utime(uv_loop_t* loop, uv_fs_t* req, const char* path, double atime, double mtime, uv_fs_cb cb);
int uv_fs_futime(uv_loop_t* loop, uv_fs_t* req, uv_file file, double atime, double mtime, uv_fs_cb cb);
int uv_fs_lstat(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb);
int uv_fs_link(uv_loop_t* loop, uv_fs_t* req, const char* path, const char* new_path, uv_fs_cb cb);
int uv_fs_symlink(uv_loop_t* loop, uv_fs_t* req, const char* path, const char* new_path, int flags, uv_fs_cb cb);
int uv_fs_readlink(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb);
int uv_fs_realpath(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb);
int uv_fs_fchmod(uv_loop_t* loop, uv_fs_t* req, uv_file file, int mode, uv_fs_cb cb);
int uv_fs_chown(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_uid_t uid, uv_gid_t gid, uv_fs_cb cb);
int uv_fs_fchown(uv_loop_t* loop, uv_fs_t* req, uv_file file, uv_uid_t uid, uv_gid_t gid, uv_fs_cb cb);
int uv_fs_lchown(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_uid_t uid, uv_gid_t gid, uv_fs_cb cb);
缓冲区和流
libuv中的基本I/O句柄是流(uv_stream_t)。TCP套接字、UDP套接字和用于文件I/O和IPC的管道都被视为流的子类。
流使用每个子类的自定义函数进行初始化,然后在使用时进行操作
int uv_read_start(uv_stream_t*, uv_alloc_cb alloc_cb, uv_read_cb read_cb);
int uv_read_stop(uv_stream_t*);
int uv_write(uv_write_t* req, uv_stream_t* handle,
const uv_buf_t bufs[], unsigned int nbufs, uv_write_cb cb);
基于流的函数比文件系统的函数更容易使用,当uv_read_start()被调用一次时,libuv会自动从流中读取数据,直到uv_read_stop()被调用。
数据的离散单元是缓冲区uv_buf_t。这只是一个指向字节(uv_buf_t.base)和长度(uv_buf_t.len)的指针集合。uv_buf_t是轻量级的,按值传递。真正需要管理的是实际的字节,这些字节必须由应用程序分配和释放。
- 错误:这个程序并不总是有效,需要更好的**指针
为了演示流,我们需要使用uv_pipe_t。这允许流本地文件。下面是一个使用libuv的简单tee实用程序。异步执行所有操作显示了事件I/O的强大功能。这两次写操作不会互相阻塞,但我们必须小心地复制缓冲区数据,以确保在缓冲区被写完之前不会释放缓冲区。
该程序将按如下方式执行:
./uvtee <output_file>
我们开始在需要的文件上打开管道。默认情况下,指向文件的Libuv管道是双向打开的。
uvtee/main.c - 用pipes读取
1 int main(int argc, char **argv) {
2 loop = uv_default_loop();
3
4 uv_pipe_init(loop, &stdin_pipe, 0);
5 uv_pipe_open(&stdin_pipe, 0);
6
7 uv_pipe_init(loop, &stdout_pipe, 0);
8 uv_pipe_open(&stdout_pipe, 1);
9
10 uv_fs_t file_req;
11 int fd = uv_fs_open(loop, &file_req, argv[1], O_CREAT | O_RDWR, 0644, NULL);
12 uv_pipe_init(loop, &file_pipe, 0);
13 uv_pipe_open(&file_pipe, fd);
14
15 uv_read_start((uv_stream_t*)&stdin_pipe, alloc_buffer, read_stdin);
16
17 uv_run(loop, UV_RUN_DEFAULT);
18 return 0;
19 }
对于使用命名管道的IPC, uv_pipe_init()的第三个参数应该设置为1。这在Processes中有介绍。uv_pipe_open()调用将管道与文件描述符关联起来,在本例中为0(标准输入)。
我们开始监控stdin。当需要新的缓冲区来保存传入数据时,调用alloc_buffer回调。Read_stdin将被这些缓冲区调用。
uvtee/main.c - 读取缓冲区
1 void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
2 *buf = uv_buf_init((char*) malloc(suggested_size), suggested_size);
3 }
4
5 void read_stdin(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
6 if (nread < 0){
7 if (nread == UV_EOF){
8 // end of file
9 uv_close((uv_handle_t *)&stdin_pipe, NULL);
10 uv_close((uv_handle_t *)&stdout_pipe, NULL);
11 uv_close((uv_handle_t *)&file_pipe, NULL);
12 }
13 } else if (nread > 0) {
14 write_data((uv_stream_t *)&stdout_pipe, nread, *buf, on_stdout_write);
15 write_data((uv_stream_t *)&file_pipe, nread, *buf, on_file_write);
16 }
17
18 // OK to free buffer as write_data copies it.
19 if (buf->base)
20 free(buf->base);
21 }
这里使用标准的malloc就足够了,但是您可以使用任何内存分配方案。例如,node.js使用自己的slab分配器将缓冲区与V8对象关联起来。
对于任何错误,读回调nread参数都小于0。这个错误可能是EOF,在这种情况下,我们关闭所有流,使用泛型关闭函数uv_close(),该函数根据其内部类型处理句柄。否则,nread是非负数,我们可以尝试向输出流写入那么多字节。最后记住,缓冲区的分配和回收是应用程序的责任,所以我们释放数据。
如果分配内存失败,分配回调可能返回长度为0的缓冲区。在本例中,read回调调用时出错UV_ENOBUFS。Libuv将继续尝试读取流,所以如果你想在分配失败时停止,你必须显式地调用uv_close()。
可以在nread = 0的情况下调用read回调,这表示此时没有什么可读的。大多数应用程序将忽略这一点。
uvtee/main.c - 写入pipe
1 typedef struct {
2 uv_write_t req;
3 uv_buf_t buf;
4 } write_req_t;
5
6 void free_write_req(uv_write_t *req) {
7 write_req_t *wr = (write_req_t*) req;
8 free(wr->buf.base);
9 free(wr);
10 }
11
12 void on_stdout_write(uv_write_t *req, int status) {
13 free_write_req(req);
14 }
15
16 void on_file_write(uv_write_t *req, int status) {
17 free_write_req(req);
18 }
19
20 void write_data(uv_stream_t *dest, size_t size, uv_buf_t buf, uv_write_cb cb) {
21 write_req_t *req = (write_req_t*) malloc(sizeof(write_req_t));
22 req->buf = uv_buf_init((char*) malloc(size), size);
23 memcpy(req->buf.base, buf.base, size);
24 uv_write((uv_write_t*) req, (uv_stream_t*)dest, &req->buf, 1, cb);
25 }
Write_data()复制从读获得的缓冲区。这个缓冲区不会传递给写完成时触发的写回调。为了解决这个问题,我们在write_req_t中包装一个写请求和一个缓冲区,并在回调中展开它。我们创建一个副本,这样就可以从对write_data的两次调用中彼此独立地释放两个缓冲区。虽然这样的演示程序是可以接受的,但是您可能需要更智能的内存管理,比如在任何主要应用程序中引用计数缓冲区或缓冲区池。
- 警告:如果您的程序打算与其他程序一起使用,那么它可能有意或无意地正在写入管道。这使得它很容易在接收SIGPIPE时中止。插入这句话是一个好主意:
signal(SIGPIPE, SIG_IGN)
在应用程序的初始化阶段。
文件变化事件
所有现代操作系统都提供了api来将监视放在单独的文件或目录上,并在文件被修改时得到通知。Libuv封装了常用的文件更改通知库1。这是libuv中比较不一致的部分之一。文件更改通知系统本身在不同的平台上差异很大,所以让所有内容在任何地方工作都很困难。为了演示,我将构建一个简单的实用程序,它在任何被监视的文件发生变化时运行一个命令:
./onchange <command> <file1> [file2] ...
使用uv_fs_event_init()启动文件更改通知:
onchange/main.c - 设置
1 int main(int argc, char **argv) {
2 if (argc <= 2) {
3 fprintf(stderr, "Usage: %s <command> <file1> [file2 ...]\n", argv[0]);
4 return 1;
5 }
6
7 loop = uv_default_loop();
8 command = argv[1];
9
10 while (argc-- > 2) {
11 fprintf(stderr, "Adding watch on %s\n", argv[argc]);
12 uv_fs_event_t *fs_event_req = malloc(sizeof(uv_fs_event_t));
13 uv_fs_event_init(loop, fs_event_req);
14 // The recursive flag watches subdirectories too.
15 uv_fs_event_start(fs_event_req, run_command, argv[argc], UV_FS_EVENT_RECURSIVE);
16 }
17
18 return uv_run(loop, UV_RUN_DEFAULT);
19 }
第三个参数是要监视的实际文件或目录。最后一个参数flags可以是:
/*
* Flags to be passed to uv_fs_event_start().
*/
enum uv_fs_event_flags {
UV_FS_EVENT_WATCH_ENTRY = 1,
UV_FS_EVENT_STAT = 2,
UV_FS_EVENT_RECURSIVE = 4
};
UV_FS_EVENT_WATCH_ENTRY和UV_FS_EVENT_STAT不做任何事情(还)。UV_FS_EVENT_RECURSIVE也将开始监视受支持平台上的子目录。
回调函数将接收以下参数:
- uv_fs_event_t *handle -句柄。句柄的路径字段是手表设置的文件。
- const char *filename -如果一个目录正在被监视,这是被更改的文件。仅在Linux和Windows上为非空。甚至在这些平台上也可能是无效的。
- int标志——UV_RENAME或UV_CHANGE中的一个,或者两者的位或。
- int status -当前为0。
在我们的示例中,我们只是打印参数并使用system()运行命令。
onchange/main.c - 文件更改通知回调
1 void run_command(uv_fs_event_t *handle, const char *filename, int events, int status) {
2 char path[1024];
3 size_t size = 1023;
4 // Does not handle error if path is longer than 1023.
5 uv_fs_event_getpath(handle, path, &size);
6 path[size] = '\0';
7
8 fprintf(stderr, "Change detected in %s: ", path);
9 if (events & UV_RENAME)
10 fprintf(stderr, "renamed");
11 if (events & UV_CHANGE)
12 fprintf(stderr, "changed");
13
14 fprintf(stderr, " %s\n", filename ? filename : "");
15 system(command);
16 }

浙公网安备 33010602011771号