From 518a29c131b9723bbb97f59bcb6d7407227b5a33 Mon Sep 17 00:00:00 2001 From: Hayk Martirosyan Date: Tue, 23 Dec 2014 02:12:34 -0500 Subject: [PATCH] Initial split of redisx from robot-net repository --- .gitignore | 6 ++++++ CMakeLists.txt | 31 +++++++++++++++++++++++++++++++ LICENSE | 2 +- examples/basic.cpp | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ make.sh | 5 +++++ src/redisx.cpp | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/redisx.hpp | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 365 insertions(+), 1 deletion(-) create mode 100644 CMakeLists.txt create mode 100644 examples/basic.cpp create mode 100755 make.sh create mode 100644 src/redisx.cpp create mode 100644 src/redisx.hpp diff --git a/.gitignore b/.gitignore index b8bd026..c705c20 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,9 @@ *.exe *.out *.app + +# CMake +build/ + +# IntelliJ +.idea/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e36451a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 2.8.4) +project(redisx) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fPIC -Wall") +# set(CMAKE_VERBOSE_MAKEFILE ON) + +# --------------------------------------------------------- +# Source files +# --------------------------------------------------------- + +set(SRC_DIR ${CMAKE_SOURCE_DIR}/src) + +set(SRC_CORE + ${SRC_DIR}/redisx.cpp +) + +set(SRC_ALL ${SRC_CORE}) + +# --------------------------------------------------------- +# Libraries +# --------------------------------------------------------- + +set(LIB_REDIS hiredis event) +set(LIB_ALL ${LIB_REDIS}) + +# --------------------------------------------------------- +# Examples +# --------------------------------------------------------- + +add_executable(basic examples/basic.cpp ${SRC_ALL}) +target_link_libraries(basic ${LIB_REDIS}) diff --git a/LICENSE b/LICENSE index e06d208..500af5c 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ Apache License same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {yyyy} {name of copyright owner} + Copyright 2015 Hayk Martirosyan Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/examples/basic.cpp b/examples/basic.cpp new file mode 100644 index 0000000..bf867ed --- /dev/null +++ b/examples/basic.cpp @@ -0,0 +1,72 @@ +/** +* Basic asynchronous calls using redisx. +*/ + +#include +#include +#include "../src/redisx.hpp" + +using namespace std; + +static const string REDIS_HOST = "localhost"; +static const int REDIS_PORT = 6379; + +unsigned long time_ms() { + return chrono::system_clock::now().time_since_epoch() + /chrono::milliseconds(1); +} + +int main(int argc, char* argv[]) { + + redisx::Redis r(REDIS_HOST, REDIS_PORT); + + r.command("GET blah", [](const string& cmd, const string& value) { + cout << "[COMMAND] " << cmd << ": " << value << endl; + }); + + r.command("GET blah", [](const string& cmd, const char* value) { + cout << "[COMMAND] " << cmd << ": " << value << endl; + }); + + r.command("LPUSH yahoo 1 2 3 4 f w", [](const string& cmd, const redisReply* reply) { + cout << "[COMMAND] " << cmd << ": " << reply->integer << endl; + }); + + r.get("blahqwefwqefef", [](const string& cmd, const char* value) { + cout << "[GET] blah: " << value << endl; + }); + + r.set("name", "lolfewef"); + + r.command("SET blah wefoijewfojiwef"); + + r.del("name"); + r.del("wefoipjweojiqw", [](const string& cmd, long long int num_deleted) { + cout << "num deleted: " << num_deleted << endl; + }); + + unsigned long t0 = time_ms(); + unsigned long t1 = t0; + + int len = 1000000; + int count = 0; + + for(int i = 0; i < len; i++) { + r.command("set blah wefoiwef", [&t0, &t1, &count, len](const string& cmd, const string& reply) { + + count++; + if(count == len) { + cout << cmd << ": " << reply << endl; + cout << "Time to queue async commands: " << t1 - t0 << "ms" << endl; + cout << "Time to receive all: " << time_ms() - t1 << "ms" << endl; + cout << "Total time: " << time_ms() - t0 << "ms" << endl; + } + }); + } + t1 = time_ms(); + + thread loop([&r] { r.start(); }); + loop.join(); + + return 0; +} diff --git a/make.sh b/make.sh new file mode 100755 index 0000000..febe16a --- /dev/null +++ b/make.sh @@ -0,0 +1,5 @@ +mkdir -p build && +cd build && +cmake .. && +time make && +cd .. diff --git a/src/redisx.cpp b/src/redisx.cpp new file mode 100644 index 0000000..b5c892a --- /dev/null +++ b/src/redisx.cpp @@ -0,0 +1,142 @@ +/** +* Redis C++11 wrapper. +*/ + +#include +#include +#include +#include +#include +#include +#include "redisx.hpp" + +using namespace std; + +namespace redisx { + +void connected(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnected(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} + +Redis::Redis(const string& host, const int port) : host(host), port(port), io_ops(0) { + + signal(SIGPIPE, SIG_IGN); + base = event_base_new(); + + c = redisAsyncConnect(host.c_str(), port); + if (c->err) { + printf("Error: %s\n", c->errstr); + return; + } + + redisLibeventAttach(c, base); + redisAsyncSetConnectCallback(c, connected); + redisAsyncSetDisconnectCallback(c, disconnected); +} + +Redis::~Redis() { + redisAsyncDisconnect(c); +} + +void Redis::start() { + event_base_dispatch(base); +} + +void Redis::command(const char* cmd) { + int status = redisAsyncCommand(c, NULL, NULL, cmd); + if (status != REDIS_OK) { + cerr << "[ERROR] Async command \"" << cmd << "\": " << c->errstr << endl; + return; + } +} + +// ---------------------------- + +template<> +void invoke_callback(const CommandAsync* cmd_obj, redisReply* reply) { + cmd_obj->invoke(reply); +} + +template<> +void invoke_callback(const CommandAsync* cmd_obj, redisReply* reply) { + if(reply->type != REDIS_REPLY_STRING && reply->type != REDIS_REPLY_STATUS) { + cerr << "[ERROR] " << cmd_obj->cmd << ": Received non-string reply." << endl; + return; + } + + cmd_obj->invoke(reply->str); +} + +template<> +void invoke_callback(const CommandAsync* cmd_obj, redisReply* reply) { + if(reply->type != REDIS_REPLY_STRING && reply->type != REDIS_REPLY_STATUS) { + cerr << "[ERROR] " << cmd_obj->cmd << ": Received non-string reply." << endl; + return; + } + cmd_obj->invoke(reply->str); +} + +template<> +void invoke_callback(const CommandAsync* cmd_obj, redisReply* reply) { + if(reply->type != REDIS_REPLY_INTEGER) { + cerr << "[ERROR] " << cmd_obj->cmd << ": Received non-integer reply." << endl; + return; + } + cmd_obj->invoke((int)reply->integer); +} + +template<> +void invoke_callback(const CommandAsync* cmd_obj, redisReply* reply) { + if(reply->type != REDIS_REPLY_INTEGER) { + cerr << "[ERROR] " << cmd_obj->cmd << ": Received non-integer reply." << endl; + return; + } + cmd_obj->invoke(reply->integer); +} + +// ---------------------------- + +void Redis::get(const char* key, function callback) { + string cmd = string("GET ") + key; + command(cmd.c_str(), callback); +} + +void Redis::set(const char* key, const char* value) { + string cmd = string("SET ") + key + " " + value; + command(cmd.c_str(), [](const string& command, const char* reply) { + if(strcmp(reply, "OK")) + cerr << "[ERROR] " << command << ": SET failed with reply " << reply << endl; + }); +} + +void Redis::set(const char* key, const char* value, std::function callback) { + string cmd = string("SET ") + key + " " + value; + command(cmd.c_str(), callback); +} + +void Redis::del(const char* key) { + string cmd = string("DEL ") + key; + command(cmd.c_str(), [](const string& command, long long int num_deleted) { + if(num_deleted != 1) + cerr << "[ERROR] " << command << ": Deleted " << num_deleted << " keys." << endl; + }); +} + +void Redis::del(const char* key, std::function callback) { + string cmd = string("DEL ") + key; + command(cmd.c_str(), callback); +} + +} // End namespace redis diff --git a/src/redisx.hpp b/src/redisx.hpp new file mode 100644 index 0000000..9b3ad62 --- /dev/null +++ b/src/redisx.hpp @@ -0,0 +1,108 @@ +/** +* Redis C++11 wrapper. +*/ + +#pragma once + +#include +#include +#include +#include +#include + +namespace redisx { + +class Redis { + +public: + + Redis(const std::string& host, const int port); + ~Redis(); + + void start(); + + template + void command( + const std::string& cmd, + const std::function& callback + ); + + void command(const char* command); + + void get(const char* key, std::function callback); + + void set(const char* key, const char* value); + void set(const char* key, const char* value, std::function callback); + + void del(const char* key); + void del(const char* key, std::function callback); + +// void publish(std::string channel, std::string msg); +// void subscribe(std::string channel, std::function callback); +// void unsubscribe(std::string channel); + +private: + + // Redis server + std::string host; + int port; + + // Number of IOs performed + long io_ops; + + struct event_base *base; + redisAsyncContext *c; +}; + +template +class CommandAsync { +public: + CommandAsync(const std::string& cmd, const std::function& callback) + : cmd(cmd), callback(callback) {} + const std::string cmd; + const std::function callback; + void invoke(ReplyT reply) const {if(callback != NULL) callback(cmd, reply); } +}; + +template +void invoke_callback( + const CommandAsync* cmd_obj, + redisReply* reply +); + +template +void command_callback(redisAsyncContext *c, void *r, void *privdata) { + + redisReply *reply = (redisReply *) r; + auto *cmd_obj = (CommandAsync *) privdata; + + if (reply->type == REDIS_REPLY_ERROR) { + std::cerr << "[ERROR] " << cmd_obj->cmd << ": " << reply->str << std::endl; + delete cmd_obj; + return; + } + + if(reply->type == REDIS_REPLY_NIL) { + std::cerr << "[ERROR] " << cmd_obj->cmd << ": Nil reply." << std::endl; + delete cmd_obj; + return; // cmd_obj->invoke(NULL); + } + + invoke_callback(cmd_obj, reply); + delete cmd_obj; +} + +template +void Redis::command(const std::string& cmd, const std::function& callback) { + + auto *cmd_obj = new CommandAsync(cmd, callback); + + int status = redisAsyncCommand(c, command_callback, (void*)cmd_obj, cmd.c_str()); + if (status != REDIS_OK) { + std::cerr << "[ERROR] Async command \"" << cmd << "\": " << c->errstr << std::endl; + delete cmd_obj; + return; + } +} + +} // End namespace redis -- libgit2 0.21.4