Redis C語言客戶端庫hiredis文件翻譯
Hiredis是redis資料庫一個輕量的C語言客戶端庫。
之所以輕量是由於它只是簡單的提供了對redis操作語句支援的介面,並沒有實現具體的操作語句的功能。但正是由於這種設計使我們只要熟悉了通用的redis操作語句就可以很容易的使用該庫和redis資料庫進行互動。
除了支援傳送命令和接收應答/應答資料,它提供了對應答資料的解析操作。而且這個基於I/O層的資料流解析操作設計考慮到了複用性,可以對應答資料進行通用的解析操作。
Hirides僅僅支援二進位制安全的redis協議,所以你只能針對版本號大於等於1.2.0的redis服務端使用。
庫包含多種API,包括同步命令操作API、非同步命令操作API和對應答資料進行解析的API。
升級
版本0.9.0是hiredis很多特性一次大的更新。但是對現有程式碼進行升級應該不會造成大的問題。升級時,要記住的關鍵一點是大於等於0.9.0的版本是使用redisContext*來保持連線狀態,之前的版本只是使用了無狀態的檔案描述符。
同步API
有幾個API需要介紹
redisContext *redisConnect(const char *ip, int port);
void *redisCommand(redisContext *c, const char *format, ...);
void freeReplyObject(void *reply);
連線redis資料庫
函式 redisConnect 被用來建立一個 redisContext。這個 context 是hiredis持有的連線狀態。redisConnect 結構體有一個整型的 err 變數來標識連線錯誤碼,如果連線錯誤則為非零值。變數 errstr 標識連線結果的文字描述。更多這方面的資訊會在以下Errors
redisContext *c = redisConnect("127.0.0.1", 6379);
if (c != NULL && c->err) {
printf("Error: %s\n", c->errstr); // handle error }
傳送命令到redis
有多種方法可以傳送命令到redis。
首先介紹的是redisCommand。此函式類似於printf的使用方式,如
reply = redisCommand(context, "SET foo bar");
類似於printf的s%格式化方式,如
reply = redisCommand(context, "SET foo %s", value);
當你需要傳送二進位制安全的命令可以採用%b的格式化方式,同時需要一個字串指標和size_t型別的字串長度引數,如下
reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen);
在API內部,Hiredis根據不同的引數分割命令轉化為操作redis資料庫的標準命令,你可以格式化多個引數來構造redis的命令,如下
reply = redisCommand(context, "SET key:%s %s", myid, value);
處理redis應答
當命令被成功執行後redisCommand會有相應的返回值。如果有錯誤發生,返回值為NULL並且redisReply結構體中的err變數將會被設定成相應的值(請參照Errors章節)。一旦有錯誤發生context不能被重用並且你需要建立一個新的連線。
redisCommand執行後返回值型別為redisReply。通過redisReply結構體中的type變數可以確定命令執行的情況。
-
REDIS_REPLY_STATUS:
-
返回執行結果為狀態的命令。比如set命令的返回值的型別是REDIS_REPLY_STATUS,然後只有當返回資訊是"OK"時,才表示該命令執行成功。可以通過reply->str得到文字資訊,通過reply->len得到資訊長度。
-
-
REDIS_REPLY_ERROR:
-
返回錯誤。錯誤資訊可以通過reply->str得到文字資訊,通過reply->len得到資訊長度。
-
-
REDIS_REPLY_INTEGER:
-
返回整型標識。可以通過reply->integer變數得到型別為long long的值。
-
-
REDIS_REPLY_NIL:
-
返回nil物件,說明不存在要訪問的資料。
-
-
REDIS_REPLY_STRING:
-
返回字串標識。可以通過reply->str得到具體值,通過reply->len得到資訊長度。
-
-
REDIS_REPLY_ARRAY:
-
返回資料集標識。資料集中元素的數目可以通過reply->elements獲得,每個元素是個redisReply物件,元素值可以通過reply->element[..index..].*形式獲得,用在獲取多個數據結果的操作。
-
執行完命令呼叫後應該通過freeReplyObject()釋放redisReply,對於巢狀物件(比如陣列)要注意,並不需要巢狀進行釋放,這樣是有害的會造成記憶體破壞。
Important:hiredis當前版本 (0.10.0)當使用非同步API時會自己釋放replies物件。這意味著你使用非同步API時並不需要主動呼叫freeReplyObject 。relpy物件當回撥返回時將會被自動釋放。但是這種行為也許會在將來的版本中改變,所以升級時請密切關注升級日誌。
清理連線資源
斷開連線並且釋放context使用以下函式
void redisFree(redisContext *c);
此函式立馬關閉socket並且釋放建立context時分配的資源。
傳送多個命令引數
和redisCommand函式相似,redisCommandArgv函式可以用於傳輸多個命令引數。函式原型為
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
,類似於 lpush, del key1 key2..., zadd key score1 member1 score2 member2...這類命令, 其中 argc是傳遞引數的個數, argv主要用於傳遞的string的value, 而argvlen 是每個string的size。
此函式返回值與redisCommand相似。參考https://gist.github.com/dspezia/1455082
管線(Pipelining)
為了搞清楚Hiredis在阻塞連線下的管線操作,需要理解其內部執行過程。
當任何類似於redisCommand的函式被呼叫,Hiredis首先將命令格式化為redis支援的命令協議。被格式化後的命令被放入context的輸出緩衝區,這個緩衝區是動態的,所以它可以容納任意數量的命令。在命令進入輸出緩衝區後,redisGetReply 函式被呼叫。這個函式有以下兩種執行方式:
-
輸入緩衝區非空:
-
從輸入緩衝區中嘗試解析單獨的reply物件並且返回reply
-
如果沒有reply能被解析,執行步驟2
-
-
輸入緩衝區為空:
-
將整個輸出緩衝區寫入socket
-
從socket中讀取資料直到有一個reply能被解析
-
Hiredis為了有效利用socket還提供了redisGetReply的介面。對於管線命令,需要完成的唯一事情就是填充輸出緩衝區。有兩個函式被用於執行此操作,這兩個函式基本與redisCommand函式功能類似,但是他們不返回reply
void redisAppendCommand(redisContext *c, const char *format, ...);
void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
當這兩個函式被一次或多次呼叫時,通過redisGetReply依次返回replies。redisGetReply的返回值要麼是REDISOK或是REDISERR,REDIS_ERR意味著獲得reply發生了錯誤,想要得到具體的錯誤原因可以通過err變數來獲取。
以下通過一個簡單例子說明管線的使用:
redisReply *reply;
redisAppendCommand(context,"SET foo bar");
redisAppendCommand(context,"GET foo");
redisGetReply(context,&reply); // reply for SET
freeReplyObject(reply);
redisGetReply(context,&reply); // reply for GET
freeReplyObject(reply);
redisGetReply這個API也可以被用來實現阻塞的訂閱模式
reply = redisCommand(context,"SUBSCRIBE foo");
freeReplyObject(reply);
while(redisGetReply(context,&reply) == REDIS_OK)
{
// consume message
freeReplyObject(reply);
}
Errors
如果某些函式(如redisConnect, redisCommand(呼叫不成功,函式返回值為NULL或者REDIS_ERR,此時context結構體中的err成員為非零值,可能為以下幾種常量
-
REDIS_ERR_IO:當建立連線時(試著寫socket或者讀socket)發生的I/O錯誤。如果你在程式碼中包含了errno.h標頭檔案,你便能得到準確的錯誤碼。
-
REDIS_ERR_EOF:redis服務端已經關閉了此連線。
-
REDIS_ERR_PROTOCOL:服務端解析協議時發生了錯誤。
-
REDIS_ERR_OTHER:其他錯誤。目前僅僅表示無法解析目標主機名的錯誤。
在錯誤情況下,可以通過context結構體中的errstr成員得到錯誤的確切描述。
非同步API
Hiredis自帶的非同步API很容易和一些基於事件的庫結合使用。比如和libev、ibevent的結合使用。
連線
函式redisAsyncConnect被用來和redis建立非阻塞連線。它返回redisAsyncContext的結構體,結構體的err成員用來檢查在建立連線的過程中是否發生了錯誤。因為建立的是非阻塞的連線,核心並不能立馬返回一個連線指定主機的結果。
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err)
{
printf("Error: %s\n", c->errstr);
// handle error
}
非同步Context可設定一個響應斷開連線事件的回撥函式,當連線斷開時會相應執行。回撥函式的原型為
void(const redisAsyncContext *c, int status);
在斷開連線的情況下,當連線是由使用者自己斷開的status引數為REDISOK,如果出現了其他錯誤status引數為REDISERR,當錯誤時通過err成員可以得到準確的錯誤碼。
當回撥執行完畢後context物件會自己釋放資源。此事件的回撥函式給你建立一個新連線提供了便利。
一個context物件僅能設定一次斷開連線的回撥,如果再進行下一次設定將會返回REDIS_ERR。設定斷開連接回調函式的原型為:
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
傳送操作命令並且響應回撥事件
在非同步的情況下,redis的操作指令將會被自動加入事件迴圈佇列。由於傳送命令執行的過程是非同步的,當命令執行完畢後將會呼叫相應的回撥函式。回撥函式的原型為
void(redisAsyncContext *c, void *reply, void *privdata);
privdata引數是由呼叫者自己定義的資料型別。
以下是進行非同步命令操作的函式:
int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,const char *format, ...);
int redisAsyncCommandArgv( redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
命令的使用方式和上面所講的同步介面類似。執行成功返回REDIS_OK,否則返回REDIS_ERR。比如連線已經關閉的情況下再使用redisAsyncCommand向此連線執行命令就會返回REDIS_ERR。
回撥執行完畢後如果reply不為空,回撥執行完畢後將自動對reply的資源進行回收。
當context發生錯誤時回撥得到的reply將為空。
斷開連線
一個非同步的連線可以通過下面這個函式終止
void redisAsyncDisconnect(redisAsyncContext *ac);
當這個函式被呼叫時,連線並不會被立即關閉,而是等待所有這個連線的非同步命令操作執行完畢,並且回撥事件已經執行完畢後才關閉此連線,這時在響應關閉連線事件的回撥函式中得到的狀態為REDIS_OK,此連線的資源也將會被自動回收。
將其掛接到事件庫X
在context物件被建立後進行很少的幾步操作就可以進行掛接。參看目錄adapters/下是如何掛接到libev 和 libevent下的。
應答解析API
Hiredis自帶的答覆解析API ,可以很容易與更高層次的語言進行繫結。 TODO: