UBUS代码例程以及分析

UBUS 相关框架和代码分析

UBUS 组成

  • 通俗点说,ubus 就是一个用于进程间通信的通用框架。ubus 具有很强的可移植性,可以很方便的移植到其他 Linux 平台上使用。
  • libubus 基础库是根据 libubox 开发出来的,同样 uhttpd 也是基于 libubox 开发。

UBUS 数据结构

struct ubus_object {
    struct avl_node avl;  /** 关系到struct ubus_context的objects */
    const char *name;     /** UBUS_ATTR_OBJPATH */
    uint32_t id;          /** 由ubusd server分配的obj id */
    const char *path;
    struct ubus_object_type *type;

/** 第1次被订阅或最后1次补退订时被调用 */
    ubus_state_handler_t subscribe_cb;
    bool has_subscribers;    /** 此对象是否被订阅 */
    const struct ubus_method *methods;  /** 方法数组 */
    int n_methods;                      /** 方法数组个数 */
};

struct ubus_object_type {
    const char *name;
    uint32_t id;            /** 由ubusd server分配的obj type id */
    const struct ubus_method *methods;  /** 方法数组 */
    int n_methods;                      /** 方法数组个数 */
};

struct ubus_method {
    const char *name;         /** 方法名称 */
    ubus_handler_t handler;   /** 方法处理回调函数 */
    unsigned long mask;                   /** 参数过滤掩码 */
    //blobmsg_policy用于解析和缓存blobmsg列表,一般声明为一个静态数组,用于指导消息解析
    const struct blobmsg_policy *policy;  /** 参数过滤列表 */
    int n_policy;                         /** 参数过滤列表个数 */
};

struct ubus_event_handler {
    struct ubus_object obj;
    ubus_event_handler_t cb;
};

struct ubus_context {
    struct list_head requests;
    struct avl_tree objects;    /** client端object链表头 */
    struct list_head pending;
    struct uloop_fd sock;       /** client端sock对象 */
    uint32_t local_id;          /** ubusd分配的client id */
    uint16_t request_seq;
    int stack_depth;
    void (*connection_lost)(struct ubus_context *ctx); //断开连接回调函数
    struct {
        struct ubus_msghdr hdr;
        char data[UBUS_MAX_MSGLEN];
    } msgbuf;                   /** 通信报文 */
};

struct ubus_object_data {
    uint32_t id;
    uint32_t type_id;
    const char *path;
    struct blob_attr *signature;
};

struct ubus_request_data {
    uint32_t object;
    uint32_t peer;
    uint16_t seq;

    /* internal use */
    bool deferred;
    int fd;
};

struct ubus_request {
    struct list_head list;

    struct list_head pending;
    int status_code;
    bool status_msg;
    bool blocked;
    bool cancelled;
    bool notify;
    uint32_t peer;
    uint16_t seq;
    ubus_data_handler_t raw_data_cb;
    ubus_data_handler_t data_cb;
    ubus_fd_handler_t fd_cb;
    ubus_complete_handler_t complete_cb;
    struct ubus_context *ctx;
    void *priv;
};

struct ubus_notify_request {
    struct ubus_request req;
    ubus_notify_complete_handler_t status_cb;
    ubus_notify_complete_handler_t complete_cb;
    uint32_t pending;
    uint32_t id[UBUS_MAX_NOTIFY_PEERS + 1];
};

struct ubus_auto_conn {
    struct ubus_context ctx;
    struct uloop_timeout timer;
    const char *path;
    ubus_connect_handler_t cb;
};

#define UBUS_OBJECT_TYPE(_name, _methods)       \
    {                       \
        .name = _name,              \
        .id = 0,                \
        .n_methods = ARRAY_SIZE(_methods),  \
        .methods = _methods         \
    }

#define __UBUS_METHOD_NOARG(_name, _handler)        \
    .name = _name,                  \
    .handler = _handler

#define __UBUS_METHOD(_name, _handler, _policy)     \
    __UBUS_METHOD_NOARG(_name, _handler),       \
    .policy = _policy,              \
    .n_policy = ARRAY_SIZE(_policy)

#define UBUS_METHOD(_name, _handler, _policy)       \
    { __UBUS_METHOD(_name, _handler, _policy) }

#define UBUS_METHOD_MASK(_name, _handler, _policy, _mask) \
    {                       \
        __UBUS_METHOD(_name, _handler, _policy),\
        .mask = _mask               \
    }

#define UBUS_METHOD_NOARG(_name, _handler)      \
    { __UBUS_METHOD_NOARG(_name, _handler) }

UBUS 接口函数

  • 初始化 client 端 context 结构,并连接 ubusd
  struct ubus_context *ubus_connect(const char *path)
  • 与 ubus_connect()函数基本功能相同,但此函数在连接断开后会自动进行重连
  void ubus_auto_connect(struct ubus_auto_conn \*conn)
  • 注册新事件
  int ubus_register_event_handler(struct ubus_context *ctx, struct ubus_event_handler *ev, const char *pattern)
  • 发出事件消息
  int ubus_send_event(struct ubus_context *ctx, const char *id, struct blob_attr *data)
  • 向 ubusd 查询指定 UBUS_ATTR_OBJPATH 对应对象信息内容 内容通过输入回调函数 ubus_lookup_handler_t 由调用者自行处理
  int ubus_lookup(struct ubus_context *ctx, const char *path, ubus_lookup_handler_t cb, void *priv)
  • 向 ubusd 查询指定 UBUS_ATTR_OBJPATH 对应的 ID 号
  int ubus_lookup_id(struct ubus_context *ctx, const char *path, uint32_t *id)
  • 向 uloop 注册 fd(把创建的 ubus 连接注册到 epoll 中)
  static inline void ubus_add_uloop(struct ubus_context *ctx)
  • client 端向 ubusd server 请求增加一个新 object
  int ubus_add_object(struct ubus_context *ctx, struct ubus_object *obj)
  • client 端向 ubusd server 请求删除一个 object
  int ubus_remove_object(struct ubus_context *ctx, struct ubus_object *obj)
  • 处理收到与 object 相关报文
  void _hidden ubus_process_obj_msg(struct ubus_context *ctx, struct ubus_msghdr *hdr)
  • 处理 UBUS_MSG_INVOKE 报文
  static void
  ubus_process_invoke(struct ubus_context *ctx, struct ubus_msghdr *hdr,
  struct ubus_object \*obj, struct blob_attr \*\*attrbuf)
  • 处理 UBUS_MSG_UNSUBSCRIBE 报文
  static void ubus_process_unsubscribe(struct ubus_context *ctx, struct ubus_msghdr *hdr,
  struct ubus_object \*obj, struct blob_attr \*\*attrbuf)
  • 处理 UBUS_MSG_NOTIFY 报文
  static void ubus_process_notify(struct ubus_context *ctx, struct ubus_msghdr *hdr,
  struct ubus_object \*obj, struct blob_attr \*\*attrbuf)
  • libubus 关注的报文中数据属性列表
static const struct blob_attr_info ubus_policy[UBUS_ATTR_MAX] = {
    [UBUS_ATTR_STATUS] = { .type = BLOB_ATTR_INT32 },
    [UBUS_ATTR_OBJID] = { .type = BLOB_ATTR_INT32 },
    [UBUS_ATTR_OBJPATH] = { .type = BLOB_ATTR_STRING },
    [UBUS_ATTR_METHOD] = { .type = BLOB_ATTR_STRING },
    [UBUS_ATTR_ACTIVE] = { .type = BLOB_ATTR_INT8 },
    [UBUS_ATTR_NO_REPLY] = { .type = BLOB_ATTR_INT8 },
    [UBUS_ATTR_SUBSCRIBERS] = { .type = BLOB_ATTR_NESTED },
};
  • 把 libubus 关注的无序报文转化为有序的 blob_attr 数组
__hidden struct blob_attr **ubus_parse_msg(struct blob_attr *msg)
  • 发送报文
/**
 *
 * @param ctx    - client上下文对象
 * @param seq    - 报文顺序号 hdr.seq
 * @param msg    - 报文内容
 * @param cmd    - 报文类型 hdr.type
 * @param perr   -
 * @param fd     - 需传递给对端的描述符,等于-1表示不需传递
 */
int __hidden ubus_send_msg(struct ubus_context *ctx, uint32_t seq,
               struct blob_attr *msg, int cmd, uint32_t peer, int fd)
  • client 端 fd 收包处理函数
void __hidden ubus_handle_data(struct uloop_fd *u, unsigned int events)
  • client 端轮询 fd 收包处理函数
void __hidden ubus_poll_data(struct ubus_context *ctx, int timeout)
  • client 连接 ubusd server
int ubus_reconnect(struct ubus_context *ctx, const char *path)
  • 发送回应信息,消息类型 UBUS_MSG_DATA
int ubus_send_reply(struct ubus_context *ctx, struct ubus_request_data *req,
            struct blob_attr *msg)
  • 异步通知指定 object 执行其方法
int ubus_invoke_async(struct ubus_context *ctx, uint32_t obj, const char *method,
                       struct blob_attr *msg, struct ubus_request *req)
  • 同步通知指定 object 执行其方法
int ubus_invoke(struct ubus_context *ctx, uint32_t obj, const char *method,
                struct blob_attr *msg, ubus_data_handler_t cb, void *priv,
        int timeout)
  • 异步发出通知消息
int ubus_notify_async(struct ubus_context *ctx, struct ubus_object *obj,
              const char *type, struct blob_attr *msg,
              struct ubus_notify_request *req)
  • 同步发出通知消息
int ubus_notify(struct ubus_context *ctx, struct ubus_object *obj,
        const char *type, struct blob_attr *msg, int timeout)

UBUS 程序示例

向 ubusd 注册新 object

定义 object 方法
enum {
    OBJ_SET_ARG1,
    OBJ_SET_ARG2,
    __OBJ_SET_ATTR_MAX
};

/** 定义set方法参数列表 */
static const struct blobmsg_policy obj_set_attrs[__OBJ_SET_ATTR_MAX] = {
    [OBJ_SET_ARG1] = { .name = "arg1", .type = BLOBMSG_TYPE_STRING },
    [OBJ_SET_ARG2 ] = { .name = "arg2", .type = BLOBMSG_TYPE_STRING },
};

static struct ubus_method obj_methods[] = {
    //下面展示了两种注册的方法
    { .name = "enable", .handler = obj_enable },
    { .name = "dump", .handler = obj_dump },
    UBUS_METHOD("set", obj_set, obj_set_attrs), //推荐使用这种方式
};
定义 object 类型
static struct ubus_object_type obj_type = UBUS_OBJECT_TYPE("my_obj", obj_methods);
定义 object
static struct ubus_object obj = {
    .name = "myobj",
    .type = &obj_type,
    .methods = obj_methods,
    .n_methods = ARRAR_SIZE(obj_methods),
};
注册新的 object
uloop_init();

struct ubus_context *ubus_ctx = ubus_connect(NULL);
ubus_add_uloop(ubus_ctx);
ubus_add_object(ubus_ctx, &obj);

uloop_run();

向 ubusd 注册事件监听

定义事件触发回调方法
static void
event_receive_cb(struct ubus_context *ctx, struct ubus_event_handler *ev,
                const char *type, struct blob_attr *msg)
{
    enum {
        EV_ACTION,
        EV_IFNAME,
        __EV_MAX
    };
    static const struct blobmsg_policy ev_policy[__EV_MAX] = {
        [EV_ACTION] = { .name = "action", .type = BLOBMSG_TYPE_STRING },
        [EV_IFNAME] = { .name = "interface", .type = BLOBMSG_TYPE_STRING },
    };
    struct blob_attr *tb[__EV_MAX];

    blobmsg_parse(ev_policy, __EV_MAX, tb, blob_data(msg), blob_len(msg));

    /* do something */
}
注册监听事件
static void
event_listen(void)
{
    static struct ubus_event_handler listener;
    memset(&listener, 0, sizeof(listener));

    /** 监听netwrok.interface事件 */
    ubus_register_event_handler(ubus_ctx, &listener, "network.interface");
}

向 ubusd 发送命令

定义命令返回回调方法
static void
command_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
    if (!msg)
        return;

    enum {
        ADDR_IPV4,
        __ADDR_MAX,
    };
    static const struct blobmsg_policy policy[__ADDR_MAX] = {
        [ADDR_IPV4] = { .name = "ipv4-address", .type = BLOBMSG_TYPE_ARRAY },
    };
    struct blob_attr *tb[__ADDR_MAX];

    blobmsg_parse(policy, __ADDR_MAX, tb, blobmsg_data(msg), blobmsg_len(msg));

    /** do something */
}
发送命令
static void
invoke_command(char *net)
{
    uint32_t id;
    char path[64] = {0};

    sprintf(path, "network.interface.%s", net);

    /** lookup `network.interface.%s` object id */
    ubus_lookup_id(ubus_ctx, path, &id);

    /** invoke command `status` */
    ubus_invoke(ubus_ctx, id, "status", NULL, command_cb, NULL, 500);
}
  • 下面看下 logd 中 ubus 操作,ubus 的 logd 注册了一个 log 对象,两个方法 read 和 write。
static const struct ubus_method log_methods[] = {
    { .name = "read", .handler = read_log, .policy = &read_policy, .n_policy = 1 },
    { .name = "write", .handler = write_log, .policy = &write_policy, .n_policy = 1 },
};

static struct ubus_object_type log_object_type =
    UBUS_OBJECT_TYPE("log", log_methods);

static struct ubus_object log_object = {
    .name = "log",
    .type = &log_object_type,
    .methods = log_methods,
    .n_methods = ARRAY_SIZE(log_methods),
};

static const struct blobmsg_policy read_policy =
    { .name = "lines", .type = BLOBMSG_TYPE_INT32 };

static const struct blobmsg_policy write_policy =
    { .name = "event", .type = BLOBMSG_TYPE_STRING };

TODO: logd 是守护进程,永不退出,所以没必要 remove object。

客户端/服务器模式代码

单播模式

  • 服务器代码
// ser.c
#include <stdio.h>
#include <unistd.h>
#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>
#include <libubox/blobmsg_json.h>
#include <libubus.h>

static int stu_no = 0;
static char *ubus_socket = NULL;
struct ubus_context *ctx = NULL;
static struct blob_buf b;

enum
{
    STU_NO,
    __STU_MAX
};

//用于消息解析,指明key为no字符串的时候,它的值是一个INT32类型的值,可以单纯的理解为"no" : 20
static const struct blobmsg_policy stu_policy[__STU_MAX] = {
    [STU_NO] = {.name = "no", .type = BLOBMSG_TYPE_INT32}};

//sta方法函数
static int stu_add(struct ubus_context *ctx, struct ubus_object *obj,
                   struct ubus_request_data *req, const char *method,
                   struct blob_attr *msg)
{
    struct blob_attr *tb[__STU_MAX];

    //根据policy从解析出blobmsg  blobmsg在blob_attr的data区
    /*
        blobmsg用于二进制对象网络序列化。嵌套在blob数据结构(blob_attr)的data区。因此形成:blob_buff <- blob_attr -< blobmsg,blob_buff可存储管理多个blob_attr,每个blob_attr又可存储管理一个blogmsg。且可存储在线性数据区,不需要链表指针。
        blobmsg_policy用于解析和缓存blobmsg列表,一般声明为一个静态数组,用于指导消息解析。
        blobmsg默认使用id为table。array类似C语言的数组,table类似C的结构。
    */
    blobmsg_parse(stu_policy, ARRAY_SIZE(stu_policy), tb, blob_data(msg), blob_len(msg));

    /*
        这里是一系列函数,因为上面的.type = BLOBMSG_TYPE_INT32 所以这里用blobmsg_get_u32来解析,一系列函数如下
        static inline uint8_t blobmsg_get_u8(struct blob_attr *attr)
        static inline bool blobmsg_get_bool(struct blob_attr *attr)
        static inline uint16_t blobmsg_get_u16(struct blob_attr *attr)
        static inline uint32_t blobmsg_get_u32(struct blob_attr *attr)
        static inline uint64_t blobmsg_get_u64(struct blob_attr *attr)
        static inline char *blobmsg_get_string(struct blob_attr *attr)
     */
    if (tb[STU_NO])
        stu_no += blobmsg_get_u32(tb[STU_NO]);

    //因为blob_buff
    //blob_buf一般声明为本地静态变量,id一般使用0(BLOBMSG_TYPE_UNSPEC)来初始化。
    blob_buf_init(&b, 0);
    //将stu_no的值添加到buf中,然后调用发送函数
    blobmsg_add_u32(&b, "no", stu_no);
    //ubus发送函数,执行完成方法调用后发送响应
    ubus_send_reply(ctx, req, b.head);

    return 0;
}

static int stu_sub(struct ubus_context *ctx, struct ubus_object *obj,
                   struct ubus_request_data *req, const char *method,
                   struct blob_attr *msg)
{
    struct blob_attr *tb[__STU_MAX];

    //解析
    blobmsg_parse(stu_policy, ARRAY_SIZE(stu_policy), tb, blob_data(msg), blob_len(msg));

    if (tb[STU_NO])
        stu_no -= blobmsg_get_u32(tb[STU_NO]);

    blob_buf_init(&b, 0);
    blobmsg_add_u32(&b, "no", stu_no);
    ubus_send_reply(ctx, req, b.head);

    return 0;
}
static const struct ubus_method stu_methods[] = {
    /* 不推荐使用该方式
    { .name = "add", .handler = stu_add, .policy = stu_policy, .n_policy = 1 },
    { .name = "sub", .handler = stu_sub, .policy = stu_policy, .n_policy = 1 }
*/
    UBUS_METHOD("add", stu_add, stu_policy),    //stu_add 和stu_sub是回调函数,/** 方法处理回调函数 */
    UBUS_METHOD("sub", stu_sub, stu_policy),
};


//绑定对象类型和方法数组(固定写法)
static struct ubus_object_type stu_object_type =
    UBUS_OBJECT_TYPE("stu", stu_methods);

//ubus将消息处理抽象为对象(object)和方法(method)的概念。一个对象中包含多个方法。对象和方法都有自己的名字,发送请求方在消息中指定要调用的对象和方法名字即可
//TODO:这里可以看出调用的是对象加方法名的方式。
//定义对象结构体的名称和绑定对象的方法数组-会在数组中注册具体的方法名称和具体调用的方法
static struct ubus_object stu_object = {
    .name = "stu",
    .type = &stu_object_type,
    .methods = stu_methods, //事件数组
    .n_methods = ARRAY_SIZE(stu_methods)};  //事件数组个数

static void ubus_add_fd(void)
{
    ubus_add_uloop(ctx);

#ifdef FD_CLOEXEC
    fcntl(ctx->sock.fd, F_SETFD,
          fcntl(ctx->sock.fd, F_GETFD) | FD_CLOEXEC);
#endif
}

static void ubus_reconn_timer(struct uloop_timeout *timeout)
{
    static struct uloop_timeout retry =
        {
            .cb = ubus_reconn_timer,
        };
    int t = 2;

    if (ubus_reconnect(ctx, ubus_socket) != 0)
    {
        uloop_timeout_set(&retry, t * 1000);
        return;
    }

    ubus_add_fd();
}

static void ubus_connection_lost(struct ubus_context *ctx)
{
    ubus_reconn_timer(NULL);    //当ubus断开的时候调用
}
int main(int argc, char **argv)
{
    char ch;
    int ret = 0;


    //创建一个epoll的句柄,最多监控32个文件描述符。
    uloop_init();

    //使用ubus_connect连接到服务管理进程ubusd,得到ubus_context(包含了连接fd、注册fd的回调等)
    /* use default UNIX sock path: /var/run/ubus.sock */
    //TODO: 这里使用ubus的原因就是ubusd绑定了/var/run/ubus.sock文件,服务器要和客户端通信,需要通过ubus转发,所以需要绑定同样的文件
    ctx = ubus_connect(ubus_socket);
    if (!ctx)
    {
        printf("ubus connect error.\n");
        return -1;
    }

    //注册断开回调函数
    ctx->connection_lost = ubus_connection_lost;

    //client端向ubusd server请求增加一个新object(虽然这个是服务器程序,但是对于ubusd来说它还是个客户端程序)
    ret = ubus_add_object(ctx, &stu_object);
    if (ret)
    {
        printf("Failed to add object to ubus:%s\n", ubus_strerror(ret));
        return 0;
    }

    //ubus_add_uloop(ctx);
    //添加fd到ubus中
    ubus_add_fd();

    /**
        * 事件循环主处理入口
        *1.当某一个进程第一次调用uloop_run时,注册sigchld和sigint信号
        *2.循环获取当前时间,把超时的timeout处理掉,有一条timeout链表在维护
        *3.循环检测是否收到一个sigchld信号,如果收到,删除对应的子进程,有一条process子进程链表在维护
        *4.循环调用epoll_wait 监相应的触发事件文件描述符fd
    **/

   //TODO: 调用uloop后,是否uloop是一个死循环程序,后面的程序是否会运行,如果这是个死循环函数,那么退出的机制是什么
    uloop_run();

    printf("================ line is %d\n",__LINE__);

    if (ctx)
        ubus_free(ctx);

    /**
        * 销毁事件循环
        * 关闭epoll描述符
        * 销毁子进程链表
        * 销毁timeout链表
    **/
    uloop_done();

    return 0;
}
  • 客户端代码
#include <stdio.h>
#include <unistd.h>
#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>
#include <libubox/blobmsg_json.h>
#include <libubus.h>

static struct blob_buf b;

static void receive_call_result_data(struct ubus_request *req, int type, struct blob_attr *msg)
{
    char *str;
    if(!msg)
        return;

    str = blobmsg_format_json_indent(msg, true, 0);
    printf("%s\n", str);

    free(str);
}

static int client_main(struct ubus_context *ctx, int argc, char**argv)
{
    uint32_t id;
    int ret;

    printf("argc =%d, argv[0] =%s\n", argc, argv[0]);
    if(argc != 3)
        return -2;
    //blob_buf一般声明为本地静态变量,id一般使用0(BLOBMSG_TYPE_UNSPEC)来初始化。
    blob_buf_init(&b, 0);
    //这里获取到的是传递的参数  ubus_client stu add '{"no":20}'  这里的就是{"no":20}
    if(!blobmsg_add_json_from_string(&b, argv[2])){
        printf("Failed to parse message data\n");
        return -1;
    }
    //调用ubus_lookup_id函数找到指定对象的ID,然后通过ubus_invoke函数调用来请求服务器,返回的结果使用 receive_call_result_data来处理
    //向ubusd查询是否存在argv[0]这个对象,如果存在,返回其id
    //TODO:这里的逻辑也是和ubus的程序流程一样,先查找对象,再调用对象的方法
    ret = ubus_lookup_id(ctx, argv[0], &id);
    if(ret)
        return ret;

    //触发server端注册的函数,并接收返回值,处理函数为receive_call_result_data
    //调用argv[1]方法
    return ubus_invoke(ctx, id, argv[1], b.head, receive_call_result_data, NULL, 30*1000);
}

int main(int argc, char **argv)
{
    struct ubus_context *ctx = NULL;
    char ch;

    /* 1. create an epoll instatnce descriptor poll_fd */
    char *ubus_socket = NULL;
    int ret = 0;

    argc -= optind;
    argv += optind;

    ctx = ubus_connect("/tmp/zhflag");
    if(!ctx){
        printf("ubus connect error.\n");
        return -1;
    }

    ret = client_main(ctx, argc, argv);
    if(ret < 0)
        return ret;

    if(ctx)
        ubus_free(ctx);

    return 0;
}

运行方法:启动服务器,然后调用客户端ubus_client stu add ‘{“no”:20}’


发布者订阅模式

  • 服务端代码

#include <unistd.h>
#include <libubox/blobmsg_json.h>
#include <libubox/uloop.h>
#include <libubus.h>

static struct ubus_context *ctx;

static void test_client_subscribe_cb(struct ubus_context *ctx, struct ubus_object *obj)
{
	fprintf(stderr, "Subscribers active: %d\n", obj->has_subscribers);
}

/* 这个空的method列表,只是为了让object有个名字,这样client可以通过object name来订阅。 */
static struct ubus_method test_methods[] = {};

static struct ubus_object_type test_obj_type =
	UBUS_OBJECT_TYPE("test", test_methods);

static struct ubus_object test_object = {
	.name = "test", /* object的名字 */
	.type = &test_obj_type,
	.subscribe_cb = test_client_subscribe_cb,
};

static void notifier_main(void)
{
	int ret;

	/* 注册一个object,client可以订阅这个object */
	ret = ubus_add_object(ctx, &test_object);
	if (ret) {
		fprintf(stderr, "Failed to add object: %s\n", ubus_strerror(ret));
		return;
	}

	/* 在需要的时候,向所有客户端发送notify消息 */
	/* step1: 如果需要传递参数,则保存到struct blob_attr类型的结构体中 */
	/*
	int ubus_notify(struct ubus_context *ctx, struct ubus_object *obj, const char *type, struct blob_attr *msg, int timeout);
	type是一个字符串,自定义的。msg是需要携带的参数。如果需要等待回复,timeout需设置为>=0。
	*/
	while (1) {
		sleep(2);
		/* step2: 广播notification消息。 */
		ubus_notify(ctx,  &test_object, "say Hi!", NULL, -1);
	}
}

int main(int argc, char **argv)
{
	const char *ubus_socket = NULL;

	uloop_init();

	ctx = ubus_connect(ubus_socket);
	if (!ctx) {
		fprintf(stderr, "Failed to connect to ubus\n");
		return -1;
	}

	ubus_add_uloop(ctx);

	notifier_main();

	uloop_run();

	ubus_free(ctx);
	uloop_done();

	return 0;
}
  • 客户端代码
#include <unistd.h>
#include <libubox/blobmsg_json.h>
#include <libubox/uloop.h>
#include <libubus.h>

static struct ubus_context *ctx;

static int counter = 0;
static uint32_t obj_id;
static struct ubus_subscriber test_event;

static int test_notify(struct ubus_context *ctx, struct ubus_object *obj,
			      struct ubus_request_data *req,
			      const char *method, struct blob_attr *msg)
{
	printf("notify handler...\n");
	counter++;
	if (counter > 3)
		ubus_unsubscribe(ctx, &test_event, obj_id); /* 取消订阅 */
	return 0;
}

static void test_handle_remove(struct ubus_context *ctx,
				      struct ubus_subscriber *obj, uint32_t id)
{
	printf("remove handler...\n");
}

static void subscriber_main(void)
{
	int ret;

	/* 通知到来时的处理函数。 */
	test_event.cb = test_notify;
	test_event.remove_cb = test_handle_remove; //server主动发起删除该client的订阅的cb函数(如server退出的时候)

	/* 注册test_event */
	ret = ubus_register_subscriber(ctx, &test_event);
	if (ret)
		fprintf(stderr, "Failed to add watch handler: %s\n", ubus_strerror(ret));

	/* 得到要订阅的object的id */
	ret = ubus_lookup_id(ctx, "test", &obj_id);
	if (ret)
		fprintf(stderr, "Failed to lookup object: %s\n", ubus_strerror(ret));

	/* 订阅object */
	ret = ubus_subscribe(ctx, &test_event, obj_id);
	if (ret)
		fprintf(stderr, "Failed to subscribe: %s\n", ubus_strerror(ret));
}

int main(int argc, char **argv)
{
	const char *ubus_socket = NULL;

	uloop_init();

	ctx = ubus_connect(ubus_socket);
	if (!ctx) {
		fprintf(stderr, "Failed to connect to ubus\n");
		return -1;
	}

	ubus_add_uloop(ctx);

	subscriber_main();

	uloop_run();

	ubus_free(ctx);
	uloop_done();

	return 0;
}

event的方式实现事件通知

  • event机制从一对一的进程之间通信来讲,和invoke机制类似。不过event机制中,发送方不需要知道谁要接收这个消息,实际上就是一个广播消息。这类似于U盘的热插拔:当插上或拔出U盘时,内核会广播一个NETLINK事件,之后内核继续做自己的事情,而不关心谁会去监听和处理这个事件。

  • 服务器代码

#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>
#include <libubus.h>
#include <json/json.h>
#include <libubox/blobmsg_json.h>
 
static struct ubus_context * ctx = NULL;
static struct blob_buf b;
static const char * sock_path;
 
static int server_ubus_send_event(void)
{
	blob_buf_init(&b, 0);
 
	/* 需要传递的参数 */
	blobmsg_add_u32(&b, "major", 3);
	blobmsg_add_u32(&b, "minor", 56);
	blobmsg_add_string(&b, "name", "mmc01");
 
	/* 广播名为"add_device"的事件 */
	return ubus_send_event(ctx, "add_device", b.head);
}
 
static int display_ubus_init(const char *path)
{
	uloop_init();
	sock_path = path;
 
	ctx = ubus_connect(path);
	if (!ctx)
	{
		printf("ubus connect failed\n");
		return -1;
	}
	
	printf("connected as %08x\n", ctx->local_id);
 
	return 0;
}
 
static void display_ubus_done(void)
{
	if (ctx)
		ubus_free(ctx);
}
 
int main(int argc, char * argv[])
{
	char * path = NULL;
	
	if (-1 == display_ubus_init(path))
	{
		printf("ubus connect failed!\n");
		return -1;
	}
 
	server_ubus_send_event();
 
	display_ubus_done();
 
	return 0;
}
  • 客户端代码

#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>
#include <libubus.h>
#include <json/json.h>
#include <libubox/blobmsg_json.h>
 
static struct ubus_context * ctx = NULL;
static const char * cli_path;
 
#define	UBUS_EVENT_ADD_DEVICE	"add_device"
#define	UBUS_EVENT_REMOVE_DEVICE	"rm_device"
 
static void ubus_probe_device_event(struct ubus_context *ctx, struct ubus_event_handler *ev,
			  const char *type, struct blob_attr *msg)
{
	char *str;
 
	if (!msg)
		return;
 
	/* 
	在这里实现收到事件后的动作。
	event也可以传递消息,放在msg中。
	
	本例子只是将返回的消息打印出来。
	*/
	str = blobmsg_format_json(msg, true);
	printf("{ \"%s\": %s }\n", type, str);
	free(str);
}
 
static int client_ubus_register_events()
{
	static struct ubus_event_handler listener;
	int ret = 0;
 
	/* 注册特定event的listener。多个event可以使用同一个listener */
	memset(&listener, 0, sizeof(listener));
	listener.cb = ubus_probe_device_event;
	
	ret = ubus_register_event_handler(ctx, &listener, UBUS_EVENT_ADD_DEVICE);
	if (ret)
	{
		fprintf(stderr, "register event failed.\n");
		return -1;
	}
 
	ret = ubus_register_event_handler(ctx, &listener, UBUS_EVENT_REMOVE_DEVICE);
	if (ret)
	{
		ubus_unregister_event_handler(ctx, &listener);
		fprintf(stderr, "register event failed.\n");
		return -1;
	}
 
	return 0;
}
 
static void ubus_add_fd(void)
{
	ubus_add_uloop(ctx);
 
#ifdef FD_CLOEXEC
	fcntl(ctx->sock.fd, F_SETFD,
		fcntl(ctx->sock.fd, F_GETFD) | FD_CLOEXEC);
#endif
}
 
static void ubus_reconn_timer(struct uloop_timeout *timeout)
{
	static struct uloop_timeout retry =
	{
		.cb = ubus_reconn_timer,
	};
	int t = 2;
 
	if (ubus_reconnect(ctx, cli_path) != 0) {
		uloop_timeout_set(&retry, t * 1000);
		return;
	}
 
	ubus_add_fd();
}
 
static void ubus_connection_lost(struct ubus_context *ctx)
{
	ubus_reconn_timer(NULL);
}
 
static int client_ubus_init(const char *path)
{
	uloop_init();
	cli_path = path;
	
	ctx = ubus_connect(path);
	if (!ctx)
	{
		printf("ubus connect failed\n");
		return -1;
	}
	
	printf("connected as %08x\n", ctx->local_id);
	ctx->connection_lost = ubus_connection_lost;
 
	ubus_add_fd();
 
	return 0;
}
 
static void client_ubus_done(void)
{
	if (ctx)
		ubus_free(ctx);
}
 
int main(int argc, char * argv[])
{
	char * path = NULL;
 
	/* 连接ubusd */
	if (UBUS_STATUS_OK != client_ubus_init(path))
	{
		printf("ubus connect failed!\n");
		return -1;
	}
 
	/* 注册某个事件的处理函数 */
	client_ubus_register_events();
 
	uloop_run();
	
	client_ubus_done();
 
	return 0;
}

libubox 分析

ibubox 主要提供一下两种功能:
  • 提供一套基于事件驱动的机制。
  • 提供多种开发支持接口。(如:链表、kv 链表、平衡查找二叉树、md5、json)
使用 libubox 开发的好处有如下几点:
  • 可以使程序基于事件驱动,从而可实现在单线程中处理多个任务。
  • 基于 libubox 提供的开发 API 可以加快开发进度的同事提高程序的稳定性。
  • 能更好的将程序融入 openwrt 的架构中,因为新的 openwrt 的很多应用和库都基于 libubox 开发的。
libubox 重要代码分析
blobmsg

blob 提供二进制数据处理能力。有几种支持的数据类型,并可以创建块数据在 socket 上发送。整型数字会在 libubox 库内部转换为网络字节序进行处理。
二进制块的处理方法是创建一个 TLV(类型-长度-值)链表数据,支持嵌套类型数据,并提供设置和获取数据接口。blob 定义在 blob.h 中。
blob(binary large object),二进制大对象,用于二进制对象序列化;blob 主要在一些系统级组件(ubox/libubox/ubus/netifd)内部使用,一般应用不需要使用 blob 封装,blob 用数据结构 blob_attr 表示。
blob_buf 用于管理(多个)二进制大对象。
blogmsg 位于 blob 的上层,提供表格和数组等数据类型的处理,定义在 blogmsg.h 中。

TLV 是用于表示可变长度的数据格式,Type 表示数据的类型,length 表示数据长度,value 存储数据值。类型和长度的占用空间是固定的,在 libubox 库中共占用 4 个字节。
Value 的长度由 length 指定。这样可以存储和传输任何类型的数据,只需预先定义服务器和客户端之间的 TLV 的类型和长度的空间大小即可。
blobmsg 用于二进制对象网络序列化。嵌套在 blob 数据结构(blob_attr)的 data 区。因此形成:blob_buff <- blob_attr -< blobmsg,blob_buff 可存储管理多个 blob_attr,每个 blob_attr 又可存储管理一个 blogmsg。且可存储在线性数据区,不需要链表指针。
blobmsg_policy 用于解析和缓存 blobmsg 列表,一般声明为一个静态数组,用于指导消息解析。
blobmsg 默认使用 id 为 table。array 类似 C 语言的数组,table 类似 C 的结构。

blobmsg 详细说明点击这里.

示例代码

config policy test
    option name 'test'
    option enable '1'
    option dns '1.1.1.1 2.2.2.2'

enum {
    POLICY_ATTR_NAME,       /** name */
    POLICY_ATTR_ENABLE,     /** enable */
    POLICY_ATTR_DNS,        /** dns */
    __POLICY_ATTR_MAX
};

static const struct blobmsg_policy policy_attrs[__POLICY_ATTR_MAX] = {
    [POLICY_ATTR_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING },
    [POLICY_ATTR_ENABLE] = { .name = "enable", .type = BLOBMSG_TYPE_BOOL },
    [POLICY_ATTR_DNS] = { .name = "dns", .type = BLOBMSG_TYPE_ARRAY },
};

/** 定义BLOBMSG_TYPE_ARRAY类型参数的实际数据类型 */
static const struct uci_blob_param_info policy_attr_info[__POLICY_ATTR_MAX] = {
    [POLICY_ATTR_DNS] = { .type = BLOBMSG_TYPE_STRING },
};

static const struct uci_blob_param_list policy_attr_list = {
    .n_params = __POLICY_ATTR_MAX,
    .params = policy_attrs,
    .info = policy_attr_info,
};

### 转化为blob

static struct uci_context *g_uci_ctx;
static struct blob_buf *b;

void
transform(const char *config)
{
    struct uci_context *ctx = g_uci_ctx;
    struct uci_package *p = NULL;

    if (!ctx) {
        ctx = uci_alloc_context();
        g_uci_ctx = ctx;
        uci_set_confdir(ctx, NULL);
    } else {
        p = uci_lookup_package(ctx, config);
        if (p)
            uci_unload(ctx, p);
    }

    if (uci_load(ctx, config, &p))
        return;

    struct uci_element *e;
    struct blob_attr *config = NULL;
    uci_foreach_element(&p->sectons, e) {
        struct uci_section *s = uci_to_section(e);

        blob_buf_init(&b, 0);
        uci_to_blob(&b, s, &policy_attr_list);
        config = blob_memdup(b.head);

        /**
         * do something with `config`
         * free(config), when not use it
         */
    }
}

### 使用转换后的blob
void
foo(blob_attr *confg)
{
    struct blob_attr *tb[__POLICY_ATTR_MAX];

    blobmsg_parse(policy_attrs, __POLICY_ATTR_MAX, tb,
            blob_data(config), blob_len(config));

    /**
     * do something with *tb[]
     */
}
libubox 中 ulog 分析(日志系统)
  • libubox/ulog.c 里面的源代码,其实也是很简单,封装了三种接口,ULOG_KMSG、ULOG_STDIO、ULOG_SYSLOG 分别对应,/dev/kmsg、fprintf、vsyslog 三种形式。如果我们选择了 ULOG_SYSLOG 就和 syslog 一模一样了。
if (_ulog_channels & ULOG_KMSG)
{
    va_start(ap, fmt);
    ulog_kmsg(priority, fmt, ap);
    va_end(ap);
}

if (_ulog_channels & ULOG_STDIO)
{
    va_start(ap, fmt);
    ulog_stdio(priority, fmt, ap);
    va_end(ap);
}

if (_ulog_channels & ULOG_SYSLOG)
{
    va_start(ap, fmt);
    ulog_syslog(priority, fmt, ap);
    va_end(ap);
}

下面是 ulog 日志系统的使用例子

void log_test(void)
{
    /*
        enum{
            MSG_ERROR,
            MSG_WARNING,
            MSG_INFO,
            MSG_DEBUG,
            MSG_MSGDUMP,
            MSG_EXCESSIVE,
        };
    */
    ulog_open(ULOG_SYSLOG, LOG_USER, NULL);
    ulog_threshold(LOG_INFO);   //设置打印的等级

    ULOG_INFO("info\n");
    ULOG_NOTE("notice\n");
    ULOG_WARN("warn\n");
    ULOG_ERR("err\n");

    ulog_close();
}
libubox 中 MD5 分析

TODO: MD5 和 MD5sum 是不同的

  • MD5 是一种被广泛使用的密码散列函数,可以产生出一个 128 位或者 256 位(16 字节或者 32 字节)的散列值(hash value),用于确保信息传输完整一致。
  • libubox 为我们提供了 md5 的两种算法,一个是数据的 md5,还有一个是文件的 md5sum
    下面是 MD5 使用例程
void md5_test(void)
{
    char* data = "test data";
    unsigned char buf[16] = {0};    //这里是16字节的md5
    md5_ctx_t ctx;

    //数据MD5
    md5_begin(&ctx);
    md5_hash(data, strlen(data), &ctx);
    md5_end(buf, &ctx);

    //文件MD5
    memset(buf, 0, sizeof(buf));
    md5sum("/usr/bin/ztest", buf);
}
libubox 中 list 分析

-libubox 里面的 list(仔细看一下它应该是从内核中移植过来的)可称之为侵入式链表,这种 list 最突出的特征就是其节点中不含有任何数据,相反,list 节点是嵌入到特定的数据结构中的。
这样做有两点好处:
链表自身导出的方法比较简洁,由于不涉及到数据的操作,因而 list 的所有 API 都可以实现通用;
不用为每种数据类型实现一套链表的操作,当然在支持泛型的语言里可以使用模板来实现这种通用 list.

TODO: 这里的链表和我们看到的 linux 的链表用法是一样的

typedef struct _ListData {
    char data;
    struct list_head list;
} ListData;

void list_test(void)
{
    ListData mylist, *tmp = NULL;
    struct list_head *pos = NULL, *p = NULL;
    char ch = '0';
    long dec = 1;
    long dec_number = 0;
    int i = 0;
    INIT_LIST_HEAD(&mylist.list);

    while((ch == '0') || (ch == '1')) {
        tmp = (ListData *)malloc(sizeof(ListData));
        tmp->data = ch;
        list_add(&(tmp->list), &(mylist.list));
        printf("list_add %d\n", tmp->data);
        ch = '1';
        i++;
        if(i == 10)
        {
            ch = '2';
        }
    }

    // 遍历过程不可操作链表
    list_for_each(pos, &mylist.list) {
        tmp = list_entry(pos, ListData,list);
        printf("list_entry %d\n", tmp->data);
        dec_number += (int)(tmp->data - '0') * dec;
        dec *= 2;
    }

    printf("Decimal number is %ld\n", dec_number);

    // 遍历过程可操作链表
    list_for_each_safe(pos, p, &mylist.list) {
        tmp = list_entry(pos, ListData, list);
        printf("list_del %d\n", tmp->data);
        list_del(pos);
        free(tmp);
    }

    if(list_empty(&mylist.list)){
        printf("The list now is empty!\n");
    }

    return;
}
libubox 中的 uloop

TODO: 多线程不适合用 uloop,在多线程模式下,uloop 可能导致死锁。

  • uloop 是 libubox 下的一个模块,有三个功能:文件描述符触发事件的监控,timeout 定时器处理, 当前进程的子进程的维护。
主要函数分析
  • 初始化事件循环 int uloop_init(void)
    创建一个 epoll 的句柄,最多监控 32 个文件描述符。设置文件描述符属性,如 FD_CLOEXEC。

  • 事件循环主处理入口 void uloop_run(void)
    uloop_run 轮询处理定时器、进程、描述符事件。遍历定时器 timeouts 链表判断是否有定时器超时,如果有则进行相应的回调处理,没有跳过。判断是否有子进程退出 SIGCHLD 信号,有就会遍历 processes 进程处理的链表,调勇相应的回调函数,没有跳过。计算出距离下一个最近的定时器的时间,作为文件描述符事件 epoll 的超时时间。然后 epoll 进行事件监听,如果有文件描述符准备就绪(可读写时间)则调用相应的回调函数,或者有信号进行中断 epoll 返回停止监听,否则 epoll 阻塞直到超时时间完成。

  • 销毁事件循环 void uloop_done(void)
    关闭 epoll 句柄。清空定时器链表中的所有的定时器。清空进程处理事件链表中删除所有的进程事件节点。

  • 注册一个新描述符到事件处理循环 int uloop_fd_add(struct uloop_fd *sock, unsigned int flags)
    uloop 最多支持 10 个描述符事件。

  • 从事件处理循环中销毁指定描述符 int uloop_fd_delete(struct uloop_fd *sock)

  • 注册一个新定时器 int uloop_timeout_add(struct uloop_timeout *timeout)
    用户不直接使用,内部接口,被接口 uloop_timeout_set 调用。将定时器插入到 timeouts 链表中,该链表成员根据超时时间从小到大排列

  • 设置定时器超时时间(毫秒),并添加 int uloop_timeout_set(struct uloop_timeout *timeout, int msecs)
    如果 pending 为 true,则从定时器链表中删除原先已存在的定时器。设置定时器的超时时间点。调用 uloop_timeout_add 接口将该定时器加入到定时器链表中。

  • 销毁指定定时器 int uloop_timeout_cancel(struct uloop_timeout *timeout)
    从定时器链表中删除指定定时器。

  • 获取定时器还剩多长时间超时 int uloop_timeout_remaining(struct uloop_timeout *timeout)
    这里 pending 标记可判断定时器是否处于生命周期,如果尚处在生命周期内,则返回离定时器超时还有多少时间,单位为毫秒。

  • 注册新进程到事件处理循环 int uloop_process_add(struct uloop_process *p)
    将进程事件插入到进程事件链表中,链表根据 PID 从小到大排序。
    其中 p->proc.pid 为注册到 uloop 监控的进程 ID。
    P->cb 为进程退出的回调函数,类型为:
    typedef void (*uloop_process_handler)(struct uloop_process *c, int ret)

  • 从事件处理循环中销毁指定进程 int uloop_process_delete(struct uloop_process *p)
    从进程事件处理链表中删除该进程事件。

结构体变量
struct uloop_fd
{
    uloop_fd_handler cb; /*文件描述符对应的处理函数 */
    int fd;              /*文件描述符*/
    bool eof;            /*EOF*/
    bool error;          /*出错*/
    bool registered;     /*是否已经添加到epoll的监控队列*/
    uint8_t flags;       /*ULOOP_READ | ULOOP_WRITE | ULOOP_BLOCKING等*/
}

struct uloop_timeout
{
    struct list_head list;      //链表节点
    bool pending;               //添加一个新的timeout pending是true, false删除该节点timeout
    uloop_timeout_handler cb;   //超时处理函数
    struct timeval time;        //超时时间
};
定时器的使用
  • 遍历定时器链表,如果有定时器已经超时,执行该定时器的回调函数(定时器功能流程)
  • 定时器超时后会自动删除,周期性的定时器需要在回调里面再次添加定时器。
  • 用户使用定时器非常简单
struct uloop_timeout *t;    //第一步定义一个定时器并申请内存空间
t = malloc(sizeof(*t));
t->cb = light_ctl_check_cb; //第二步指定回调函数
t->pending = false;
uloop_timeout_set(t, 2000); //第三步设置定时器超时时间

void timeout_callback(struct uloop_timeout *timeout)
{
    printf("timeout_callback\r\n");
    uloop_timeout_set(timeout, 5000);   //需要重新设置定时时间
}

void uloop_timeout_test(void)
{
    struct uloop_timeout fd_timeout = {
        .cb = timeout_callback, //回调函数
    };
    uloop_init();
    uloop_timeout_set(&fd_timeout, 5000); //5 second
    uloop_run();    //什么时候退出?
    uloop_timeout_cancel(&fd_timeout);
    uloop_done();
}

进程事件
  • 进程事件结构
#define ULOOP_READ          (1 << 0)
#define ULOOP_WRITE         (1 << 1)
#define ULOOP_EDGE_TRIGGER  (1 << 2)
#define ULOOP_BLOCKING      (1 << 3)
#define ULOOP_EVENT_MASK    (ULOOP_READ | ULOOP_WRITE)

struct uloop_process {
    struct list_head list;
    bool pending;
    uloop_process_handler cb;   /** 文件描述符, 调用者初始化 */
    pid_t pid;                  /** 文件描述符, 调用者初始化 */
};

void read_std_callback(struct uloop_fd *u, unsigned int events)
{
    char buf[1024] = {0};
    if (ULOOP_READ) {
        if ( read(u->fd, buf, 1024) > 0) {
            printf("read_std: %s\n", buf);
        }
    }
}

void uloop_fd_test(void)
{
    struct uloop_fd fd_test = {
        .cb = read_std_callback,
        .fd = STDIN_FILENO,
    };
    uloop_init();
    /*添加uloop_fd*/
    uloop_fd_add(&fd_test, ULOOP_READ);
    uloop_run();
    uloop_fd_delete(&fd_test);
    uloop_done();
}

uloop 服务器和客户端例程
  • 服务器代码
void recv_sock_callback(struct uloop_fd *u, unsigned int events)
{
    char buf[1024] = {0};
    int connect_fd;
    struct sockaddr_in cli_addr;
    socklen_t len = sizeof(struct sockaddr);

    connect_fd = accept(u->fd, (struct sockaddr *)(&cli_addr), &len);
    if (connect_fd < 0) {
        perror("accept");
        return;
    }
    if (recv(connect_fd, buf, 1024, 0) > 0) {
        printf("recv_buf: %s\n", buf);
    }
    close(connect_fd);
}

int usock_and_uloop_fd_test(void)
{
    int type = USOCK_TCP | USOCK_SERVER  | USOCK_NOCLOEXEC | USOCK_IPV4ONLY;
    const char *host = "127.0.0.1";
    const char *service = "1212";
    int u_fd = usock(type, host, service);
    if (u_fd < 0) {
        perror("usock");
        return -1;
    }
    struct uloop_fd fd_sock = {
        .cb = recv_sock_callback,
        .fd = u_fd,
    };
    uloop_init();
    /*添加uloop_fd*/
    uloop_fd_add(&fd_sock, ULOOP_READ);
    uloop_run();
    uloop_fd_delete(&fd_sock);
    uloop_done();

    return 0;
}
  • 客户端代码
#include "ztest.h"

void log_init(void)
{
    ulog_open(ULOG_SYSLOG, LOG_USER, NULL);
    ulog_threshold(LOG_INFO);
}

int main(int argc, char **argv){
    int type = USOCK_TCP | USOCK_NOCLOEXEC | USOCK_IPV4ONLY;
    const char *host = "127.0.0.1";
    const char *service = "1212";
    log_init();
    ULOG_INFO("--------zclient--------\n");

    int c_fd = usock(type, host, service);
    if(c_fd < 0) {
        perror("usock");
        return -1;
    }

    send(c_fd, "helloworld", 10, 0);

    close(c_fd);
    return 1;
}

欢迎加入QQ群(610849576),大家一起交流

好的,以下是一个简单的发布订阅模式的ubus通信测试代码,你可以参考一下: 服务端代码: ```c #include <libubox/uloop.h> #include <libubus.h> static struct ubus_context *ctx; static void test_subscribe_handler(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { const char *data = blobmsg_get_string(msg, "data"); printf("Received data: %s\n", data); } static const struct ubus_method test_methods[] = { { .name = "subscribe", .handler = test_subscribe_handler }, }; static struct ubus_object_type test_object_type = UBUS_OBJECT_TYPE("test", test_methods); static struct ubus_object test_object = { .name = "test", .type = &test_object_type, .methods = test_methods, .n_methods = ARRAY_SIZE(test_methods), }; static void test_notify_handler(struct ubus_context *ctx, struct ubus_subscriber *sub, uint32_t id, struct blob_attr *msg) { const char *data = blobmsg_get_string(msg, "data"); printf("Received data: %s\n", data); } int main(int argc, char **argv) { ctx = ubus_connect(NULL); if (!ctx) { fprintf(stderr, "Failed to connect to ubus\n"); return -1; } ubus_add_uloop(ctx); if (ubus_add_object(ctx, &test_object) != 0) { fprintf(stderr, "Failed to add ubus object\n"); ubus_free(ctx); return -1; } struct ubus_subscriber subscriber; memset(&subscriber, 0, sizeof(subscriber)); subscriber.cb = test_notify_handler; if (ubus_subscribe(ctx, &subscriber, "test.subscribe") != 0) { fprintf(stderr, "Failed to subscribe\n"); ubus_free(ctx); return -1; } uloop_run(); return 0; } ``` 客户端代码: ```c #include <libubox/uloop.h> #include <libubus.h> static struct ubus_context *ctx; static void test_notify_handler(struct ubus_context *ctx, struct ubus_subscriber *sub, uint32_t id, struct blob_attr *msg) { const char *data = blobmsg_get_string(msg, "data"); printf("Received data: %s\n", data); } int main(int argc, char **argv) { ctx = ubus_connect(NULL); if (!ctx) { fprintf(stderr, "Failed to connect to ubus\n"); return -1; } ubus_add_uloop(ctx); struct ubus_subscriber subscriber; memset(&subscriber, 0, sizeof(subscriber)); subscriber.cb = test_notify_handler; if (ubus_subscribe(ctx, &subscriber, "test.subscribe") != 0) { fprintf(stderr, "Failed to subscribe\n"); ubus_free(ctx); return -1; } struct blob_buf buf; blob_buf_init(&buf, 0); blobmsg_add_string(&buf, "data", "Hello, world!"); if (ubus_notify(ctx, "test.subscribe", buf.head, 0, NULL) != 0) { fprintf(stderr, "Failed to send notification\n"); ubus_free(ctx); return -1; } uloop_run(); return 0; } ``` 这个例子中,服务端会创建一个ubus对象并添加到ubus上下文中,同时会添加一个订阅处理函数和一个订阅者。当客户端发送一个通知时,服务端订阅处理函数会被调用,并且订阅者也会收到通知。客户端会先订阅test.subscribe事件,然后发送一条带有"data"字段的通知。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值