Commit 518a29c131b9723bbb97f59bcb6d7407227b5a33
1 parent
583c6fe3
Initial split of redisx from robot-net repository
Basic CMake setup, redisx implementation, and one example program.
Showing
7 changed files
with
365 additions
and
1 deletions
.gitignore
CMakeLists.txt
0 โ 100644
| 1 | +cmake_minimum_required(VERSION 2.8.4) | |
| 2 | +project(redisx) | |
| 3 | + | |
| 4 | +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fPIC -Wall") | |
| 5 | +# set(CMAKE_VERBOSE_MAKEFILE ON) | |
| 6 | + | |
| 7 | +# --------------------------------------------------------- | |
| 8 | +# Source files | |
| 9 | +# --------------------------------------------------------- | |
| 10 | + | |
| 11 | +set(SRC_DIR ${CMAKE_SOURCE_DIR}/src) | |
| 12 | + | |
| 13 | +set(SRC_CORE | |
| 14 | + ${SRC_DIR}/redisx.cpp | |
| 15 | +) | |
| 16 | + | |
| 17 | +set(SRC_ALL ${SRC_CORE}) | |
| 18 | + | |
| 19 | +# --------------------------------------------------------- | |
| 20 | +# Libraries | |
| 21 | +# --------------------------------------------------------- | |
| 22 | + | |
| 23 | +set(LIB_REDIS hiredis event) | |
| 24 | +set(LIB_ALL ${LIB_REDIS}) | |
| 25 | + | |
| 26 | +# --------------------------------------------------------- | |
| 27 | +# Examples | |
| 28 | +# --------------------------------------------------------- | |
| 29 | + | |
| 30 | +add_executable(basic examples/basic.cpp ${SRC_ALL}) | |
| 31 | +target_link_libraries(basic ${LIB_REDIS}) | ... | ... |
LICENSE
| ... | ... | @@ -186,7 +186,7 @@ Apache License |
| 186 | 186 | same "printed page" as the copyright notice for easier |
| 187 | 187 | identification within third-party archives. |
| 188 | 188 | |
| 189 | - Copyright {yyyy} {name of copyright owner} | |
| 189 | + Copyright 2015 Hayk Martirosyan | |
| 190 | 190 | |
| 191 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); |
| 192 | 192 | you may not use this file except in compliance with the License. | ... | ... |
examples/basic.cpp
0 โ 100644
| 1 | +/** | |
| 2 | +* Basic asynchronous calls using redisx. | |
| 3 | +*/ | |
| 4 | + | |
| 5 | +#include <iostream> | |
| 6 | +#include <thread> | |
| 7 | +#include "../src/redisx.hpp" | |
| 8 | + | |
| 9 | +using namespace std; | |
| 10 | + | |
| 11 | +static const string REDIS_HOST = "localhost"; | |
| 12 | +static const int REDIS_PORT = 6379; | |
| 13 | + | |
| 14 | +unsigned long time_ms() { | |
| 15 | + return chrono::system_clock::now().time_since_epoch() | |
| 16 | + /chrono::milliseconds(1); | |
| 17 | +} | |
| 18 | + | |
| 19 | +int main(int argc, char* argv[]) { | |
| 20 | + | |
| 21 | + redisx::Redis r(REDIS_HOST, REDIS_PORT); | |
| 22 | + | |
| 23 | + r.command<const string&>("GET blah", [](const string& cmd, const string& value) { | |
| 24 | + cout << "[COMMAND] " << cmd << ": " << value << endl; | |
| 25 | + }); | |
| 26 | + | |
| 27 | + r.command<const char*>("GET blah", [](const string& cmd, const char* value) { | |
| 28 | + cout << "[COMMAND] " << cmd << ": " << value << endl; | |
| 29 | + }); | |
| 30 | + | |
| 31 | + r.command<const redisReply*>("LPUSH yahoo 1 2 3 4 f w", [](const string& cmd, const redisReply* reply) { | |
| 32 | + cout << "[COMMAND] " << cmd << ": " << reply->integer << endl; | |
| 33 | + }); | |
| 34 | + | |
| 35 | + r.get("blahqwefwqefef", [](const string& cmd, const char* value) { | |
| 36 | + cout << "[GET] blah: " << value << endl; | |
| 37 | + }); | |
| 38 | + | |
| 39 | + r.set("name", "lolfewef"); | |
| 40 | + | |
| 41 | + r.command("SET blah wefoijewfojiwef"); | |
| 42 | + | |
| 43 | + r.del("name"); | |
| 44 | + r.del("wefoipjweojiqw", [](const string& cmd, long long int num_deleted) { | |
| 45 | + cout << "num deleted: " << num_deleted << endl; | |
| 46 | + }); | |
| 47 | + | |
| 48 | + unsigned long t0 = time_ms(); | |
| 49 | + unsigned long t1 = t0; | |
| 50 | + | |
| 51 | + int len = 1000000; | |
| 52 | + int count = 0; | |
| 53 | + | |
| 54 | + for(int i = 0; i < len; i++) { | |
| 55 | + r.command<const string&>("set blah wefoiwef", [&t0, &t1, &count, len](const string& cmd, const string& reply) { | |
| 56 | + | |
| 57 | + count++; | |
| 58 | + if(count == len) { | |
| 59 | + cout << cmd << ": " << reply << endl; | |
| 60 | + cout << "Time to queue async commands: " << t1 - t0 << "ms" << endl; | |
| 61 | + cout << "Time to receive all: " << time_ms() - t1 << "ms" << endl; | |
| 62 | + cout << "Total time: " << time_ms() - t0 << "ms" << endl; | |
| 63 | + } | |
| 64 | + }); | |
| 65 | + } | |
| 66 | + t1 = time_ms(); | |
| 67 | + | |
| 68 | + thread loop([&r] { r.start(); }); | |
| 69 | + loop.join(); | |
| 70 | + | |
| 71 | + return 0; | |
| 72 | +} | ... | ... |
make.sh
0 โ 100755
src/redisx.cpp
0 โ 100644
| 1 | +/** | |
| 2 | +* Redis C++11 wrapper. | |
| 3 | +*/ | |
| 4 | + | |
| 5 | +#include <signal.h> | |
| 6 | +#include <iostream> | |
| 7 | +#include <thread> | |
| 8 | +#include <hiredis/adapters/libevent.h> | |
| 9 | +#include <vector> | |
| 10 | +#include <string.h> | |
| 11 | +#include "redisx.hpp" | |
| 12 | + | |
| 13 | +using namespace std; | |
| 14 | + | |
| 15 | +namespace redisx { | |
| 16 | + | |
| 17 | +void connected(const redisAsyncContext *c, int status) { | |
| 18 | + if (status != REDIS_OK) { | |
| 19 | + printf("Error: %s\n", c->errstr); | |
| 20 | + return; | |
| 21 | + } | |
| 22 | + printf("Connected...\n"); | |
| 23 | +} | |
| 24 | + | |
| 25 | +void disconnected(const redisAsyncContext *c, int status) { | |
| 26 | + if (status != REDIS_OK) { | |
| 27 | + printf("Error: %s\n", c->errstr); | |
| 28 | + return; | |
| 29 | + } | |
| 30 | + printf("Disconnected...\n"); | |
| 31 | +} | |
| 32 | + | |
| 33 | +Redis::Redis(const string& host, const int port) : host(host), port(port), io_ops(0) { | |
| 34 | + | |
| 35 | + signal(SIGPIPE, SIG_IGN); | |
| 36 | + base = event_base_new(); | |
| 37 | + | |
| 38 | + c = redisAsyncConnect(host.c_str(), port); | |
| 39 | + if (c->err) { | |
| 40 | + printf("Error: %s\n", c->errstr); | |
| 41 | + return; | |
| 42 | + } | |
| 43 | + | |
| 44 | + redisLibeventAttach(c, base); | |
| 45 | + redisAsyncSetConnectCallback(c, connected); | |
| 46 | + redisAsyncSetDisconnectCallback(c, disconnected); | |
| 47 | +} | |
| 48 | + | |
| 49 | +Redis::~Redis() { | |
| 50 | + redisAsyncDisconnect(c); | |
| 51 | +} | |
| 52 | + | |
| 53 | +void Redis::start() { | |
| 54 | + event_base_dispatch(base); | |
| 55 | +} | |
| 56 | + | |
| 57 | +void Redis::command(const char* cmd) { | |
| 58 | + int status = redisAsyncCommand(c, NULL, NULL, cmd); | |
| 59 | + if (status != REDIS_OK) { | |
| 60 | + cerr << "[ERROR] Async command \"" << cmd << "\": " << c->errstr << endl; | |
| 61 | + return; | |
| 62 | + } | |
| 63 | +} | |
| 64 | + | |
| 65 | +// ---------------------------- | |
| 66 | + | |
| 67 | +template<> | |
| 68 | +void invoke_callback(const CommandAsync<const redisReply*>* cmd_obj, redisReply* reply) { | |
| 69 | + cmd_obj->invoke(reply); | |
| 70 | +} | |
| 71 | + | |
| 72 | +template<> | |
| 73 | +void invoke_callback(const CommandAsync<const string&>* cmd_obj, redisReply* reply) { | |
| 74 | + if(reply->type != REDIS_REPLY_STRING && reply->type != REDIS_REPLY_STATUS) { | |
| 75 | + cerr << "[ERROR] " << cmd_obj->cmd << ": Received non-string reply." << endl; | |
| 76 | + return; | |
| 77 | + } | |
| 78 | + | |
| 79 | + cmd_obj->invoke(reply->str); | |
| 80 | +} | |
| 81 | + | |
| 82 | +template<> | |
| 83 | +void invoke_callback(const CommandAsync<const char*>* cmd_obj, redisReply* reply) { | |
| 84 | + if(reply->type != REDIS_REPLY_STRING && reply->type != REDIS_REPLY_STATUS) { | |
| 85 | + cerr << "[ERROR] " << cmd_obj->cmd << ": Received non-string reply." << endl; | |
| 86 | + return; | |
| 87 | + } | |
| 88 | + cmd_obj->invoke(reply->str); | |
| 89 | +} | |
| 90 | + | |
| 91 | +template<> | |
| 92 | +void invoke_callback(const CommandAsync<int>* cmd_obj, redisReply* reply) { | |
| 93 | + if(reply->type != REDIS_REPLY_INTEGER) { | |
| 94 | + cerr << "[ERROR] " << cmd_obj->cmd << ": Received non-integer reply." << endl; | |
| 95 | + return; | |
| 96 | + } | |
| 97 | + cmd_obj->invoke((int)reply->integer); | |
| 98 | +} | |
| 99 | + | |
| 100 | +template<> | |
| 101 | +void invoke_callback(const CommandAsync<long long int>* cmd_obj, redisReply* reply) { | |
| 102 | + if(reply->type != REDIS_REPLY_INTEGER) { | |
| 103 | + cerr << "[ERROR] " << cmd_obj->cmd << ": Received non-integer reply." << endl; | |
| 104 | + return; | |
| 105 | + } | |
| 106 | + cmd_obj->invoke(reply->integer); | |
| 107 | +} | |
| 108 | + | |
| 109 | +// ---------------------------- | |
| 110 | + | |
| 111 | +void Redis::get(const char* key, function<void(const string&, const char*)> callback) { | |
| 112 | + string cmd = string("GET ") + key; | |
| 113 | + command<const char*>(cmd.c_str(), callback); | |
| 114 | +} | |
| 115 | + | |
| 116 | +void Redis::set(const char* key, const char* value) { | |
| 117 | + string cmd = string("SET ") + key + " " + value; | |
| 118 | + command<const char*>(cmd.c_str(), [](const string& command, const char* reply) { | |
| 119 | + if(strcmp(reply, "OK")) | |
| 120 | + cerr << "[ERROR] " << command << ": SET failed with reply " << reply << endl; | |
| 121 | + }); | |
| 122 | +} | |
| 123 | + | |
| 124 | +void Redis::set(const char* key, const char* value, std::function<void(const string&, const char*)> callback) { | |
| 125 | + string cmd = string("SET ") + key + " " + value; | |
| 126 | + command<const char*>(cmd.c_str(), callback); | |
| 127 | +} | |
| 128 | + | |
| 129 | +void Redis::del(const char* key) { | |
| 130 | + string cmd = string("DEL ") + key; | |
| 131 | + command<long long int>(cmd.c_str(), [](const string& command, long long int num_deleted) { | |
| 132 | + if(num_deleted != 1) | |
| 133 | + cerr << "[ERROR] " << command << ": Deleted " << num_deleted << " keys." << endl; | |
| 134 | + }); | |
| 135 | +} | |
| 136 | + | |
| 137 | +void Redis::del(const char* key, std::function<void(const string&, long long int)> callback) { | |
| 138 | + string cmd = string("DEL ") + key; | |
| 139 | + command<long long int>(cmd.c_str(), callback); | |
| 140 | +} | |
| 141 | + | |
| 142 | +} // End namespace redis | ... | ... |
src/redisx.hpp
0 โ 100644
| 1 | +/** | |
| 2 | +* Redis C++11 wrapper. | |
| 3 | +*/ | |
| 4 | + | |
| 5 | +#pragma once | |
| 6 | + | |
| 7 | +#include <functional> | |
| 8 | +#include <string> | |
| 9 | +#include <iostream> | |
| 10 | +#include <hiredis/hiredis.h> | |
| 11 | +#include <hiredis/async.h> | |
| 12 | + | |
| 13 | +namespace redisx { | |
| 14 | + | |
| 15 | +class Redis { | |
| 16 | + | |
| 17 | +public: | |
| 18 | + | |
| 19 | + Redis(const std::string& host, const int port); | |
| 20 | + ~Redis(); | |
| 21 | + | |
| 22 | + void start(); | |
| 23 | + | |
| 24 | + template<class ReplyT> | |
| 25 | + void command( | |
| 26 | + const std::string& cmd, | |
| 27 | + const std::function<void(const std::string&, ReplyT)>& callback | |
| 28 | + ); | |
| 29 | + | |
| 30 | + void command(const char* command); | |
| 31 | + | |
| 32 | + void get(const char* key, std::function<void(const std::string&, const char*)> callback); | |
| 33 | + | |
| 34 | + void set(const char* key, const char* value); | |
| 35 | + void set(const char* key, const char* value, std::function<void(const std::string&, const char*)> callback); | |
| 36 | + | |
| 37 | + void del(const char* key); | |
| 38 | + void del(const char* key, std::function<void(const std::string&, long long int)> callback); | |
| 39 | + | |
| 40 | +// void publish(std::string channel, std::string msg); | |
| 41 | +// void subscribe(std::string channel, std::function<void(std::string channel, std::string msg)> callback); | |
| 42 | +// void unsubscribe(std::string channel); | |
| 43 | + | |
| 44 | +private: | |
| 45 | + | |
| 46 | + // Redis server | |
| 47 | + std::string host; | |
| 48 | + int port; | |
| 49 | + | |
| 50 | + // Number of IOs performed | |
| 51 | + long io_ops; | |
| 52 | + | |
| 53 | + struct event_base *base; | |
| 54 | + redisAsyncContext *c; | |
| 55 | +}; | |
| 56 | + | |
| 57 | +template<class ReplyT> | |
| 58 | +class CommandAsync { | |
| 59 | +public: | |
| 60 | + CommandAsync(const std::string& cmd, const std::function<void(const std::string&, ReplyT)>& callback) | |
| 61 | + : cmd(cmd), callback(callback) {} | |
| 62 | + const std::string cmd; | |
| 63 | + const std::function<void(const std::string&, ReplyT)> callback; | |
| 64 | + void invoke(ReplyT reply) const {if(callback != NULL) callback(cmd, reply); } | |
| 65 | +}; | |
| 66 | + | |
| 67 | +template<class ReplyT> | |
| 68 | +void invoke_callback( | |
| 69 | + const CommandAsync<ReplyT>* cmd_obj, | |
| 70 | + redisReply* reply | |
| 71 | +); | |
| 72 | + | |
| 73 | +template<class ReplyT> | |
| 74 | +void command_callback(redisAsyncContext *c, void *r, void *privdata) { | |
| 75 | + | |
| 76 | + redisReply *reply = (redisReply *) r; | |
| 77 | + auto *cmd_obj = (CommandAsync<ReplyT> *) privdata; | |
| 78 | + | |
| 79 | + if (reply->type == REDIS_REPLY_ERROR) { | |
| 80 | + std::cerr << "[ERROR] " << cmd_obj->cmd << ": " << reply->str << std::endl; | |
| 81 | + delete cmd_obj; | |
| 82 | + return; | |
| 83 | + } | |
| 84 | + | |
| 85 | + if(reply->type == REDIS_REPLY_NIL) { | |
| 86 | + std::cerr << "[ERROR] " << cmd_obj->cmd << ": Nil reply." << std::endl; | |
| 87 | + delete cmd_obj; | |
| 88 | + return; // cmd_obj->invoke(NULL); | |
| 89 | + } | |
| 90 | + | |
| 91 | + invoke_callback<ReplyT>(cmd_obj, reply); | |
| 92 | + delete cmd_obj; | |
| 93 | +} | |
| 94 | + | |
| 95 | +template<class ReplyT> | |
| 96 | +void Redis::command(const std::string& cmd, const std::function<void(const std::string&, ReplyT)>& callback) { | |
| 97 | + | |
| 98 | + auto *cmd_obj = new CommandAsync<ReplyT>(cmd, callback); | |
| 99 | + | |
| 100 | + int status = redisAsyncCommand(c, command_callback<ReplyT>, (void*)cmd_obj, cmd.c_str()); | |
| 101 | + if (status != REDIS_OK) { | |
| 102 | + std::cerr << "[ERROR] Async command \"" << cmd << "\": " << c->errstr << std::endl; | |
| 103 | + delete cmd_obj; | |
| 104 | + return; | |
| 105 | + } | |
| 106 | +} | |
| 107 | + | |
| 108 | +} // End namespace redis | ... | ... |