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. Java面试题—初级(1)

    1.一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制? 可以有多个类,但只能有一个public的类,并且public的类名必须与文件名相一致. 2.Java有 ...

  2. Vue项目结构说明

    简单介绍目录结构 http://blog.csdn.net/u013778905/article/details/53864289 (别人家的链接,留给我自己看的)

  3. .Net中Web增加加密狗管理

    由于业务中最近需要使用到加密狗,增加对Web代码的管控,所以需要进行加密狗使用的研究 首先,对于没有接触使用过加密狗的人需要有个大致的认识,加密狗分为 MasterDog, 1.下载加密狗的开发套件, ...

  4. html的基本结构

    html的基本结构 1.<html>内容</html> html文档的文档标记,也称为html开始标记 这对标记分别位于网页的最前端和最后端,表示开始和结束 2.<hea ...

  5. IndentationError : expected an indented block

    IndentationError:在python的条件语句出现 expected an indented block问题 是指缩进问题,比如for循环里面的print前面需要四个空格. Python语 ...

  6. vue中实现全选功能

    <!DOCTYPE html><html><head><meta charset="utf-8"><title>Vue ...

  7. C# WinForm 富文本编辑器 用kindeditor实现本地图片只选取不上传到编辑器

    以下资料有参考网上其它童鞋作品,原作者看到务喷!!!! 以下资料有参考网上其它童鞋作品,原作者看到务喷!!!! 重要的事只要说两遍... 网上找了几天关于WinForm富文本编辑效果都不理想,各种坑, ...

  8. [Luogu 2265]路边的水沟

    Description LYQ市有一个巨大的水沟网络,可以近似看成一个n*m的矩形网格,网格的每个格点都安装了闸门,我们将从水沟网络右下角的闸门到左上角的闸门的一条路径称为水流. 现给定水沟网的长和宽 ...

  9. ●洛谷P3688 [ZJOI2017]树状数组

    题链: https://www.luogu.org/problemnew/show/P3688题解: 二维线段树. 先不看询问时l=1的特殊情况. 对于一个询问(l,r),如果要让错误的程序得到正确答 ...

  10. [UOJ UR #4追击圣诞老人]

    来自FallDream的博客,未经允许,请勿转载, 谢谢. 传送门 考虑直接维护一个堆,然后往里面丢链,并且取出k个堆顶就行了. 然后就需要分类讨论啥的,给你的三个点变成两条链,每次取出一条链之后选择 ...