#Basics of libuv
libuv強制使用異步的,事件驅動的編程風格。它的核心工作是提供一個event-loop,還有基于I/O和其它事件通知的回調函數。libuv還提供了一些核心工具,例如定時器,非阻塞的網絡支持,異步文件系統訪問,子進程等。
##Event loops
在事件驅動編程中,程序會關注每一個事件,并且對每一個事件的發生做出反應。libuv會負責將來自操作系統的事件收集起來,或者監視其他來源的事件。這樣,用戶就可以注冊回調函數,回調函數會在事件發生的時候被調用。event-loop會一直保持運行狀態。用偽代碼描述如下:
```c
while there are still events to process:
e = get the next event
if there is a callback associated with e:
call the callback
```
舉幾個事件的例子:
>* 準備好被寫入的文件。
>* 包含準備被讀取的數據的socket。
>* 超時的定時器。
event-loop最終會被`uv_run()`啟動-當使用libuv時,最后都會調用的函數。
系統編程中最經常處理的一般是輸入和輸出,而不是一大堆的數據處理。問題在于傳統的輸入/輸出函數(例如`read`,`fprintf`)都是阻塞式的。實際上,向文件寫入數據,從網絡讀取數據所花的時間,對比cpu的處理速度差得太多。任務沒有完成,函數是不會返回的,所以你的程序在這段時間內什么也做不了。對于需要高性能的的程序來說,這是一個主要的障礙。
其中一個標準的解決方案是使用多線程。每一個阻塞的I/O操作都會被分配到各個線程中(或者是使用線程池)。當某個線程一旦阻塞,處理器就可以調度處理其他需要cpu資源的線程。
但是libuv使用了另外一個解決方案,那就是異步,非阻塞。大多數的現代操作系統提供了基于事件通知的子系統。例如,一個正常的socket上的`read`調用會發生阻塞,直到發送方把信息發送過來。但是,實際上程序可以請求操作系統監視socket事件的到來,并將這個事件通知放到事件隊列中。這樣,程序就可以很簡單地檢查事件是否到來(可能此時正在使用cpu做數值處理的運算),并及時地獲取數據。說libuv是異步的,是因為程序可以在一頭表達對某一事件的興趣,并在另一頭獲取到數據(對于時間或是空間來說)。它是非阻塞是因為應用程序無需在請求數據后等待,可以自由地做其他的事。libuv的事件循環方式很好地與該模型匹配, 因為操作系統事件可以視為另外一種libuv事件. 非阻塞方式可以保證在其他事件到來時被盡快處理(當然還要考慮硬件的能力)。
#####Note
>我們不需要關心I/O在后臺是如何工作的,但是由于我們的計算機硬件的工作方式,線程是處理器最基本的執行單元,libuv和操作系統通常會運行后臺/工作者線程, 或者采用非阻塞方式來輪流執行任務。
Bert Belder,一個libuv的核心開發者,通過一個短視頻向我們解釋了libuv的架構和它的后臺工作方式。如果你之前沒有接觸過類似libuv,libev,這個視頻會非常有用。視頻的網址是https://youtu.be/nGn60vDSxQ4 。
包含了libuv的event-loop的更多詳細信息的[文檔](http://docs.libuv.org/en/v1.x/design.html#the-i-o-loop)。
##HELLO WORLD
讓我們開始寫第一個libuv程序吧!它什么都沒做,只是開啟了一個loop,然后很快地退出了。
####helloworld/main.c
```c
#include <stdio.h>
#include <stdlib.h>
#include <uv.h>
int main() {
uv_loop_t *loop = malloc(sizeof(uv_loop_t));
uv_loop_init(loop);
printf("Now quitting.\n");
uv_run(loop, UV_RUN_DEFAULT);
uv_loop_close(loop);
free(loop);
return 0;
}
```
這個程序會很快就退出了,因為沒有可以很處理的事件。我們可以使用各種API函數來告訴event-loop我們要監視的事件。
從libuv的1.0版本開始,用戶就可以在使用`uv_loop_init`初始化loop之前,給其分配相應的內存。這就允許你植入自定義的內存管理方法。記住要使用`uv_loop_close(uv_loop_t *)`關閉loop,然后再回收內存空間。在例子中,程序退出的時候會關閉loop,系統也會自動回收內存。對于長時間運行的程序來說,合理釋放內存很重要。
###Default loop
可以使用`uv_default_loop`獲取libuv提供的默認loop。如果你只需要一個loop的話,可以使用這個。
#####Note
>nodejs中使用了默認的loop作為自己的主loop。如果你在編寫nodejs的綁定,你應該注意一下。
##Error handling
初始化函數或者是同步執行的函數,會在執行失敗后返回代表錯誤的負數。但是對于異步執行的函數,會在執行失敗的時候,給它們的回調函數傳遞一個狀態參數。錯誤信息被定義為`UV_E`[常量](http://docs.libuv.org/en/v1.x/errors.html#error-constants)。
你可以使用`uv_strerror(int)`和`uv_err_name(int)`分別獲取`const char *`格式的錯誤信息和錯誤名字。
I/O函數的回調函數(例如文件和socket等)會被傳遞一個`nread`參數。如果`nread`小于0,就代表出現了錯誤(當然,UV_EOF是讀取到文件末端的錯誤,你要特殊處理)。
##Handles and Requests
libuv的工作建立在用戶表達對特定事件的興趣。這通常通過創造對應I/O設備,定時器,進程等的handle來實現。handle是不透明的數據結構,其中對應的類型`uv_TYPE_t`中的type指定了handle的使用目的。
####libuv watchers
```c
/* Handle types. */
typedef struct uv_loop_s uv_loop_t;
typedef struct uv_handle_s uv_handle_t;
typedef struct uv_stream_s uv_stream_t;
typedef struct uv_tcp_s uv_tcp_t;
typedef struct uv_udp_s uv_udp_t;
typedef struct uv_pipe_s uv_pipe_t;
typedef struct uv_tty_s uv_tty_t;
typedef struct uv_poll_s uv_poll_t;
typedef struct uv_timer_s uv_timer_t;
typedef struct uv_prepare_s uv_prepare_t;
typedef struct uv_check_s uv_check_t;
typedef struct uv_idle_s uv_idle_t;
typedef struct uv_async_s uv_async_t;
typedef struct uv_process_s uv_process_t;
typedef struct uv_fs_event_s uv_fs_event_t;
typedef struct uv_fs_poll_s uv_fs_poll_t;
typedef struct uv_signal_s uv_signal_t;
/* Request types. */
typedef struct uv_req_s uv_req_t;
typedef struct uv_getaddrinfo_s uv_getaddrinfo_t;
typedef struct uv_getnameinfo_s uv_getnameinfo_t;
typedef struct uv_shutdown_s uv_shutdown_t;
typedef struct uv_write_s uv_write_t;
typedef struct uv_connect_s uv_connect_t;
typedef struct uv_udp_send_s uv_udp_send_t;
typedef struct uv_fs_s uv_fs_t;
typedef struct uv_work_s uv_work_t;
/* None of the above. */
typedef struct uv_cpu_info_s uv_cpu_info_t;
typedef struct uv_interface_address_s uv_interface_address_t;
typedef struct uv_dirent_s uv_dirent_t;
```
handle代表了持久性對象。在異步的操作中,相應的handle上有許多與之關聯的request。request是短暫性對象(通常只維持在一個回調函數的時間),通常對映著handle上的一個I/O操作。request用來在初始函數和回調函數之間,傳遞上下文。例如uv_udp_t代表了一個udp的socket,然而,對于每一個向socket的寫入的完成后,都會向回調函數傳遞一個`uv_udp_send_t`。
handle可以通過下面的函數設置:
```c
uv_TYPE_init(uv_loop_t *, uv_TYPE_t *)
```
回調函數是libuv所關注的事件發生后,所調用的函數。應用程序的特定邏輯會在回調函數中實現。例如,一個IO監視器的回調函數會接收到從文件讀取到的數據,一個定時器的回調函數會在超時后被觸發等等。
###Idling
下面有一個使用空轉handle的例子。回調函數在每一個循環中都會被調用。在Utilities這部分會講到一些空轉handle的使用場景。現在讓我們使用一個空轉監視器,然后來觀察它的生命周期,接著看`uv_run`調用是否會造成阻塞。當達到事先規定好的計數后,空轉監視器會退出。因為`uv_run`已經找不到活著的事件監視器了,所以`uv_run()`也退出。
####idle-basic/main.c
```c
#include <stdio.h>
#include <uv.h>
int64_t counter = 0;
void wait_for_a_while(uv_idle_t* handle) {
counter++;
if (counter >= 10e6)
uv_idle_stop(handle);
}
int main() {
uv_idle_t idler;
uv_idle_init(uv_default_loop(), &idler);
uv_idle_start(&idler, wait_for_a_while);
printf("Idling...\n");
uv_run(uv_default_loop(), UV_RUN_DEFAULT);
uv_loop_close(uv_default_loop());
return 0;
}
```
###Storing context
在基于回調函數的編程風格中,你可能會需要在調用處和回調函數之間,傳遞一些上下文等特定的應用信息。所有的handle和request都有一個`data`域,可以用來存儲信息并傳遞。這是一個c語言庫中很常見的模式。即使是`uv_loop_t`也有一個相似的`data`域。