#Filesystem
簡單的文件讀寫是通過```uv_fs_*```函數族和與之相關的```uv_fs_t```結構體完成的.
####note
>libuv 提供的文件操作和 socket operations 并不相同. 套接字操作使用了操作系統本身提供了非阻塞操作, 而文件操作內部使用了阻塞函數, 但是 libuv 是在線程池中調用這些函數, 并在應用程序需要交互時通知在事件循環中注冊的監視器.
所有的文件操作函數都有兩種形式 - 同步 synchronous 和 異步 asynchronous.
同步 synchronous 形式如果沒有指定回調函數則會被自動調用( 并阻塞的), 函數的返回值是```libuv error code```. 但以上通常只對同步調用有意義.
而異步 asynchronous 形式則會在傳入回調函數時被調用, 并且返回 0.
##Reading/Writing files
文件描述符可以采用如下方式獲得:
```c
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 flags 相同. libuv 會小心地處理 Windows 環境下的相關標志位(flags)的轉換, 所以編寫跨平臺程序時你不用擔心不同平臺上文件打開的標志位不同。
關閉文件描述符可以使用:
```c
int uv_fs_close(uv_loop_t* loop, uv_fs_t* req, uv_file file, uv_fs_cb cb)
```
文件系統的回調函數有如下的形式:
```c
void callback(uv_fs_t* req);
```
讓我們看一下一個簡單的```cat```命令的實現。我們通過注冊一個當文件被打開時被調用的回調函數來開始:
####uvcat/main.c - opening a file
```c
// The request passed to the callback is the same as the one the call setup
// function was passed.
assert(req == &open_req);
if (req->result >= 0) {
iov = uv_buf_init(buffer, sizeof(buffer));
uv_fs_read(uv_default_loop(), &read_req, req->result,
&iov, 1, -1, on_read);
}
else {
fprintf(stderr, "error opening file: %s\n", uv_strerror((int)req->result));
}
}
```
`uv_fs_t`的`result`域保存了`uv_fs_open`回調函數打開的文件描述符。如果文件被正確地打開,我們可以開始讀取了:
```c
void on_read(uv_fs_t *req) {
if (req->result < 0) {
fprintf(stderr, "Read error: %s\n", uv_strerror(req->result));
}
else if (req->result == 0) {
uv_fs_t close_req;
// synchronous
uv_fs_close(uv_default_loop(), &close_req, open_req.result, NULL);
}
else if (req->result > 0) {
iov.len = req->result;
uv_fs_write(uv_default_loop(), &write_req, 1, &iov, 1, -1, on_write);
}
}
```
在調用讀取函數的時候,你必須傳遞一個已經初始化的緩沖區,在```on_read()```被觸發后,緩沖區被被寫入數據。```uv_fs_*```系列的函數是和POSIX的函數對應的,所以當讀到文件的末尾時(EOF),result返回0。在使用streams或者pipe的情況下,使用的是libuv自定義的```UV_EOF```。
現在你看到類似的異步編程的模式。但是```uv_fs_close()```是同步的,一般來說,一次性的,開始的或者關閉的部分,都是同步的,因為我們一般關心的主要是任務和多路I/O的快速I/O。所以在這些對性能微不足道的地方,都是使用同步的,這樣代碼還會簡單一些。
文件系統的寫入使用 ```uv_fs_write()```,當寫入完成時會觸發回調函數,在這個例子中回調函數會觸發下一次的讀取。
####uvcat/main.c - write callback
```c
void on_write(uv_fs_t *req) {
if (req->result < 0) {
fprintf(stderr, "Write error: %s\n", uv_strerror((int)req->result));
}
else {
uv_fs_read(uv_default_loop(), &read_req, open_req.result, &iov, 1, -1, on_read);
}
}
```
#####Warning
>由于文件系統和磁盤的調度策略,寫入成功的數據不一定就存在磁盤上。
我們開始在main中推動多米諾骨牌:
####uvcat/main.c
```c
int main(int argc, char **argv) {
uv_fs_open(uv_default_loop(), &open_req, argv[1], O_RDONLY, 0, on_open);
uv_run(uv_default_loop(), UV_RUN_DEFAULT);
uv_fs_req_cleanup(&open_req);
uv_fs_req_cleanup(&read_req);
uv_fs_req_cleanup(&write_req);
return 0;
}
```
#####Warning
>函數uv_fs_req_cleanup()在文件系統操作結束后必須要被調用,用來回收在讀寫中分配的內存。
##Filesystem operations
所有像 ``unlink``, ``rmdir``, ``stat`` 這樣的標準文件操作都是支持異步的,并且使用方法和上述類似。下面的各個函數的使用方法和read/write/open類似,在``uv_fs_t.result``中保存返回值.所有的函數如下所示:
(譯者注:返回的result值,<0表示出錯,其他值表示成功。但>=0的值在不同的函數中表示的意義不一樣,比如在```uv_fs_read```或者```uv_fs_write```中,它代表讀取或寫入的數據總量,但在```uv_fs_open```中表示打開的文件描述符.)
```c
UV_EXTERN int uv_fs_close(uv_loop_t* loop,
uv_fs_t* req,
uv_file file,
uv_fs_cb cb);
UV_EXTERN int uv_fs_open(uv_loop_t* loop,
uv_fs_t* req,
const char* path,
int flags,
int mode,
uv_fs_cb cb);
UV_EXTERN 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);
UV_EXTERN int uv_fs_unlink(uv_loop_t* loop,
uv_fs_t* req,
const char* path,
uv_fs_cb cb);
UV_EXTERN 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);
UV_EXTERN int uv_fs_mkdir(uv_loop_t* loop,
uv_fs_t* req,
const char* path,
int mode,
uv_fs_cb cb);
UV_EXTERN int uv_fs_mkdtemp(uv_loop_t* loop,
uv_fs_t* req,
const char* tpl,
uv_fs_cb cb);
UV_EXTERN int uv_fs_rmdir(uv_loop_t* loop,
uv_fs_t* req,
const char* path,
uv_fs_cb cb);
UV_EXTERN int uv_fs_scandir(uv_loop_t* loop,
uv_fs_t* req,
const char* path,
int flags,
uv_fs_cb cb);
UV_EXTERN int uv_fs_scandir_next(uv_fs_t* req,
uv_dirent_t* ent);
UV_EXTERN int uv_fs_stat(uv_loop_t* loop,
uv_fs_t* req,
const char* path,
uv_fs_cb cb);
UV_EXTERN int uv_fs_fstat(uv_loop_t* loop,
uv_fs_t* req,
uv_file file,
uv_fs_cb cb);
UV_EXTERN int uv_fs_rename(uv_loop_t* loop,
uv_fs_t* req,
const char* path,
const char* new_path,
uv_fs_cb cb);
UV_EXTERN int uv_fs_fsync(uv_loop_t* loop,
uv_fs_t* req,
uv_file file,
uv_fs_cb cb);
UV_EXTERN int uv_fs_fdatasync(uv_loop_t* loop,
uv_fs_t* req,
uv_file file,
uv_fs_cb cb);
UV_EXTERN int uv_fs_ftruncate(uv_loop_t* loop,
uv_fs_t* req,
uv_file file,
int64_t offset,
uv_fs_cb cb);
UV_EXTERN 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);
UV_EXTERN int uv_fs_access(uv_loop_t* loop,
uv_fs_t* req,
const char* path,
int mode,
uv_fs_cb cb);
UV_EXTERN int uv_fs_chmod(uv_loop_t* loop,
uv_fs_t* req,
const char* path,
int mode,
uv_fs_cb cb);
UV_EXTERN int uv_fs_utime(uv_loop_t* loop,
uv_fs_t* req,
const char* path,
double atime,
double mtime,
uv_fs_cb cb);
UV_EXTERN int uv_fs_futime(uv_loop_t* loop,
uv_fs_t* req,
uv_file file,
double atime,
double mtime,
uv_fs_cb cb);
UV_EXTERN int uv_fs_lstat(uv_loop_t* loop,
uv_fs_t* req,
const char* path,
uv_fs_cb cb);
UV_EXTERN int uv_fs_link(uv_loop_t* loop,
uv_fs_t* req,
const char* path,
const char* new_path,
uv_fs_cb cb);
```
##Buffers and Streams
在libuv中,最基礎的I/O操作是流stream(``uv_stream_t``)。TCP套接字,UDP套接字,管道對于文件I/O和IPC來說,都可以看成是流stream(``uv_stream_t``)的子類.
上面提到的各個流的子類都有各自的初始化函數,然后可以使用下面的函數操作:
```c
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()``被調用。
數據的離散單元是buffer-``uv_buffer_t``。它包含了指向數據的開始地址的指針(``uv_buf_t.base``)和buffer的長度(``uv_buf_t.len``)這兩個信息。``uv_buf_t``很輕量級,使用值傳遞。我們需要管理的只是實際的數據,即程序必須自己分配和回收內存。
.. ERROR::
THIS PROGRAM DOES NOT ALWAYS WORK, NEED SOMETHING BETTER**
為了更好地演示流stream,我們將會使用``uv_pipe_t``。它可以將本地文件轉換為流(stream)的形態。接下來的這個是使用libuv實現的,一個簡單的T型工具(如果不是很了解,請看[維基百科](https://en.wikipedia.org/wiki/Tee_(command)))。所有的操作都是異步的,這也正是事件驅動I/O的威力所在。兩個輸出操作不會相互阻塞,但是我們也必須要注意,確保一塊緩沖區不會在還沒有寫入之前,就提前被回收了。
這個程序執行命令如下
```
./uvtee <output_file>
```
在使用pipe打開文件時,libuv會默認地以可讀和可寫的方式打開文件。
####uvtee/main.c - read on pipes
```c
int main(int argc, char **argv) {
loop = uv_default_loop();
uv_pipe_init(loop, &stdin_pipe, 0);
uv_pipe_open(&stdin_pipe, 0);
uv_pipe_init(loop, &stdout_pipe, 0);
uv_pipe_open(&stdout_pipe, 1);
uv_fs_t file_req;
int fd = uv_fs_open(loop, &file_req, argv[1], O_CREAT | O_RDWR, 0644, NULL);
uv_pipe_init(loop, &file_pipe, 0);
uv_pipe_open(&file_pipe, fd);
uv_read_start((uv_stream_t*)&stdin_pipe, alloc_buffer, read_stdin);
uv_run(loop, UV_RUN_DEFAULT);
return 0;
}
```
當需要使用IPC的命名管道的時候(*無名管道是Unix最初的IPC形式,但是由于無名管道的局限性,后來出現了有名管道FIFO,這種管道由于可以在文件系統中創建一個名字,所以可以被沒有親緣關系的進程訪問*),``uv_pipe_init()``的第三個參數應該被設置為1。這部分會在Process進程的這一章節說明。``uv_pipe_open()``函數把管道和文件描述符關聯起來,在上面的代碼中表示把管道``stdin_pipe``和標準輸入關聯起來(譯者注:``0``代表標準輸入,``1``代表標準輸出,``2``代表標準錯誤輸出)。
當調用``uv_read_start()``后,我們開始監聽``stdin``,當需要新的緩沖區來存儲數據時,調用alloc_buffer,在函數``read_stdin()``中可以定義緩沖區中的數據處理操作。
####uvtee/main.c - reading buffers
```c
void 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 read_stdin(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
if (nread < 0){
if (nread == UV_EOF){
// end of file
uv_close((uv_handle_t *)&stdin_pipe, NULL);
uv_close((uv_handle_t *)&stdout_pipe, NULL);
uv_close((uv_handle_t *)&file_pipe, NULL);
}
} else if (nread > 0) {
write_data((uv_stream_t *)&stdout_pipe, nread, *buf, on_stdout_write);
write_data((uv_stream_t *)&file_pipe, nread, *buf, on_file_write);
}
if (buf->base)
free(buf->base);
}
```
標準的``malloc``是非常高效的方法,但是你依然可以使用其它的內存分配的策略。比如,nodejs使用自己的內存分配方法(```Smalloc```),它將buffer用v8的對象關聯起來,具體的可以查看[nodejs的官方文檔](https://nodejs.org/docs/v0.11.5/api/smalloc.html)。
當回調函數```read_stdin()```的nread參數小于0時,表示錯誤發生了。其中一種可能的錯誤是EOF(**讀到文件的尾部**),這時我們可以使用函數```uv_close()```關閉流了。除此之外,當nread大于0時,nread代表我們可以向輸出流中寫入的字節數目。最后注意,緩沖區要由我們手動回收。
當分配函數```alloc_buf()```返回一個長度為0的緩沖區時,代表它分配內存失敗。在這種情況下,讀取的回調函數會被錯誤```UV_ENOBUFS```喚醒。libuv同時也會繼續嘗試從流中讀取數據,所以如果你想要停止的話,必須明確地調用```uv_close()```.
當nread為0時,代表已經沒有可讀的了,大多數的程序會自動忽略這個。
####uvtee/main.c - Write to pipe
```c
typedef struct {
uv_write_t req;
uv_buf_t buf;
} write_req_t;
void free_write_req(uv_write_t *req) {
write_req_t *wr = (write_req_t*) req;
free(wr->buf.base);
free(wr);
}
void on_stdout_write(uv_write_t *req, int status) {
free_write_req(req);
}
void on_file_write(uv_write_t *req, int status) {
free_write_req(req);
}
void write_data(uv_stream_t *dest, size_t size, uv_buf_t buf, uv_write_cb cb) {
write_req_t *req = (write_req_t*) malloc(sizeof(write_req_t));
req->buf = uv_buf_init((char*) malloc(size), size);
memcpy(req->buf.base, buf.base, size);
uv_write((uv_write_t*) req, (uv_stream_t*)dest, &req->buf, 1, cb);
}
```
`write_data()`開辟了一塊地址空間存儲從緩沖區讀取出來的數據,這塊緩存不會被釋放,直到與``uv_write()``綁定的回調函數執行.為了實現它,我們用結構體``write_req_t``包裹一個write request和一個buffer,然后在回調函數中展開它。因為我們復制了一份緩存,所以我們可以在兩個``write_data()``中獨立釋放兩個緩存。 我們之所以這樣做是因為,兩個調用`write_data()`是相互獨立的。為了保證它們不會因為讀取速度的原因,由于共享一片緩沖區而損失掉獨立性,所以才開辟了新的兩塊區域。當然這只是一個簡單的例子,你可以使用更聰明的內存管理方法來實現它,比如引用計數或者緩沖區池等。
#####WARNING
>你的程序在被其他的程序調用的過程中,有意無意地會向pipe寫入數據,這樣的話它會很容易被信號SIGPIPE終止掉,你最好在初始化程序的時候加入這句:
`signal(SIGPIPE, SIG_IGN)`。
##File change events
所有的現代操作系統都會提供相應的API來監視文件和文件夾的變化(**如Linux的inotify,Darwin的FSEvents,BSD的kqueue,Windows的ReadDirectoryChangesW, Solaris的event ports**)。libuv同樣包括了這樣的文件監視庫。這是libuv中很不協調的部分,因為在跨平臺的前提上,實現這個功能很難。為了更好地說明,我們現在來寫一個監視文件變化的命令:
```
./onchange <command> <file1> [file2] ...
```
實現這個監視器,要從```uv_fs_event_init()```開始:
####onchange/main.c - The setup
```c
int main(int argc, char **argv) {
if (argc <= 2) {
fprintf(stderr, "Usage: %s <command> <file1> [file2 ...]\n", argv[0]);
return 1;
}
loop = uv_default_loop();
command = argv[1];
while (argc-- > 2) {
fprintf(stderr, "Adding watch on %s\n", argv[argc]);
uv_fs_event_t *fs_event_req = malloc(sizeof(uv_fs_event_t));
uv_fs_event_init(loop, fs_event_req);
// The recursive flag watches subdirectories too.
uv_fs_event_start(fs_event_req, run_command, argv[argc], UV_FS_EVENT_RECURSIVE);
}
return uv_run(loop, UV_RUN_DEFAULT);
}
```
函數```uv_fs_event_start()```的第三個參數是要監視的文件或文件夾。最后一個參數,```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`可以在支持的系統平臺上遞歸地監視子文件夾。
在回調函數`run_command()`中,接收的參數如下:
>1.`uv_fs_event_t *handle`-句柄。里面的path保存了發生改變的文件的地址。
>2.`const char *filename`-如果目錄被監視,它代表發生改變的文件名。只在Linux和Windows上不為null,在其他平臺上可能為null。
>3.`int flags` -`UV_RENAME`名字改變,`UV_CHANGE`內容改變之一,或者他們兩者的按位或的結果(`|`)。
>4.`int status`-當前為0.
在我們的例子中,只是簡單地打印參數和調用`system()`運行command.
####onchange/main.c - file change notification callback
```c
void run_command(uv_fs_event_t *handle, const char *filename, int events, int status) {
char path[1024];
size_t size = 1023;
// Does not handle error if path is longer than 1023.
uv_fs_event_getpath(handle, path, &size);
path[size] = '\0';
fprintf(stderr, "Change detected in %s: ", path);
if (events & UV_RENAME)
fprintf(stderr, "renamed");
if (events & UV_CHANGE)
fprintf(stderr, "changed");
fprintf(stderr, " %s\n", filename ? filename : "");
system(command);
}
```
----