前言
redis客户端与redis服务器通信是属于CS通讯模型:客户发送一个请求给服务器,然后客户端等待服务器的返回;服务器收到请求后,解析请求并返回客户端想要的数据或者返回一个错误。
通常来说,客户端与服务器通讯都会遵守一定的规则,这个规则又称为协议。协议有公开有协议,也有私有的协议。著名的公开协议有HTTP、FTP等,它们都是构建在TCP层之上的应用层协议。而redis客户端与redis服务器主要使用RESP 2(redis6之前的版本)和RESP 3(redis6)协议来进行通讯。
目前个人已经开源了一个redis C++客户端:github地址
项目介绍
1. 项目主要在windows平台使用vs2019进行开发,github上的代码可以直接下载下来打开解决方案并进行编译调试。在linux下,可以使用cmake进行编译,CMakeLists.txt同时也已经提供。
2. 项目中并没有使用任何的网络库,也没有其他第三方依赖,所以编译将非常简单
3. 仅仅要求使用C++11
简单使用
1. 连接redis服务器
std::string ip("81.71.11.65");
int port = 6379;
auto client = std::make_shared<RClient>(ip, port);
int ret = client->connect("123456");
if (ret != 0)
{
std::cout << "Err: " << client->strerror() << std::endl;
}
else
{
std::cout << "connect successfully..." << std::endl;
}
2. 执行redis命令
std::string ip("81.71.11.65");
int port = 6379;
auto client = std::make_shared<RClient>(ip, port);
int ret = client->connect("123456");
if (ret != 0)
{
std::cout << "Err: " << client->strerror() << std::endl;
}
else
{
std::cout << "connect successfully..." << std::endl;
}
//transfer to RESP 3
ret = client->use_resp3();
if (ret != 0)
{
return 1;
}
std::string cmd = "sadd test_set 101 102\r\n";
int written_len = client->command(cmd.c_str(), cmd.size());
if (written_len != cmd.size())
{
//send error
return 1;
}
int ret_code = 0;
auto result_ptr = client->get_results(ret_code);
if (result_ptr)
{
if (result_ptr->value_type() == ParserType::Number)
{
auto ptr = std::dynamic_pointer_cast<RedisValue>(result_ptr);
std::cout << "result : " << ptr->u.int_val_ << std::endl;
}
}
else
{
std::cout << "get results error: " << ret_code << std::endl;
}
3. 使用高级数据结构
std::string ip("81.71.77.77");
int port = 6379;
auto client = std::make_shared<RClient>(ip, port);
int ret = client->connect("123456");
if (ret != 0)
{
std::cout << "Err: " << client->strerror() << std::endl;
return;
}
ret = client->use_resp3();
if (ret != 0)
{
return;
}
auto set_client = std::make_shared<RdSet>(client);
int count = set_client->sadd("test_set", { 10, 9, 8 });
std::cout << "set count: " << count << std::endl;
auto results = set_client->smembers("test_set");
if (results)
{
if (results->value_type() != ParserType::Set
&& results->value_type() != ParserType::Array)
{
return;
}
std::cout << "values:";
auto ptr = std::dynamic_pointer_cast<RedisComplexValue>(results);
auto it = ptr->results.begin();
for (; it != ptr->results.end(); ++it)
{
if ((*it)->is_string())
{
auto ptr = std::dynamic_pointer_cast<RedisValue>(*it);
std::cout << " " << ptr->str_val_;
}
}
std::cout << std::endl;
}
bool flag = set_client->is_member("test_set", 2);
std::cout << "flag:" << flag << std::endl;
教程(原理)
使用rclientpp的核心是理解client->get_results(ret_code)的返回,接口定义如下:
class RClient
{
public:
RClient(const std::string& ipstr, int port);
...
std::shared_ptr<BaseValue> get_results(int& ret_code);
};
get_results将返回一个BaseValue的shared_ptr, BaseValue的定义如下:
class BaseValue
{
public:
BaseValue(ParserType type)
:type_(type)
{}
ParserType value_type() const
{
return type_;
}
public:
ParserType type_;
};
class RedisValue : public BaseValue
{
public:
RedisValue(ParserType type)
:BaseValue(type)
{
}
RedisValue(int64_t val)
:BaseValue(ParserType::Number)
{
u.int_val_ = val;
}
RedisValue(long double val)
:BaseValue(ParserType::Double)
{
u.d_val_ = val;
}
RedisValue(bool val)
:BaseValue(ParserType::Boolean)
{
u.boolean_val_ = val;
}
RedisValue(const std::string& val, ParserType type)
:BaseValue(type),
str_val_(val)
{
}
RedisValue(const std::string&& val, ParserType type)
:BaseValue(type),
str_val_(val)
{
}
virtual bool is_ok()
{
if (value_type() == ParserType::SimpleString)
{
if (str_val_ == std::string("OK"))
{
return true;
}
}
return false;
}
public:
union
{
int64_t int_val_;
long double d_val_;
bool boolean_val_;
} u;
std::string str_val_;
};
class RedisComplexValue : public BaseValue
{
public:
RedisComplexValue(ParserType type)
:BaseValue(type)
{
}
std::list<std::shared_ptr<BaseValue>> results;
int count{0};
};
可以看到,RedisValue与RedisComplexValue都继承自BaseValue,就是说get_results()返回的将会是RedisValue或者RedisComplexValue,BaseValue是作为抽象基类存在的,永远不会创建一个BaseValue,而只会创建它的子类。
RedisValue类包装了C++基础的数据类型(POD+string),如int, long long, double, bool, string,它代表着redis服务器返回的单值。
RedisComplexValue类是一个复合的数据类型,它由一系列的BaseValue组成,而每一个BaseValue又会是RedisValue或者RedisComplexValue,这是由我们Redis命令返回的结果决定的。
特别注意
对于一些redis命令,其返回多条平行的数据类型,如:
SUBSCRIBE chat_msg chat_msg2\r\n
返回:
*3\r\n$9\r\nsubscribe\r\n$8\r\nchat_msg\r\n:1\r\n*3\r\n$9\r\nsubscribe\r\n$9\r\nchat_msg2\r\n:2\r\n
从上面可以看到,返回是两个数组,它们并没有谁包含谁,是平行的关系,这时候,get_results()只会返回第一个数组,后面的不会返回。文件subscriber.cpp提供了处理这种情况的范例。
好了,核心的内容已经介绍完了。