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;
}