diff --git a/README.md b/README.md index 838b438..20fb285 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,198 @@ Local Redis server, TCP connection: Results are comparable to that of an average laptop. On a high-end machine, `speed_test_async_multi` usually tops 1,000,000 commands/s. +## Tutorial +This section introduces the main features of redox. Look in the `examples/` for more inspiration. + +#### Hello world +Here is the simplest possible redox program: + + #include + #include + + using namespace std; + using namespace redox; + + int main(int argc, char* argv[]) { + + Redox rdx = {"localhost", 6379}; + if(!rdx.connect()) return 1; + + rdx.set("hello", "world!"); + cout << "Hello, " << rdx.get("hello") << endl; + + rdx.disconnect(); + return 0; + } + +Compile and run: + + $ g++ hello.cpp -o hello -std=c++11 -lredox -lev -lhiredis + $ ./hello + Hello, world! + +This example is synchronous, in the sense that the commands don't return until +a reply is received from the server. + +#### Asynchronous commands +In a high-performance application, we don't want to wait for a reply, but instead +do other work. +The `command` method accepts a Redis command and a callback to be invoked when a reply is received. + + rdx.command("GET hello", [](Command& c) { + if(c.ok()) { + cout << "Hello, async " << c.reply() << endl; + } else { + cerr << "Command has error code " << c.status() << endl; + } + }); + +This statement tells redox to run the command `GET hello`. The `` template +parameter means that we want the reply to be put into a string and that we expect +the server to respond with something that can be put into a string. The full list +of reply types is listed in this document and covers convenient access to anything +returned from the Redis protocol. + +The second argument is a callback function that accepts a reference to a Command object +of the requested reply type. The Command object contains the reply and any error +information. If `c.ok()` is true, the expected reply is accessed from +`c.reply()` (a string in this case). If `c.ok()` is false, then the error +code is given by `c.status()`, which can report an error or nil reply, a reply of +the wrong type, a send error, etc. The callback is guaranteed to be invoked +exactly once, and the memory for the Command object is freed automatically once +the callback returns. + +Here is a simple example of running `GET hello` asynchronously ten times: + + Redox rdx; // Localhost by default + if(!rdx.connect()) return 1; // Block until connected + + auto got_reply = [](Command& c) { + if(!c.ok()) return; + cout << c.cmd() << ": " << c.reply() << endl; + }; + + for(int i = 0; i < 10; i++) rdx.command("GET hello", got_reply); + + // Do useful work + this_thread::sleep_for(chrono::milliseconds(10)); + + rdx.disconnect(); // Block until disconnected + +The `.command()` method returns immediately, so this program doesn't wait for a reply +from the server - it just pauses for ten milliseconds and then shuts down. If we want to +shut down after we get all replies, we could do something like this: + + Redox rdx; + if(!rdx.connect()) return 1; + + int total = 10; // Number of commands to run + atomic_int count(0); // Number of replies expected + auto got_reply = [&](Command& c) { + count++; + if(c.ok()) cout << c.cmd() << " #" << count << ": " << c.reply() << endl; + if(count == total) rdx.stop(); // Signal to shut down + }; + + for(int i = 0; i < total; i++) rdx.command("GET hello", got_reply); + + // Do useful work + + rdx.wait(); // Block until shut down complete + +This example tracks of how how many replies are received and signals the Redox +instance to stop once they all process. We use an `std::atomic_int` to be safe +because the callback is invoked from a separate thread. The `stop()` method +signals Redox to shut down its event loop and disconnect from Redis. The `wait()` +method blocks until `stop()` has been called and everything is brought down. +The `disconnect()` method used earlier is just a call to `stop()` and then a +call to `wait()`. + +#### Synchronous commands +Redox implements synchronous commands by running asynchronous commands and waiting +on them with condition variables. That way, we can reap the benefits of pipelining +between synchronous commands in different threads. The `commandSync` method provides +a similar API to `command`, but instead of a callback returns a Command object when +a reply is received. + + Command& c = rdx.commandSync("GET hello"); + if(c.ok()) cout << c.cmd() << ": " << c.reply() << endl; + c.free(); + +When using synchronous commands, the user is responsible for freeing the memory of +the Command object by calling `c.free()`. The `c.cmd()` method just returns the +command string (`GET hello` in this case). + +#### Looping and delayed commands +We often want to run commands on regular invervals. Redox provides the `commandLoop` +method to accomplish this. It is easier to use and more efficient than running individual +commands in a loop, because it only creates a single Command object. +`commandLoop` takes a command string, a callback, and an interval (in seconds) +to repeat the command. It then runs the command on the given interval until the user +calls `c.free()`. + + Command& cmd = rdx.commandLoop("GET hello", [](Command& c) { + if(c.ok()) cout << c.cmd() << ": " << c.reply() << endl; + }, 0.1); + + this_thread::sleep_for(chrono::seconds(1)); + cmd.free(); + rdx.disconnect(); + +Finally, `commandDelayed` runs a command after a specified delay (in seconds). It does +not return a command object, because the memory is automatically freed after the callback +is invoked. + + rdx.commandDelayed("GET hello", [](Command& c) { + if(c.ok()) cout << c.cmd() << ": " << c.reply() << endl; + }, 1); + this_thread::sleep_for(chrono::seconds(2)); + +#### Convenience methods +The four methods `command`, `commandSync`, `commandLoop`, and `commandDelayed` form +the core of Redox's functionality. There are convenience methods provided that are +simple wrappers over the core methods. Some examples of those are `.get()`, `.set()`, +`.del()`, and `.publish()`. These methods are nice because they return simple values, +and there are no Command objects or template parameters. However, they make strong +assumptions about how to deal with errors (ignore or throw exceptions), and since +their implementations are a few lines of code it is often easier to create custom +convenience methods for your application. + +#### Publisher / Subscriber +Redox provides an API for the pub/sub functionality of Redis. Publishing is done just like +any other command using a Redox instance. There is a separate Subscriber class that +receives messages and provides subscribe/unsubscribe and psubscribe/punsubscribe methods. + + Redox rdx; Subscriber sub; + if(!rdx.connect() || !sub.connect()) return 1; + + sub.subscribe("hello", [](const string& topic, const string& msg) { + cout << topic << ": " << msg << endl; + }); + + for(int i = 0; i < 10; i++) { + rdx.publish("hello", "this is a pubsub message"); + this_thread::sleep_for(chrono::milliseconds(500)); + } + + sub.disconnect(); rdx.disconnect(); + +## Reply types +These the available template parameters in redox and the Redis +[return types](http://redis.io/topics/protocol) they can hold. +If a given command returns an incompatible type you will get +a `WRONG_TYPE` or `NIL_REPLY` status. + + * ``: All reply types, returns the hiredis struct directly + * ``: Simple Strings, Bulk Strings + * ``: Simple Strings, Bulk Strings + * ``: Integers + * ``: Integers (careful about overflow, `long long int` recommended) + * ``: Null Bulk Strings, any other receiving a nil reply will get a NIL_REPLY status + * `>`: Arrays of Simple Strings or Bulk Strings (in received order) + * `>`: Arrays of Simple Strings or Bulk Strings (in sorted order) + * `>`: Arrays of Simple Strings or Bulk Strings (in no order) + ## Installation Instructions provided are for Ubuntu, but all components are platform-independent. @@ -72,27 +264,3 @@ then: cmake -Dtests=ON .. make test_redox ./test_redox - -## Tutorial -Here is a hello world program for redox: - - #include - #include "redox.hpp" - - int main(int argc, char* argv[]) { - - redox::Redox rdx = {"localhost", 6379}; - if(!rdx.connect()) return 1; - - rdx.set("hello", "world!"); - std::cout << "Hello, " << rdx.get("hello") << std::endl; - - rdx.disconnect(); - return 0; - } - -Compile and run: - - $ g++ hello.cpp -o hello -std=c++11 -lredox -lev -lhiredis - $ ./hello - Hello, world! diff --git a/examples/basic.cpp b/examples/basic.cpp index a7be0e2..0a8e8bb 100644 --- a/examples/basic.cpp +++ b/examples/basic.cpp @@ -3,15 +3,16 @@ */ #include -#include "redox.hpp" +#include using namespace std; using redox::Redox; using redox::Command; +using redox::Subscriber; int main(int argc, char* argv[]) { - Redox rdx = {"localhost", 6379, nullptr, cout, redox::log::Info}; // Initialize Redox + Redox rdx = {"localhost", 6379}; // Initialize Redox if(!rdx.connect()) return 1; // Start the event loop @@ -22,6 +23,5 @@ int main(int argc, char* argv[]) { cout << "key = \"occupation\", value = \"" << rdx.get("occupation") << "\"" << endl; - rdx.disconnect(); return 0; }