Phants Client for Redis™*
Overview

Phants Client for Redis is a Redis client implementation written in C# for Unity. What is Redis?

From redis.io:

Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes with radius queries and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.

Modes of Operation

From a Redis server's perspective, clients have two fundamentally different states or modes of operation:

While it is certainly possible to create one client that jumps between these modes, we opted for a simpler approach: use SubscriptionClient if you want subscription events, and use CommandClient if you want to send commands.

Document Scope

These pages serve only to document the API provided by the client software, which simply creates a (nearly one-to-one) mapping between C# methods and Redis server commands. Here you will find, for example, everything you need to know to call the BzpopMin() method, but the behavior of the BZPOPMIN command itself is not described.

You are encouraged to check out the Redis Command Reference, as well as Redis Pub/Sub to get the most out of this package. To learn more about the underlying Redis data types, see the Redis Protocol specification.

Getting Started

First thing's first, you're going to need a Redis server of some sort. This is often as simple as using apt-get on a Linux box. This client has been tested against Redis servers running on Ubuntu server, Raspberry Pi, and the Windows 10 WSL. If you are using this client, you probably already have some idea of what you need and how you want to run it.

A Note About Versions

New commands are added with each version of Redis. Each entry in the Command Reference notes the version where the command became available (e.g. "BITFIELD - Available since 3.2.0."). While Phants Client for Redis implements nearly every command up to version 5.0, your server may not. If this is the case, some commands (and hence, CommandClient methods) will not work; the server simply will not recognize them as valid commands.

Firewalls

You may have difficulty connecting if there is a firewall involved. This is especially true when running a Redis server on Windows, as the Windows firewall typically prevents access to port 6379 by default. You may have to open access to this port.

Getting Connected

With your server up and running, you should be able to connect any number of ways with simple tools like netcat or telnet. This will allow you to try out some raw Redis commands to ensure that everything is ready to go. This is likely an easier way to test rather than jumping right into Unity. In any event, you'll connect to Redis on port number 6379. This is the standard Redis port; if you've changed the defaults, you'll obviously have to adjust for your setup.

1 nc 127.0.0.1 6379
2 set foo_key "FoO"
3 +OK
4 get foo_key
5 $3
6 FoO

Once this is working, take the client for a spin. We have a regular MonoBehaviour script attached to an empty object in the scene. On Awake(), we create the client and try to connect. A connect callback is provided by the caller; it will be called when the connection attempt has completed. If the connection was successful, error will be null.

void Awake() {
// Create a command client with send/recv buffers that are 1024 bytes each.
CommandClient cmdClient = new CommandClient("127.0.0.1", 6379, 1024, 1024);
cmdClient.Connect(OnCmdConnect);
}
void OnCmdConnect(RedisClientBase client, string error) {
if (error != null) {
Debug.Log($"Uh oh, failed to connect: {error}");
return;
}
Debug.Log("Connection success!");
}

Other code that has access to a client object can register for connection events as well, but such events are only fired when a connection is successful.

public void RegisterForConnectionEventsExample() {
this.myCmdClient.connected += ConnectedHandler;
}
// Example connection callback.
private void ConnectedHandler(RedisClientBase client) {
Debug.Log("Connected!");
}

Disconnects are done in the same fashion. The caller can provide a callback to Disconnect(), and other interested parties can register for disconnect events.

Sending Commands

Every command method in Phants Client for Redis is asynchronous, meaning you'll essentially provide a callback responsible for processing the result. They're not exactly callbacks though. Rather, they are interfaces designed to handle the set of data types that Redis supports. For lack of a better name, we call these the Sink interfaces (as in source/sink). Sinks are composed of two or more Parser interfaces. In each Sink, one of the interfaces is always an ErrorParser, since most/all Redis commands can return an error. The other Parsers in the Sink are called upon to process the data returned from a command. It may seem a bit abstract, so perhaps an example is in order:

The SCARD command returns a RESP Integer reply, so naturally, the Scard() method takes an object that implements the IntegerSink interface. This interface brings together an IntegerParser and the obligatory ErrorParser. The Sink object must implement void Integer(long), which will be called with the value that was returned:

class ScardTestSink : IntegerSink {
public void Integer(long i) {
Debug.Log($"SCARD result: {i}");
}
public void Error(string e) {
Debug.Log($"Got error string: {e}");
}
}

Now imagine your Redis server has a Set named scard_test_key, you would execute SCARD against it like so:

//Test SCARD
ScardTestSink scardSink = new ScardTestSink();
cmdClient.Scard(scardSink, "scard_test_key");

Sink Adapters

This is easy enough, but defining a custom class for every response type or method of interest is far from ideal. It was designed this way so you could define one if you needed to, but for typical use, Sink Adapter classes for each RESP type have been implemented and included with this package. Returning to the SCARD example, you can use an IntegerSinkAdapter and implement the callbacks inline with your code (or anywhere else, however you want to do it).

IntegerCallback integerCallback = (long i) => {
Debug.Log($"SCARD result: {i}");
};
ErrorCallback errorCallback = (string e) => {
Debug.Log($"Got error string: {e}");
};
IntegerSink intSink = new IntegerSinkAdapter(integerCallback, errorCallback);

Whitespace in Strings

The RESP protocol separates arguments with whitespace, so any strings containing whitespace must be quoted, or the server will interpret them incorrectly. This goes for string values (such as the value in a key/value pair), but it also goes for key names, as well as Lua scripts, or any other argument that could potentially contain whitespace. The client does not provide quotation automatically, as this would introduce unnecessary computation in situations where it is not necessary (e.g. the user wisely chooses key names that are whitespace free). However, there is a static utility method provided, QuoteWrap(string), that will return a quoted string. When in doubt, use this method to produce safe argument strings.

Subscribing and Publishing

As mentioned above, Redis has a subscriber mode. In this mode, a client subscribes to one or more channels and waits indefinitely for messages to arrive. Here, an example is provided to demonstrate how to make use of this facility.

void Awake() {
// Set up a callback for message events. This can be defined inline or as a class method.
SubscriptionClient.MessageHandler subHandler = (SubscriptionClient client, string channelName, string messageContents) => {
Debug.Log($"Got a message on channel {channelName}: {messageContents}");
};
// Create a subscription client with a 1024 byte receive buffer.
SubscriptionClient subClient = new SubscriptionClient("127.0.0.1", 6379, 1024);
// Register a message handler.
subClient.messaged += subHandler;
// Connect to the Redis server. Once connected, we can begin subscribing to channels.
subClient.Connect(OnSubConnect);
}
void OnSubConnect(RedisClientBase client, string error) {
if (error != null) {
Debug.Log($"Uh oh, failed to connect: {error}");
return;
}
Debug.Log("Connection success!");
// Subscribe to a few channels of interest.
SubscriptionClient subClient = client as SubscriptionClient;
client.Subscribe("channel_one");
client.Subscribe("channel.two");
client.Subscribe("foo");
}

Once the SubscriptionClient is running, it will receive messages on the given channels. This can be tested with any Redis client (netcat, etc). Of course, the Publish() method of CommandClient works as well:

void Update()
{
// Once per second, Publish a message.
if ((Time.frameCount % 60) == 0) {
// This IntegerSink ignores the response, we only care about publishing our messages.
IntegerSink intSink = new IntegerSinkAdapter((long i) => {}, (string e) => {Debug.Log(e);});
// Publish a message to channel_one. The 'subHandler' above will be called when the message arrives.
cmdClient.Publish(intSink, "channel_one", "Hello, channel_one subscribers!");
}
}