1. 程式人生 > 其它 >Redis 學習 - hiredis(官網 2021-01-06)

Redis 學習 - hiredis(官網 2021-01-06)

技術標籤:Redis

目錄



官網:https://github.com/redis/hiredis
https://redislabs.com/lp/hiredis/
https://github.com/redis/hiredis/releases



Hiredis

1. Hiredis簡介

Hiredis是Redis資料庫的一個極簡C客戶端庫。

它是極簡主義的,因為它只增加了對協議的最小支援,但同時它使用了一個高階的類似printf的API。

除了支援傳送命令和接收應答之外,它還附帶了一個與I/O層分離的應答解析器。它是一個流解析器,設計為易於重用,例如,它可以用於更高級別的語言繫結,以實現高效的應答解析。

Hiredis只支援二進位制安全的Redis協議,所以你可以使用它與任何版本的Redis >= 1.2.0。

這個庫附帶了多個api。有同步API、非同步API和應答解析API。

IMPORTANT: Breaking changes from 0.14.1 -> 1.0.0 (一些更改)

  • redisContext has two additional members (free_privdata, and privctx).
  • redisOptions.timeout has been renamed to redisOptions.connect_timeout, and we’ve added redisOptions.command_timeout.
  • redisReplyObjectFunctions.createArray now takes size_t instead of int for its length parameter.

2. Synchronous API(同步API)

要使用同步API,只需要引入幾個函式呼叫:

redisContext *redisConnect(const char *ip, int port);
void *redisCommand(redisContext *c, const char *format, ...);
void freeReplyObject(void *reply);

2.1 Connecting(連線)

redisConnect函式用於建立所謂的redisContext。
Context(上下文)是Hiredis儲存連線狀態的地方。

redisContext結構
1 有一個整數err欄位,當連線處於錯誤狀態時,這個欄位是非零的。
2 errstr欄位將包含一個包含錯誤描述的字串。
3 關於錯誤的更多資訊可以在Errors一節(見下文)中找到。
4 在嘗試使用redisConnect連線Redis,你應該檢查err欄位,看看是否建立連線成功:

redisContext *c = redisConnect("127.0.0.1", 6379);
if (c == NULL || c->err) {
    if (c) {
        printf("Error: %s\n", c->errstr);
        // handle error
    } else {
        printf("Can't allocate redis context\n");
    }
}

注意:redisContext不是執行緒安全的。

2.2 Sending commands(傳送命令)

有幾種方法可以向Redis發出命令。首先要介紹的是redisCommand。該函式採用類似於printf的格式。最簡單的形式是這樣使用的:

reply = redisCommand(context, "SET foo bar");

說明符%s在命令中插入一個字串,並使用strlen來確定字串的長度:

reply = redisCommand(context, "SET foo %s", value);

當你需要在命令中傳遞二進位制安全字串時,可以使用%b說明符。與指向字串的指標一起,它需要string物件的size_t長度引數:

reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen);

在內部,Hiredis以不同的引數拆分命令,並將其轉換為與Redis通訊的協議。一個或多個空格分隔引數,所以你可以在引數的任何位置使用說明符:

reply = redisCommand(context, "SET key:%s %s", myid, value);

2.3 Using replies(使用回覆)

當命令成功執行時,redisCommand的返回值儲存一個應答。
當錯誤發生時,返回值為NULL,並且上下文中的err欄位將被設定(參見Errors部分)。
一旦返回錯誤,就不能重用上下文,您應該建立一個新的連線。

redisCommand的標準應答型別為redisReply。redisReply中的type欄位應該用來測試收到了什麼型別的回覆:

2.4 RESP2

  • REDIS_REPLY_STATUS:
    The command replied with a status reply. (返回應答狀態)
    狀態字串可以使用reply->str訪問。
    該字串的長度可以通過reply->len訪問。

  • REDIS_REPLY_ERROR:
    The command replied with an error.(返回一個錯誤)。
    可以訪問與REDIS_REPLY_STATUS相同的錯誤字串(reply->str) 檢視錯誤描述。

  • REDIS_REPLY_INTEGER:
    The command replied with an integer. (返回一個整數)
    該整數值可以通過long long型別的reply->integer欄位訪問。

  • REDIS_REPLY_NIL:
    The command replied with a nil object. (返回一個nil物件)。
    沒有資料可訪問。

  • REDIS_REPLY_STRING:
    A bulk (string) reply.(批量(字串)) 。
    返回的值可以通過reply->str訪問。該字串的長度可以通過reply->len訪問。

  • REDIS_REPLY_ARRAY:
    A multi bulk reply. (一個多批量回復(其實就是陣列))。
    陣列的元素個數儲存在reply->elements中。
    每個元素也是一個redisReply物件,可以通過reply->element[…index…]訪問陣列中的某個元素。
    Redis可能會用巢狀陣列來回復,這是完全支援的。

2.5 RESP3

Hiredis還支援如下所示的每一種新的RESP3資料型別。有關協議的更多資訊,請參見RESP3規範。

  • REDIS_REPLY_DOUBLE:
    The command replied with a double-precision floating point number. (返回一個雙精度浮點數)。
    該值儲存在str成員中 儲存為字串,可以使用strtod或類似的方法進行轉換。

  • REDIS_REPLY_BOOL:
    A boolean true/false reply. (返回一個bool值)。
    該值儲存在integer成員中,值為0或1。

  • REDIS_REPLY_MAP:
    An array with the added invariant that there will always be an even number of elements. (新增元素個數始終為偶數的不變式的陣列)。
    除了前面提到的不變式外,這個對映在函式上等價於REDIS_REPLY_ARRAY。

  • REDIS_REPLY_SET:
    An array response where each entry is unique. 一個數組響應,其中每個條目都是唯一的。
    與對映型別一樣,除了沒有重複的值之外,資料與陣列響應是相同的。

  • REDIS_REPLY_PUSH:
    An array that can be generated spontaneously by Redis. 一個可以由Redis自動生成的陣列。
    此陣列響應將始終包含至少兩個子元素。第一個包含推送訊息的型別(例如message或invalidate),第二個是包含推送有效負載本身的子陣列。

  • REDIS_REPLY_ATTR:
    從Redis 6.0.6開始,這個回覆型別在Redis中不被使用

  • REDIS_REPLY_BIGNUM:
    A string representing an arbitrarily large signed or unsigned integer value.表示任意大的有符號或無符號整數值的字串。
    該數字將在redisReply的str成員中被編碼為字串。

  • REDIS_REPLY_VERB:
    A verbatim string, intended to be presented to the user without modification. 一種一字不差的字串,不經修改就呈現給使用者。
    字串有效載荷儲存在str成員中,型別資料儲存在vtype成員中(例如txt是原始文字,md是markdown)。

回覆應該使用freeReplyObject()函式釋放。
注意,這個函式將負責釋放包含在陣列和巢狀陣列中的子應答物件,因此使用者不需要釋放子應答(這實際上是有害的,將破壞記憶體)。

重要提示:
當使用非同步API時,hiredis(1.0.0)的當前版本會釋放應答。
這意味著當你使用這個API時,你不應該呼叫freeReplyObject。
回撥返回後,應答被hiredis清理。
我們可能會引入一個標誌,以便在庫的未來版本中對此進行配置。

2.6 Cleaning up(清理)

要斷開和釋放上下文,可以使用以下函式:

void redisFree(redisContext *c);

這個函式立即關閉套接字,然後釋放在建立上下文時完成的分配。

2.7 Sending commands (cont’d)(傳送命令)(二進位制安全)

與redisCommand一樣,可以使用redisCommandArgv函式發出命令。它的原型如下:

void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);

接受的引數個數為:argc;
各個引數(每個引數是一個字串):字串陣列argv(這是一個:元素型別為char*的陣列);
各個引數的長度:argvlen(這是一個:元素型別為size_t的陣列)。

為方便起見,argvlen可以設定為NULL,並且函式將在每個引數上使用strlen(3)來確定其長度。
顯然,當任何引數都需要二進位制安全時,應該在陣列argvlen中提供每個引數的長度。

返回值具有與redisCommand相同的語義。

2.8 Pipelining 流水線

為了解釋Hiredis如何在阻塞連線中支援管道,需要了解內部執行流。

當redisCommand家族中的任何一個函式被呼叫時,Hiredis首先根據Redis協議格式化命令。格式化的命令然後放入上下文的輸出緩衝區。這個輸出緩衝區是動態的,因此它可以容納任意數量的命令。在將命令放入輸出緩衝區之後,將呼叫redisGetReply。該函式有以下兩條執行路徑:

  1. 輸入緩衝區非空:
    嘗試解析來自輸入緩衝區的單個響應並返回它
    如果沒有可以解析的答覆,繼續2
  2. 輸入緩衝區為空:
    將整個輸出緩衝區寫入套接字
    從套接字讀取,直到可以解析單個應答

函式redisGetReply作為Hiredis API的一部分,可以在套接字上期待應答時使用。
對於管道命令,唯一需要做的事情是填充輸出緩衝區。
由於這個原因,除了不返回應答外,可以使用兩個與redisCommand家族相同的命令:

void redisAppendCommand(redisContext *c, const char *format, ...);
void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);

在呼叫任意一個函式一次或多次後,可以使用redisGetReply來接收後續的響應。
該函式的返回值是REDIS_OK或REDIS_ERR,後者表示在讀取應答時發生了錯誤。
與其他命令一樣,上下文中的err欄位可以用來找出導致此錯誤的原因。

下面的例子展示了一個簡單的管道(導致只有一個write(2)呼叫和一個read(2)呼叫):

redisReply *reply;
redisAppendCommand(context,"SET foo bar");
redisAppendCommand(context,"GET foo");
redisGetReply(context,(void *)&reply); // reply for SET
freeReplyObject(reply);
redisGetReply(context,(void *)&reply); // reply for GET
freeReplyObject(reply);

這個API也可以用來實現阻塞訂閱器:

reply = redisCommand(context,"SUBSCRIBE foo");
freeReplyObject(reply);
while(redisGetReply(context,(void *)&reply) == REDIS_OK) {
    // consume message
    freeReplyObject(reply);
}

2.9 Errors錯誤

當函式呼叫不成功時,根據函式返回NULL或REDIS_ERR。上下文中的err欄位將是非零的,並設定為以下常量之一:

REDIS_ERR_IO:在建立連線時出現了一個I/O錯誤,試圖寫到套接字或從套接字讀取。如果在應用程式中包含了errno.h,則可以使用全域性errno變數來找出問題所在。

REDIS_ERR_EOF:伺服器關閉連線,導致一個空讀。

REDIS_ERR_PROTOCOL:解析協議時出現錯誤。

REDIS_ERR_OTHER:任何其他錯誤。目前,它只在指定的主機名無法解析時使用。

在每種情況下,上下文中的errstr欄位都將被設定為儲存錯誤的字串表示形式。

3. Asynchronous API(非同步API)

Hiredis提供了一個非同步API,可以輕鬆地與任何事件庫一起工作。例如:Hiredis與libevlibevent捆綁使用。

3.1 Connecting(連線)

函式redisAsyncConnect可以用來建立一個非阻塞連線到Redis。
它返回一個指向新建立的redisAsyncContext結構體的指標。
建立後應檢查err欄位,以檢視是否存在建立連線的錯誤。
因為將要建立的連線是非阻塞的,所以如果指定的主機和埠能夠接受連線,核心就不能立即返回。

注意:redisAsyncContext不是執行緒安全的。

redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) {
    printf("Error: %s\n", c->errstr);
    // handle error
}

非同步上下文可以儲存一個斷開回調函式,當連線斷開時(由於一個錯誤或每個使用者請求)呼叫該函式。這個函式應該有以下的原型:

void(const redisAsyncContext *c, int status);

在斷開連線時,當用戶發起斷開連線時,status引數被設定為REDIS_OK,或者當由錯誤導致斷開連線時設定為REDIS_ERR。當它是REDIS_ERR時,可以訪問上下文中的err欄位來找出錯誤的原因。

在斷開回調觸發後,context物件總是被釋放。當需要重新連線時,斷開回調是一個很好的選擇。

每個上下文只能設定一次斷開回調。對於後續呼叫,它將返回REDIS_ERR。設定斷開回調函式的原型如下:

int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);

ac->data可以用於將使用者資料傳遞給這個回撥函式,對於redisConnectCallback也可以這樣做。

3.2 Sending commands and their callbacks(傳送命令和它們的回撥)

在非同步上下文中,由於事件迴圈的性質,命令是自動流水線的。
因此,與同步API不同,傳送命令的方式只有一種。
因為命令是非同步傳送到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。

如果讀取帶有空回撥函式的命令的應答,則會立即釋放它。
當命令的回撥非空時,記憶體在回撥之後立即被釋放:應答僅在回撥的持續時間內有效。

當上下文遇到錯誤時,所有掛起的回撥都將以空應答呼叫。

3.3 Disconnecting(斷開)

可以使用以下方式終止非同步連線:

void redisAsyncDisconnect(redisAsyncContext *ac);

當呼叫此函式時,連線不會立即終止。相反,新的命令將不再被接受,並且只有當所有掛起的命令都已寫入套接字,它們各自的響應已經被讀取,它們各自的回撥已經被執行時,連線才會終止。在此之後,斷開連接回調函式將以REDIS_OK狀態執行,上下文物件將被釋放。

3.4 Hooking it up to event library X(將其連線到事件庫X)

在建立上下文物件之後,需要在其上設定一些鉤子。有關繫結到libev和libevent的資訊,請參閱適adapters/目錄。

4 Reply parsing API(回覆解析API)

Hiredis附帶了一個應答解析API,這使得編寫更高級別的語言繫結變得很容易。

reply解析API由以下函式組成:

redisReader *redisReaderCreate(void);
void redisReaderFree(redisReader *reader);
int redisReaderFeed(redisReader *reader, const char *buf, size_t len);
int redisReaderGetReply(redisReader *reader, void **reply);

當hiredis建立一個普通的Redis上下文時,同樣的一組函式被hiredis內部使用,上面的API只是將它暴露給使用者直接使用。

4.1 Usage(使用)

redisReaderCreate()函式建立一個redisReader結構體,該結構體為協議解析器儲存一個帶有未解析資料和狀態的緩衝區。

傳入的資料——很可能來自套接字——可以使用redisReaderFeed()放置在redisReader的內部緩衝區中。
redisReaderFeed()這個函式將複製buf所指向的len位元組的緩衝區。
當呼叫redisReaderGetReply()時,將解析該資料。
這個函式通過void **reply返回一個整數狀態和一個reply物件(如上所述)。返回的狀態可以是REDIS_OK或REDIS_ERR,後者表示出了問題(協議錯誤或記憶體不足錯誤)。

解析器將多批量有效負載的巢狀級別限制為7。如果多塊巢狀級別高於此級別,解析器將返回一個錯誤。

4.2 Customizing replies(定製答道)

函式’ redisReaderGetReply() ‘建立’ redisReply ‘,並使函式引數’ reply ‘指向建立的變數’ redisReply '。

例如,如果響應型別為’ REDIS_REPLY_STATUS ‘,那麼’ redisReply ‘的’ str '欄位將保持狀態為普通的C字串。

但是,可以通過在’ redisReader ‘結構上設定’ fn ‘欄位來定製負責建立’ redisReply ‘例項的函式。這應該在建立’ redisReader '後立即完成。

例如, hiredis-rb
使用自定義應答物件函式建立Ruby物件。

4.3 Reader max buffer(Reader的最大緩衝區)

無論是直接使用Reader API還是通過一個普通的Redis上下文間接使用它,redisReader結構都使用一個緩衝區來積累來自伺服器的資料。為了避免在未使用的緩衝區中浪費記憶體,通常這個緩衝區在它為空且大於16 KiB時被銷燬

然而,當使用非常大的有效負載時,破壞緩衝區可能會大大降低效能,因此可以修改空閒緩衝區的最大大小,將reader結構體的maxbuf欄位的值更改為所需的值。0這個特殊值意味著空閒緩衝區沒有最大值,因此該緩衝區永遠不會被釋放。

例如,如果你有一個普通的Redis上下文,你可以設定最大空閒緩衝區為零(無限制),只需:

context->reader->maxbuf = 0;

這樣做只是為了在使用大負載時最大化效能。
為了防止分配無用的記憶體,上下文應該儘快重新設定為REDIS_READER_MAX_BUF。

4.4 Reader max array elements(reader陣列元素的最大個數)

預設情況下,hiredis應答解析器將多塊元素的最大數目設定為2^32 - 1或4,294,967,295個條目。如果你需要處理多塊的回覆,你可以把值設定得更高或為0,這意味著無限:

context->reader->maxelements = 0;

5 SSL/TLS Support

5.1 Building

預設情況下不支援SSL/TLS,需要一個明確的標誌:
安裝hiredis時,make時要加引數,如下:

make USE_SSL=1

這需要OpenSSL開發包(例如,包括標頭檔案)可用。

When enabled, SSL/TLS支援libhiredis_ssl.a和libhiredis_ssl.so靜態/動態庫。這使原始庫不受影響,因此不會引入額外的依賴項。

5.2 Using it

首先,你需要確保包含SSL標頭檔案:

#include "hiredis.h"
#include "hiredis_ssl.h"

除了libhiredis之外,還需要連結到libhiredis_ssl,並新增-lssl -lcrypto以滿足其依賴關係。
Hiredis在其正常的redisContext或redisAsyncContext之上實現了SSL/TLS,所以你需要首先建立一個連線,然後發起SSL/TLS握手。

5.2.1 Hiredis OpenSSL Wrappers

在Hiredis可以協商SSL/TLS連線之前,有必要初始化OpenSSL並建立上下文。你可以通過兩種方式做到這一點:

  1. 直接使用OpenSSL API初始化庫的全域性上下文,並建立SSL_CTX *和SSL *上下文。使用SSL *物件,您可以呼叫redisInitiateSSL()。
  2. 使用一組hiredis提供的圍繞OpenSSL的包裝器,建立一個redisSSLContext物件來儲存配置,並使用redisInitiateSSLWithContext()來初始化SSL/TLS握手。
/* An Hiredis SSL context. It holds SSL configuration and can be reused across
 * many contexts.
 */
redisSSLContext *ssl_context;

/* An error variable to indicate what went wrong, if the context fails to
 * initialize.
 */
redisSSLContextError ssl_error;

/* Initialize global OpenSSL state.
 *
 * You should call this only once when your app initializes, and only if
 * you don't explicitly or implicitly initialize OpenSSL it elsewhere.
 */
redisInitOpenSSL();

/* Create SSL context */
ssl_context = redisCreateSSLContext(
    "cacertbundle.crt",     /* File name of trusted CA/ca bundle file, optional */
    "/path/to/certs",       /* Path of trusted certificates, optional */
    "client_cert.pem",      /* File name of client certificate file, optional */
    "client_key.pem",       /* File name of client private key, optional */
    "redis.mydomain.com",   /* Server name to request (SNI), optional */
    &ssl_error);

if(ssl_context == NULL || ssl_error != 0) {
    /* Handle error and abort... */
    /* e.g. 
    printf("SSL error: %s\n", 
        (ssl_error != 0) ? 
            redisSSLContextGetError(ssl_error) : "Unknown error");
    // Abort
    */
}

/* Create Redis context and establish connection */
c = redisConnect("localhost", 6443);
if (c == NULL || c->err) {
    /* Handle error and abort... */
}

/* Negotiate SSL/TLS */
if (redisInitiateSSLWithContext(c, ssl_context) != REDIS_OK) {
    /* Handle error, in c->err / c->errstr */
}

6 RESP3 PUSH replies

Redis 6.0引入了PUSH replies,回覆型別為>。這些訊息是自發生成的,可以在任何時候到達,因此必須使用回撥來處理。

6.1 Default behavior(預設的行為)

預設情況下,Hiredis會在redisContext和redisAsyncContext上安裝處理程式,這將攔截並釋放檢測到的任何推送響應。這意味著在升級到Redis 6和切換到RESP3後,現有的程式碼將照常工作。

6.2 Custom PUSH handler prototypes(自定義PUSH處理程式原型)

回撥原型在’ redisContext ‘和’ redisAsyncContext '之間有所不同。

6.2.1 redisContext

void my_push_handler(void *privdata, void *reply) {
    /* Handle the reply */

    /* Note: We need to free the reply in our custom handler for
             blocking contexts.  This lets us keep the reply if
             we want. */
    freeReplyObject(reply);
}

6.2.2 redisAsyncContext

void my_async_push_handler(redisAsyncContext *ac, void *reply) {
    /* Handle the reply */

    /* Note:  Because async hiredis always frees replies, you should
              not call freeReplyObject in an async push callback. */
}

6.3 Installing a custom handler(安裝自定義處理程式)

有兩種方法可以設定自己的PUSH handlers。

  1. 在redisOptions結構體中設定push_cb或async_push_cb,並使用redisConnectWithOptions或redisAsyncConnectWithOptions連線。
redisOptions = {0};
REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379);
options->push_cb = my_push_handler;
redisContext *context = redisConnectWithOptions(&options);
  1. 在連線的上下文上呼叫redisSetPushCallback或redisAsyncSetPushCallback。
redisContext *context = redisConnect("127.0.0.1", 6379);
redisSetPushCallback(context, my_push_handler);

注意,redisSetPushCallback和redisAsyncSetPushCallback都返回任何當前配置的處理程式,使它很容易覆蓋,然後返回到舊的值。

6.4 Specifying no handler(沒有指定處理程式)

如果你有一個獨特的用例,你不希望hiredis自動攔截和自由推送回復,你將想要配置任何處理程式。這可以通過兩種方式實現。

  1. 在redisOptions中設定REDIS_OPT_NO_PUSH_AUTOFREE標誌,並保留回撥函式指標為空。
redisOptions = {0};
REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379);
options->options |= REDIS_OPT_NO_PUSH_AUTOFREE;
redisContext *context = redisConnectWithOptions(&options);
  1. 一旦連線,用NULL呼叫redisSetPushCallback。
redisContext *context = redisConnect("127.0.0.1", 6379);
redisSetPushCallback(context, NULL);

注意:在沒有配置處理程式的情況下,對redisCommand的呼叫可能會生成多個應答,所以這個策略只適用於存在某種blockingredisGetReply()迴圈(例如監視或訂閱工作負載)的情況。

hiredis官方例程

1. example.c

主要使用了
	redisConnectWithTimeout()	建立連線
	redisCommand()				傳送redis命令
	freeReplyObject()			釋放每一次的reply
	redisFree()					斷開連線並釋放redisContext 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <hiredis/hiredis.h>// /usr/local/include/hiredis/hiredis.h

#ifdef _MSC_VER
#include <winsock2.h> /* For struct timeval */
#endif

int main(int argc, char **argv)
{
	unsigned int j, isunix = 0;
	redisContext *c;
	redisReply *reply;
	const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1";

	if (argc > 2)
	{
		if (*argv[2] == 'u' || *argv[2] == 'U')
		{
			isunix = 1;
			/* in this case, host is the path to the unix socket */
			printf("Will connect to unix socket @%s\n", hostname);
		}
	}

	int port = (argc > 2) ? atoi(argv[2]) : 6379;

	struct timeval timeout = {1, 500000}; // 1.5 seconds
	if (isunix)
	{
		c = redisConnectUnixWithTimeout(hostname, timeout);
	}
	else
	{
		c = redisConnectWithTimeout(hostname, port, timeout);
	}
	if (c == NULL || c->err)
	{
		if (c)
		{
			printf("Connection error: %s\n", c->errstr);
			redisFree(c);
		}
		else
		{
			printf("Connection error: can't allocate redis context\n");
		}
		exit(1);
	}

	/* PING server */
	reply = redisCommand(c, "PING");
	printf("PING: %s\n", reply->str);
	freeReplyObject(reply);

	/* Set a key */
	reply = redisCommand(c, "SET %s %s", "foo", "hello world");
	printf("SET: %s\n", reply->str);
	freeReplyObject(reply);

	/* Set a key using binary safe API */
	reply = redisCommand(c, "SET %b %b", "bar", (size_t)3, "hello", (size_t)5);
	printf("SET (binary API): %s\n", reply->str);
	freeReplyObject(reply);

	/* Try a GET and two INCR */
	reply = redisCommand(c, "GET foo");
	printf("GET foo: %s\n", reply->str);
	freeReplyObject(reply);

	reply = redisCommand(c, "INCR counter");
	printf("INCR counter: %lld\n", reply->integer);
	freeReplyObject(reply);
	/* again ... */
	reply = redisCommand(c, "INCR counter");
	printf("INCR counter: %lld\n", reply->integer);
	freeReplyObject(reply);

	/* Create a list of numbers, from 0 to 9 */
	reply = redisCommand(c, "DEL mylist");
	freeReplyObject(reply);
	for (j = 0; j < 10; j++)
	{
		char buf[64];

		snprintf(buf, 64, "%u", j);
		reply = redisCommand(c, "LPUSH mylist element-%s", buf);
		freeReplyObject(reply);
	}

	/* Let's check what we have inside the list */
	reply = redisCommand(c, "LRANGE mylist 0 -1");
	if (reply->type == REDIS_REPLY_ARRAY)
	{
		for (j = 0; j < reply->elements; j++)
		{
			printf("%u) %s\n", j, reply->element[j]->str);
		}
	}
	freeReplyObject(reply);

	/* Disconnects and frees the context */
	redisFree(c);

	return 0;
}
[[email protected] testcpp]# gcc example.c -o out -lhiredis
[[email protected] testcpp]# ./out 
PING: PONG
SET: OK
SET (binary API): OK
GET foo: hello world
INCR counter: 1
INCR counter: 2
0) element-9
1) element-8
2) element-7
3) element-6
4) element-5
5) element-4
6) element-3
7) element-2
8) element-1
9) element-0
[[email protected] testcpp]# 

2. example-libevent.c

主要使用了
	event_base_new()				建立libevent的事件處理框架例項event_base
	redisAsyncConnectWithOptions()	建立連線
	redisLibeventAttach()			把context新增到event_base上
	redisAsyncSetConnectCallback()	設定建立連線後的回撥
	redisAsyncSetDisconnectCallback()設定斷開連線後的回撥
	redisAsyncCommand()				傳送redis命令(可以設定回撥函式!!!)
	event_base_dispatch()			libevent開始事件迴圈
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

#include <hiredis/hiredis.h> // /usr/local/include/hiredis/hiredis.h
#include <hiredis/async.h>
#include <hiredis/adapters/libevent.h>

void getCallback(redisAsyncContext *c, void *r, void *privdata)
{
	redisReply *reply = r;
	if (reply == NULL)
	{
		if (c->errstr)
		{
			printf("errstr: %s\n", c->errstr);
		}
		return;
	}
	printf("argv[%s]: %s\n", (char *)privdata, reply->str);

	/* Disconnect after receiving the reply to GET */
	redisAsyncDisconnect(c);// 斷開連線
}

void connectCallback(const redisAsyncContext *c, int status)
{
	if (status != REDIS_OK)
	{
		printf("Error: %s\n", c->errstr);
		return;
	}
	printf("Connected...\n");
}

void disconnectCallback(const redisAsyncContext *c, int status)
{
	if (status != REDIS_OK)
	{
		printf("Error: %s\n", c->errstr);
		return;
	}
	printf("Disconnected...\n");
}

int main(int argc, char **argv)
{
#ifndef _WIN32
	signal(SIGPIPE, SIG_IGN);
#endif

	struct event_base *base = event_base_new();
	redisOptions options = {0};
	REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379);
	struct timeval tv = {0};
	tv.tv_sec = 1;
	options.timeout = &tv;
	//options.connect_timeout = &tv;//已經更改

	redisAsyncContext *c = redisAsyncConnectWithOptions(&options);
	if (c->err)
	{
		/* Let *c leak for now... */
		printf("Error: %s\n", c->errstr);
		return 1;
	}

	redisLibeventAttach(c, base);
	redisAsyncSetConnectCallback(c, connectCallback);
	redisAsyncSetDisconnectCallback(c, disconnectCallback);
	redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc - 1], strlen(argv[argc - 1]));
	redisAsyncCommand(c, getCallback, (char *)"end-1", "GET key");
	event_base_dispatch(base);
	return 0;
}
[[email protected] testcpp]# gcc example.c -o out -lhiredis -levent
[[email protected] testcpp]# ./out HelloWorld
Connected...
argv[end-1]: HelloWorld
Disconnected...
[[email protected] testcpp]# 

3. example-ssl.c

安裝hiredis時要支援openssl

make USE_SSL=1
make install

但是連結openssl/ssl.h時報錯!???

[[email protected] hiredis-master]# make USE_SSL=1
cc -std=c99 -pedantic -c -O3 -fPIC  -DHIREDIS_TEST_SSL -Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers -g -ggdb alloc.c
cc -std=c99 -pedantic -c -O3 -fPIC  -DHIREDIS_TEST_SSL -Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers -g -ggdb net.c
cc -std=c99 -pedantic -c -O3 -fPIC  -DHIREDIS_TEST_SSL -Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers -g -ggdb hiredis.c
cc -std=c99 -pedantic -c -O3 -fPIC  -DHIREDIS_TEST_SSL -Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers -g -ggdb sds.c
cc -std=c99 -pedantic -c -O3 -fPIC  -DHIREDIS_TEST_SSL -Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers -g -ggdb async.c
cc -std=c99 -pedantic -c -O3 -fPIC  -DHIREDIS_TEST_SSL -Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers -g -ggdb read.c
cc -std=c99 -pedantic -c -O3 -fPIC  -DHIREDIS_TEST_SSL -Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers -g -ggdb sockcompat.c
cc -shared -Wl,-soname,libhiredis.so.1.0.1-dev -o libhiredis.so alloc.o net.o hiredis.o sds.o async.o read.o sockcompat.o 
ar rcs libhiredis.a alloc.o net.o hiredis.o sds.o async.o read.o sockcompat.o
cc -std=c99 -pedantic -c -O3 -fPIC  -DHIREDIS_TEST_SSL -Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers -g -ggdb test.c
cc -std=c99 -pedantic -c -O3 -fPIC  -DHIREDIS_TEST_SSL -Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers -g -ggdb ssl.c
ar rcs libhiredis_ssl.a ssl.o
cc -o hiredis-test -O3 -fPIC  -DHIREDIS_TEST_SSL -Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers -g -ggdb -I. test.o libhiredis.a libhiredis_ssl.a  -lssl -lcrypto -lssl -lcrypto -lpthread
/opt/rh/devtoolset-9/root/usr/libexec/gcc/x86_64-redhat-linux/9/ld: libhiredis_ssl.a(ssl.o): in function `redisInitOpenSSL':
/root/testcpp/hiredis-master/ssl.c:160: undefined reference to `OPENSSL_init_ssl'
/opt/rh/devtoolset-9/root/usr/libexec/gcc/x86_64-redhat-linux/9/ld: libhiredis_ssl.a(ssl.o): in function `redisCreateSSLContext':
/root/testcpp/hiredis-master/ssl.c:232: undefined reference to `TLS_client_method'
/opt/rh/devtoolset-9/root/usr/libexec/gcc/x86_64-redhat-linux/9/ld: /root/testcpp/hiredis-master/ssl.c:238: undefined reference to `SSL_CTX_set_options'
collect2: error: ld returned 1 exit status
make: *** [hiredis-test] Error 1
[[email protected] hiredis-master]# 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

#include <hiredis/hiredis.h>	 // /usr/local/include/hiredis/hiredis.h
#include <hiredis/hiredis_ssl.h> // /usr/local/include/hiredis/hiredis_ssl.h

#ifdef _MSC_VER
#include <winsock2.h> /* For struct timeval */
#endif

int main(int argc, char **argv)
{
	unsigned int j;
	redisSSLContext *ssl;
	redisSSLContextError ssl_error;
	redisContext *c;
	redisReply *reply;
	if (argc < 4)
	{
		printf("Usage: %s <host> <port> <cert> <key> [ca]\n", argv[0]);
		exit(1);
	}
	const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1";
	int port = atoi(argv[2]);
	const char *cert = argv[3];
	const char *key = argv[4];
	const char *ca = argc > 4 ? argv[5] : NULL;

	redisInitOpenSSL();
	ssl = redisCreateSSLContext(ca, NULL, cert, key, NULL, &ssl_error);
	if (!ssl)
	{
		printf("SSL Context error: %s\n",
			   redisSSLContextGetError(ssl_error));
		exit(1);
	}

	struct timeval tv = {1, 500000}; // 1.5 seconds
	redisOptions options = {0};
	REDIS_OPTIONS_SET_TCP(&options, hostname, port);
	options.connect_timeout = &tv;
	c = redisConnectWithOptions(&options);

	if (c == NULL || c->err)
	{
		if (c)
		{
			printf("Connection error: %s\n", c->errstr);
			redisFree(c);
		}
		else
		{
			printf("Connection error: can't allocate redis context\n");
		}
		exit(1);
	}

	if (redisInitiateSSLWithContext(c, ssl) != REDIS_OK)
	{
		printf("Couldn't initialize SSL!\n");
		printf("Error: %s\n", c->errstr);
		redisFree(c);
		exit(1);
	}

	/* PING server */
	reply = redisCommand(c, "PING");
	printf("PING: %s\n", reply->str);
	freeReplyObject(reply);

	/* Set a key */
	reply = redisCommand(c, "SET %s %s", "foo", "hello world");
	printf("SET: %s\n", reply->str);
	freeReplyObject(reply);

	/* Set a key using binary safe API */
	reply = redisCommand(c, "SET %b %b", "bar", (size_t)3, "hello", (size_t)5);
	printf("SET (binary API): %s\n", reply->str);
	freeReplyObject(reply);

	/* Try a GET and two INCR */
	reply = redisCommand(c, "GET foo");
	printf("GET foo: %s\n", reply->str);
	freeReplyObject(reply);

	reply = redisCommand(c, "INCR counter");
	printf("INCR counter: %lld\n", reply->integer);
	freeReplyObject(reply);
	/* again ... */
	reply = redisCommand(c, "INCR counter");
	printf("INCR counter: %lld\n", reply->integer);
	freeReplyObject(reply);

	/* Create a list of numbers, from 0 to 9 */
	reply = redisCommand(c, "DEL mylist");
	freeReplyObject(reply);
	for (j = 0; j < 10; j++)
	{
		char buf[64];

		snprintf(buf, 64, "%u", j);
		reply = redisCommand(c, "LPUSH mylist element-%s", buf);
		freeReplyObject(reply);
	}

	/* Let's check what we have inside the list */
	reply = redisCommand(c, "LRANGE mylist 0 -1");
	if (reply->type == REDIS_REPLY_ARRAY)
	{
		for (j = 0; j < reply->elements; j++)
		{
			printf("%u) %s\n", j, reply->element[j]->str);
		}
	}
	freeReplyObject(reply);

	/* Disconnects and frees the context */
	redisFree(c);

	redisFreeSSLContext(ssl);

	return 0;
}

編譯報錯!待解決!有時間解決一下

[[email protected] testcpp]# gcc example-ssl.c  -o out -lhiredis -levent -lssl -lcrypto -lhiredis_ssl -L/usr/local/lib
/usr/local/lib/libhiredis_ssl.a(ssl.o): In function `redisInitOpenSSL':
/root/testcpp/hiredis-master/ssl.c:159: undefined reference to `OPENSSL_init_ssl'
/usr/local/lib/libhiredis_ssl.a(ssl.o): In function `redisCreateSSLContext':
/root/testcpp/hiredis-master/ssl.c:231: undefined reference to `TLS_client_method'
/root/testcpp/hiredis-master/ssl.c:237: undefined reference to `SSL_CTX_set_options'
collect2: error: ld returned 1 exit status
[[email protected] testcpp]# 


[[email protected] testcpp]# gcc test.c -o out -lhiredis -lssl -lcrypto -lhiredis_ssl
/opt/rh/devtoolset-9/root/usr/libexec/gcc/x86_64-redhat-linux/9/ld: /usr/local/lib/libhiredis_ssl.a(ssl.o): in function `redisInitOpenSSL':
/root/testcpp/hiredis-master/ssl.c:159: undefined reference to `OPENSSL_init_ssl'
/opt/rh/devtoolset-9/root/usr/libexec/gcc/x86_64-redhat-linux/9/ld: /usr/local/lib/libhiredis_ssl.a(ssl.o): in function `redisCreateSSLContext':
/root/testcpp/hiredis-master/ssl.c:231: undefined reference to `TLS_client_method'
/opt/rh/devtoolset-9/root/usr/libexec/gcc/x86_64-redhat-linux/9/ld: /root/testcpp/hiredis-master/ssl.c:237: undefined reference to `SSL_CTX_set_options'
collect2: error: ld returned 1 exit status
[[email protected] testcpp]# 

4. example-libevent-ssl.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

#include <hiredis/hiredis.h>	 // /usr/local/include/hiredis/hiredis.h
#include <hiredis/hiredis_ssl.h> // /usr/local/include/hiredis/hiredis_ssl.h
#include <hiredis/async.h>
#include <hiredis/adapters/libevent.h>

void getCallback(redisAsyncContext *c, void *r, void *privdata)
{
	redisReply *reply = r;
	if (reply == NULL)
		return;
	printf("argv[%s]: %s\n", (char *)privdata, reply->str);

	/* Disconnect after receiving the reply to GET */
	redisAsyncDisconnect(c);
}

void connectCallback(const redisAsyncContext *c, int status)
{
	if (status != REDIS_OK)
	{
		printf("Error: %s\n", c->errstr);
		return;
	}
	printf("Connected...\n");
}

void disconnectCallback(const redisAsyncContext *c, int status)
{
	if (status != REDIS_OK)
	{
		printf("Error: %s\n", c->errstr);
		return;
	}
	printf("Disconnected...\n");
}

int main(int argc, char **argv)
{
#ifndef _WIN32
	signal(SIGPIPE, SIG_IGN);
#endif

	struct event_base *base = event_base_new();
	if (argc < 5)
	{
		fprintf(stderr,
				"Usage: %s <key> <host> <port> <cert> <certKey> [ca]\n", argv[0]);
		exit(1);
	}

	const char *value = argv[1];
	size_t nvalue = strlen(value);

	const char *hostname = argv[2];
	int port = atoi(argv[3]);

	const char *cert = argv[4];
	const char *certKey = argv[5];
	const char *caCert = argc > 5 ? argv[6] : NULL;

	redisSSLContext *ssl;
	redisSSLContextError ssl_error;

	redisInitOpenSSL();

	ssl = redisCreateSSLContext(caCert, NULL,
								cert, certKey, NULL, &ssl_error);
	if (!ssl)
	{
		printf("Error: %s\n", redisSSLContextGetError(ssl_error));
		return 1;
	}

	redisAsyncContext *c = redisAsyncConnect(hostname, port);
	if (c->err)
	{
		/* Let *c leak for now... */
		printf("Error: %s\n", c->errstr);
		return 1;
	}
	if (redisInitiateSSLWithContext(&c->c, ssl) != REDIS_OK)
	{
		printf("SSL Error!\n");
		exit(1);
	}

	redisLibeventAttach(c, base);
	redisAsyncSetConnectCallback(c, connectCallback);
	redisAsyncSetDisconnectCallback(c, disconnectCallback);
	redisAsyncCommand(c, NULL, NULL, "SET key %b", value, nvalue);
	redisAsyncCommand(c, getCallback, (char *)"end-1", "GET key");
	event_base_dispatch(base);

	redisFreeSSLContext(ssl);
	return 0;
}
[[email protected] testcpp]# gcc example-libevent-ssl.c  -o out -lhiredis -levent -lssl -lcrypto -lhiredis_ssl -L/usr/local/lib
/usr/local/lib/libhiredis_ssl.a(ssl.o): In function `redisInitOpenSSL':
/root/testcpp/hiredis-master/ssl.c:159: undefined reference to `OPENSSL_init_ssl'
/usr/local/lib/libhiredis_ssl.a(ssl.o): In function `redisCreateSSLContext':
/root/testcpp/hiredis-master/ssl.c:231: undefined reference to `TLS_client_method'
/root/testcpp/hiredis-master/ssl.c:237: undefined reference to `SSL_CTX_set_options'
collect2: error: ld returned 1 exit status
[[email protected] testcpp]# 


5. example-push.c

Are you sure you’re connected to redis-server >= 6.0.0?
所以要保證你的redis版本大於等於6.0.0

/*
 * Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com>
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of Redis nor the names of its contributors may be used
 *     to endorse or promote products derived from this software without
 *     specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <hiredis/hiredis.h>

#define KEY_COUNT 5

#define panicAbort(fmt, ...)                                                            \
	do                                                                                  \
	{                                                                                   \
		fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, __VA_ARGS__); \
		exit(-1);                                                                       \
	} while (0)

static void assertReplyAndFree(redisContext *context, redisReply *reply, int type)
{
	if (reply == NULL)
		panicAbort("NULL reply from server (error: %s)", context->errstr);

	if (reply->type != type)
	{
		if (reply->type == REDIS_REPLY_ERROR)
			fprintf(stderr, "Redis Error: %s\n", reply->str);

		panicAbort("Expected reply type %d but got type %d", type, reply->type);
	}

	freeReplyObject(reply);
}

/* Switch to the RESP3 protocol and enable client tracking */
static void enableClientTracking(redisContext *c)
{
	redisReply *reply = redisCommand(c, "HELLO 3");
	if (reply == NULL || c->err)
	{
		panicAbort("NULL reply or server error (error: %s)", c->errstr);
	}

	if (reply->type != REDIS_REPLY_MAP)
	{
		fprintf(stderr, "Error: Can't send HELLO 3 command.  Are you sure you're ");
		fprintf(stderr, "connected to redis-server >= 6.0.0?\nRedis error: %s\n",
				reply->type == REDIS_REPLY_ERROR ? reply->str : "(unknown)");
		exit(-1);
	}

	freeReplyObject(reply);

	/* Enable client tracking */
	reply = redisCommand(c, "CLIENT TRACKING ON");
	assertReplyAndFree(c, reply, REDIS_REPLY_STATUS);
}

void pushReplyHandler(void *privdata, void *r)
{
	redisReply *reply = r;
	int *invalidations = privdata;

	/* Sanity check on the invalidation reply */
	if (reply->type != REDIS_REPLY_PUSH || reply->elements != 2 ||
		reply->element[1]->type != REDIS_REPLY_ARRAY ||
		reply->element[1]->element[0]->type != REDIS_REPLY_STRING)
	{
		panicAbort("%s", "Can't parse PUSH message!");
	}

	/* Increment our invalidation count */
	*invalidations += 1;

	printf("pushReplyHandler(): INVALIDATE '%s' (invalidation count: %d)\n",
		   reply->element[1]->element[0]->str, *invalidations);

	freeReplyObject(reply);
}

/* We aren't actually freeing anything here, but it is included to show that we can
 * have hiredis call our data destructor when freeing the context */
void privdata_dtor(void *privdata)
{
	unsigned int *icount = privdata;
	printf("privdata_dtor():  In context privdata dtor (invalidations: %u)\n", *icount);
}

int main(int argc, char **argv)
{
	unsigned int j, invalidations = 0;
	redisContext *c;
	redisReply *reply;

	const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1";
	int port = (argc > 2) ? atoi(argv[2]) : 6379;

	redisOptions o = {0};
	REDIS_OPTIONS_SET_TCP(&o, hostname, port);

	/* Set our context privdata to the address of our invalidation counter.  Each
     * time our PUSH handler is called, hiredis will pass the privdata for context.
     *
     * This could also be done after we create the context like so:
     *
     *    c->privdata = &invalidations;
     *    c->free_privdata = privdata_dtor;
     */
	REDIS_OPTIONS_SET_PRIVDATA(&o, &invalidations, privdata_dtor);

	/* Set our custom PUSH message handler */
	o.push_cb = pushReplyHandler;

	c = redisConnectWithOptions(&o);
	if (c == NULL || c->err)
		panicAbort("Connection error:  %s", c ? c->errstr : "OOM");

	/* Enable RESP3 and turn on client tracking */
	enableClientTracking(c);

	/* Set some keys and then read them back.  Once we do that, Redis will deliver
     * invalidation push messages whenever the key is modified */
	for (j = 0; j < KEY_COUNT; j++)
	{
		reply = redisCommand(c, "SET key:%d initial:%d", j, j);
		assertReplyAndFree(c, reply, REDIS_REPLY_STATUS);

		reply = redisCommand(c, "GET key:%d", j);
		assertReplyAndFree(c, reply, REDIS_REPLY_STRING);
	}

	/* Trigger invalidation messages by updating keys we just read */
	for (j = 0; j < KEY_COUNT; j++)
	{
		printf("            main(): SET key:%d update:%d\n", j, j);
		reply = redisCommand(c, "SET key:%d update:%d", j, j);
		assertReplyAndFree(c, reply, REDIS_REPLY_STATUS);
		printf("            main(): SET REPLY OK\n");
	}

	printf("\nTotal detected invalidations: %d, expected: %d\n", invalidations, KEY_COUNT);

	/* PING server */
	redisFree(c);
}
[[email protected] testcpp]# gcc example-push.c  -o out -lhiredis
[[email protected] testcpp]# ./out 
            main(): SET key:0 update:0
pushReplyHandler(): INVALIDATE 'key:0' (invalidation count: 1)
            main(): SET REPLY OK
            main(): SET key:1 update:1
pushReplyHandler(): INVALIDATE 'key:1' (invalidation count: 2)
            main(): SET REPLY OK
            main(): SET key:2 update:2
pushReplyHandler(): INVALIDATE 'key:2' (invalidation count: 3)
            main(): SET REPLY OK
            main(): SET key:3 update:3
pushReplyHandler(): INVALIDATE 'key:3' (invalidation count: 4)
            main(): SET REPLY OK
            main(): SET key:4 update:4
pushReplyHandler(): INVALIDATE 'key:4' (invalidation count: 5)
            main(): SET REPLY OK

Total detected invalidations: 5, expected: 5
privdata_dtor():  In context privdata dtor (invalidations: 5)
[[email protected] testcpp]# 

MyDemo

hiredis_hash_test.c

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

#include <hiredis/hiredis.h>

int main()
{
	const char *ip = "127.0.0.1"; //ip
	int port = 6379;			  //port
	redisContext *context;		  //管理連線伺服器的指標
	redisReply *reply;			  //執行命令後返回的結構體指標,儲存了處理結果

	//1.連線redis伺服器
	context = redisConnect(ip, port);
	if (context == NULL || context->err)
	{
		if (context)
		{
			printf("Connection error contex->errstr: %s\n", context->errstr);
			redisFree(context);
		}
		else
		{
			printf("Connection error: can't allocate redis context\n");
		}
		exit(1);
	}
	//2.執行操作
	reply = (redisReply *)redisCommand(context, "hmset user name zhangsanfeng age 24 sex man password 123456");
	if (reply->type == 5)
	{
		printf("命令執行完畢,狀態碼:%s\n", reply->str);
	}
	freeReplyObject(reply);
	
	// 將hash的value值讀出
	reply = (redisReply *)redisCommand(context, "hgetall user");
	if (reply->type == 2)
	{
		//遍歷陣列
		for (int i = 0; i < reply->elements; i += 2)
		{
			printf("%s : %s\n", reply->element[i]->str, reply->element[i + 1]->str);
		}
	}
	freeReplyObject(reply);

	//3.斷開連線
	redisFree(context);

	return 0;
}
[[email protected] testcpp]# gcc test.c  -o out -lhiredis 
[[email protected] testcpp]# ./out 
命令執行完畢,狀態碼:OK
name : zhangsanfeng
age : 24
sex : man
password : 123456
[[email protected] testcpp]#