hiredis异步接口封装并导出到Lua

(金庆的专栏 2017.1)

hiredis 不支持 Windows, Windows 下使用 wasppdotorg / hiredis-for-windows 。
Linux 下仍是 redis/hiredis。
hiredis-for-windows 是以 hiredis 0.13.3 为基础移植的。

hiredis-for-windows 需要稍加修正:
    * 去除 inline 宏
    * TCP_NODELAY 改在连接之前设置。
详见其Issue.

Cluster 支持采用 shinberg/cpp-hiredis-cluster。这是个CPP库,支持异步,
要求 hiredis >= 0.12.0。
jinq0123/cpp-hiredis-cluster 在 develop 分支上更改了接口,让它更好用。

因为网络库是boost asio, 所以需要asio适配器,采用 jinq0123/hiredis-boostasio-adapter。

cpp-hiredis-cluster 提供的是统一的Command接口,接收字符串命令,返回 redisReply.
对于常用命令,需要更简单的接口。
在Lua手游服务器代码中新建CRedis类,封装 cpp-hiredis-cluster,
为常用redis命令封装更好用的接口。
CRedis类封装了asio, 接口是根据应用需要定义的,所以是专用接口,
不在 cpp-hiredis-cluster 中实现。

bool CRedis::Init(io_service& rIos, const std::string& sHost, uint16_t uPort)
创建 RedisCluster 对象。
io_service 用于创建一个 redis 事件适配器,
RedisCluster创建需要一个适配器。
sHost, uPort 用于初始化连接redis cluster, 获取集群信息。

using Cmd = RedisCluster::AsyncHiredisCommand;

bool CRedis::Init(io_service& rIos, const std::string& sHost, uint16_t uPort)
{
    m_pAdapter.reset(new Adapter(rIos));
    try
    {
        m_pCluster.reset(Cmd::createCluster("127.0.0.1", 7000, *m_pAdapter));
    }
    catch (const RedisCluster::ClusterException &e)
    {
        LOG_ERROR("Cluster exception: " << e.what());
        return false;
    }

    return true;
}

static Cmd::Action handleException(const RedisCluster::ClusterException &exception,
    RedisCluster::HiredisProcess::processState state)
{
    // Check the exception type.
    // Retry in case of non-critical exceptions.
    if (!dynamic_cast<const RedisCluster::CriticalException*>(&exception))
    {
        LOG_WARN("Exception in processing async redis callback: "
            << exception.what() << " Retry...");
        // retry to send a command to redis node
        return Cmd::RETRY;
    }
    LOG_ERROR("Critical exception in processing async redis callback: "
        << exception.what());
    return Cmd::FINISH;
}

static void handleSetReply(const redisReply &reply, const CRedis::SetCb& setCb)
{
    if (!setCb) return;
    if (reply.type == REDIS_REPLY_STATUS)
    {
        const std::string OK("OK");
        if (OK == reply.str)
        {
            setCb(true);
            return;
        }
    }

    LOG_WARN("Set reply: " << reply.str);
    setCb(false);
}

void CRedis::Set(const string& sKey, const string& sValue, const SetCb& setCb)
{
    assert(m_pCluster);
    Cmd::commandf2(*m_pCluster, sKey,
        [setCb](const redisReply& reply) {
            handleSetReply(reply, setCb);
        },
        handleException,
        "set %s %s", sKey.c_str(), sValue.c_str());
}

static void handleGetReply(const redisReply& reply,
    const CRedis::ReplyStringCb& hdlStrReply)
{
    if (!hdlStrReply) return;
    using Rt = CRedis::ReplyType;
    if (reply.type == REDIS_REPLY_NIL)
    {
        hdlStrReply(Rt::NIL, "");
        return;
    }
    std::string sReply(reply.str, reply.len);
    if (reply.type == REDIS_REPLY_STRING)
        hdlStrReply(Rt::OK, sReply);
    else
        hdlStrReply(Rt::ERR, sReply);
}

void CRedis::Get(const std::string& sKey, const ReplyStringCb& hdlStrRepl)
{
    assert(m_pCluster);
    Cmd::commandf2(*m_pCluster, sKey,
        [hdlStrRepl](const redisReply& reply) {
            handleGetReply(reply, hdlStrRepl);
        },
        handleException,
        "get %s", sKey.c_str());
}

handleException 是Cmd::command() 接口中需要的异常处理,这里是尽量重试。

Cmd::command() 中的第3个参数是 redis 应答的回调,读取 redisReply, 然后触发命令的回调。

CRedis::Get() 执行 redis GET 命令,固定1个参数,返回是字符串,nil, 或错误。

    enum class ReplyType
    {
        OK = 0,  // redis replys string/integer/array
        NIL = 1,  // redis replys nil
        ERR = 2,  // redis replys error status
    };

    // 简单的常用命令会自动解析reply, 使用更易用的回调。
    using ReplyStringCb = function<void (ReplyType, const string& sReply)>;

CRedis::Get() 的回调就是 ReplyStringCb。

void CRedis::Set() 的回调只需知道成功或失败,SetCb 定义为:
    using SetCb = function<void (bool ok)>;

失败时会有错误信息,已统一打印日志,不再暴露出来。

SET 命令完整的参数列表还有其他参数:
set key value [EX seconds] [PX milliseconds] [NX|XX]

因为常用的就 "set key value", 其他扩展的命令需要使用通用的 command() 接口,
并需要读取 redisReply.

使用 LuaIntf 导出 CRedis 到 Lua.
因为只有一个 CRedis, 所以导出为模块 c_redis:

#include <LuaIntf/LuaIntf.h>

namespace {

void Set(const string& sKey, const string& sValue, const LuaRef& luaSetCb)
{
    // Default is empty callback.
    auto setCb = ToFunction<CRedis::SetCb>(luaSetCb);
    GetRedis().Set(sKey, sValue, setCb);
}

void Get(const string& sKey, const LuaRef& luaReplyStringCb)
{
    auto replyStringCb = ToFunction<CRedis::ReplyStringCb>(
        luaReplyStringCb);
    GetRedis().Get(sKey, replyStringCb);
}

}  // namespace

void Bind(lua_State* L)
{
    LuaBinding(L).beginModule("c_redis")
        .addFunction("set", &Set)
        .addFunction("get", &Get)
    .endModule();
}

需要将 lua 的回调函数转成 cpp 的回调:

template <class Function>
Function ToFunction(const LuaIntf::LuaRef& luaFunction)
{
    // Default is empty.
    if (!luaFunction)
        return Function();  // skip nil
    if (luaFunction.isFunction())
        return luaFunction.toValue<Function>();  // Todo: catch
    LOG_WARN_TO("ToFunction", "Lua function expected, but got "
        << luaFunction.typeName());
    return Function();
}

Lua 这样调用:
c_redis.set("FOO", "1234")
c_redis.set("FOO", "1234", function(ok) print(ok) end)
c_redis.get("FOO", function(reply_type, reply)
    assert("string" == type(reply))
    if 0 == reply_type or 1 == reply_type then
        print("FOO="..reply)
    else
        print("Error: "..reply)
    end
end)

hiredis异步接口封装并导出到Lua的更多相关文章

  1. 基于C#的MongoDB数据库开发应用(3)--MongoDB数据库的C#开发之异步接口

    在前面的系列博客中,我曾经介绍过,MongoDB数据库的C#驱动已经全面支持异步的处理接口,并且接口的定义几乎是重写了.本篇主要介绍MongoDB数据库的C#驱动的最新接口使用,介绍基于新接口如何实现 ...

  2. C++ Redis mset 二进制数据接口封装方案

    C++ Redis mset 二进制数据接口封装方案 需求 C++中使用hiredis客户端接口访问redis: 需要使用mset一次设置多个二进制数据 以下给出三种封装实现方案: 简单拼接方案 在r ...

  3. NodeJS中常见异步接口定义(get、post、jsonp)

    越来越多的人在使用nodeJS,作为一门服务端语言,我们不可避免的要写异步接口(ajax和jsonp).再次强调ajax和jsonp是两个概念,但是由于jquery的封装,使这两种异步接口的调用方式, ...

  4. vue2.0 + vux (五)api接口封装 及 首页 轮播图制作

    1.安装 jquery 和 whatwg-fetch (优雅的异步请求API) npm install jquery --save npm install whatwg-fetch --save 2. ...

  5. javascript 异步请求封装成同步请求

    此方法是异步请求封装成同步请求,加上token验证,环境试用微信小程序,可以修改文件中的ajax,进行封装自己的,比如用axios等 成功码采用标准的 200 到 300 和304 ,需要可以自行修改 ...

  6. java单元测试之如何实现异步接口的测试案例

    测试是软件发布的重要环节,单元测试在实际开发中是一种常用的测试方法,java单元测试主要用junit,最新是junit5,本人开发一般用junit4.因为单元测试能够在软件模块组合之前尽快发现问题,所 ...

  7. Postman实现数字签名,Session依赖, 接口依赖, 异步接口结果轮询

    Script(JS)为Postman赋予无限可能 基于Postman 6.1.4 Mac Native版 演示结合user_api_demo实现 PS 最近接到任务, 要把几种基本下单接口调试和持续集 ...

  8. Java微信公众平台接口封装源码分享

    前言:      这篇博客是在三月初动手项目的时候准备写的,但是为了完成项目只好拖延时间写这篇博客,顺便也可以在项目中应用我自己总结的的一些经验.今天看来,这些方法的应用还是可以的,至少实现了我之前的 ...

  9. 基于Verilog的带FIFO输出缓冲的串口接收接口封装

    一.模块框图及基本思路 rx_module:串口接收的核心模块,详细介绍请见“基于Verilog的串口接收实验” rx2fifo_module:rx_module与rx_fifo之间的控制模块,其功能 ...

随机推荐

  1. IDEA2017.3.4破解方式

    下载jetbrainsCrack-2.7-release-str.jar包 下载地址: https://files.cnblogs.com/files/xifenglou/JetBrains.zip ...

  2. Shell自学二(参数传递和数组)

    8.传递参数    1.使用$n来传递参数($0表示文件名)    例子:    echo "执行的文件名:$0";    echo "第一个参数为:$1";  ...

  3. [SDOI2017]序列计数

    题目描述 Alice想要得到一个长度为nn的序列,序列中的数都是不超过mm的正整数,而且这nn个数的和是pp的倍数. Alice还希望,这nn个数中,至少有一个数是质数. Alice想知道,有多少个序 ...

  4. [SHOI2014]概率充电器

    Description 著名的电子产品品牌 SHOI 刚刚发布了引领世界潮流的下一代电子产品——概率充电器: “采用全新纳米级加工技术,实现元件与导线能否通电完全由真随机数决定!SHOI 概率充电器, ...

  5. linux内核中的链表

    1.内核中的链表 linux内核链表与众不同,他不是把将数据结构塞入链表,而是将链表节点塞入数据,在2.1内核中引入了官方链表,从此内核中所有的链表使用都采用此链表,千万不要在重复造车轮子了!链表实现 ...

  6. bzoj 3673&3674: 可持久化并查集 by zky

    Description n个集合 m个操作 操作: 1 a b 合并a,b所在集合 2 k 回到第k次操作之后的状态(查询算作操作) 3 a b 询问a,b是否属于同一集合,是则输出1否则输出0 0& ...

  7. bzoj1010[HNOI2008]玩具装箱toy 斜率优化dp

    1010: [HNOI2008]玩具装箱toy Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 11893  Solved: 5061[Submit][S ...

  8. FFT模板(BZOJ2179)

    实现了两个长度为n的大数相乘. #include <cstdio> #include <cmath> #include <complex> using namesp ...

  9. 用js来实现那些数据结构11(字典)

    我们这篇文章来说说Map这种数据结构如何用js来实现,其实它和集合(Set)极为类似,只不过Map是[键,值]的形式存储元素,通过键来查询值,Map用于保存具有映射关系的数据,Map里保存着两组数据: ...

  10. linux 下文件误删恢复

    linux 下文件误删恢复 0x01 事件背景 某天晚上写代码的时候,本来想删除当前目录下一个叫xxx的文件夹 rm -rdf ./xxx/*, 结果光顾着和人说话,一不留神手贱把命令敲成了rm -r ...