一、简介
OpenWrt路由操作系统的框架基础软件有很多,大部分是通用的软件模块,如 dhcp 、dnsmasq、iproute、cmwp、vpn、ipsec等等;OpenWrt还集成部分具有专属特征软件模块,也是OpenWRT系统核心框架软件组件,从此篇开始分析 《OpenWrt系统框架基础软件模块》系列文章。
OpenWrt 核心软件:procd、uci、libubox、ubus、ubox、luci、netifd 软件组件内容,此部分软件模块是构成OpenWrt框架基础软件。
因为OpenWRT是小型软路由操作系统、网络协议栈知识内容十分丰富、望而生畏;我们先把庞大网络协议栈知识放一下,从易于入手的OpenWRT这个开源软路由开始入手,无论是网络协议栈还是OpenWRT的学习,总是要找到起点或入口;OpenWRT入口处就是 libubox ,因为其他软件是依托libubox库 api 接口构建起来的应用,请参考《详解 OpenWRT系统框架基础软件模块之libubox》
本篇文章分析ubus软件组件,ubus分三个部分:
- ubusd精灵进程
- ubus接口库(libubus.so)
- ubus命令行工具
二、uBus IPC/RPC 系统总线
OpenWrt 提供了一个系统总线ubus,它类似于Linux桌面操作系统的d-Bus,目标是提供系统级的进程间通信(IPC/RPC)功能。ubus在设计理念上与d-Bus基本保持一致,提供了系统级总线功能,与d-Bus相比减少了系统内存占用空间,这样可以适应于嵌入式Linux操作系统的低内存和低端CPU性能的特殊环境。
ubus是OpenWrt的RPC工具,是OpenWrt的微系统总线架构,是在2011年加入OpenWrt中的。为了提供各种后台进程和应用程序之间的通信机制,ubus工程被开发出来。
OpenWRT系统中很多应用与守护进程之间、都是通过ubus总线进行,如上图所示。ubus基于libubus库的封装建立ubusd总线守护线程;ubusd守护线程中涉及到netlink-kobject模块实现udev、hotplug功能,netlink-route模块实现路由配置相关内容,通过 AF-NETLINK协议簇实现用户与内核间通讯;
用户空间通过 AF-UNIX 协议簇实现 守护进程与用户线程间通讯,通过epoll 或 kqueue 实现IO事件驱动型队列;很多用户进程通过在ubus上注册订阅信息方式、达成事件通知到用户进程目的;ubus采用epoll监控AF-UNIX套接字状态变化,内核监控事件可以被ubusd感知并通知到注册到总线的用户进程。
本篇主要介绍ubusd应用方法。
2.1 ubusd 守护进程
ubus模块的核心是ubusd守护进程,它提供了一个总线层,在系统启动时运行,负责进程间的消息路由和传递。其他进程注册到 ubusd进程进行消息的发送和接收。
这个接口是用Linux文件socket(AF-UNIX协议簇) 和 TLV(类型-长度-值)收发消息来实现的。每一个进程在指定命名空间下注册自己的路径。每一个路径都可以提供带有各种参数的多个函数处理过程,函数处理过程程序可以在完成处理后返回消息。
- /etc/init.d/ubus: ubusd精灵进程是由/etc/init.d/ubus启动的,ubusd精灵进程在系统进程启动完成之后立即启动;
- 当ubusd 守护进程启动之后, 其他应用程序可基于libubus提供的接口或使用ubus命令行程序来和ubusd进程进行通信;
- ubus是为进程间发送消息而设计的,不适合传输大量数据(进程间传输大量数据应采用共享内存);
ubus提供的功能主要有以下4个方面:
- 提供注册对象和方法供其他实体调用
- 调用其他应用程序提供的注册对象的控制接口
- 在特定对象上注册监听事件
- 向特定对象发送事件消息
对象与方法
ubus将消息处理抽象为对象(object)和方法(method)的概念。一个对象中包含多个方法。对象和方法都有自己的名字,发送请求方在消息中指定要调用的对象和方法名字即可.
信息订阅
ubus的另外一个概念是订阅(subscriber)。客户端需要向服务器注册收到特定消息时的处理方法。这样当服务器在状态发生改变时会通过ubus总线来通知给客户端。
TLV 消息格式
- ubus可用于两个进程之间的通信,进程之间以TLV格式传递消息,用户不用关心消息的实际传输格式
- ubus能够以JSON格式和用户进行数据交换
ubus 总线应用场景
- 客户端/服务器模式:即进程A提供一些逻辑较复杂的记忆状态的服务,并且常驻内存中。进程 B 以命令行工具或函数 API 形式调用这些服务;
- 订阅通知模式:消息总线的订阅模式类同,解决对象间一对多的依赖关系,当一个对象的状态发生改变时,所有订阅的对象都会得到通知、并回调订阅者的回调函数。 即进程 A 作为服务器,当对象状态改变时通知给它所有的订阅者;
ubus接口库
ubus的接口由libubus.so 库提供,其他进程可以通过该动态链接库、来应用ubus总线。减少用户对低层封装内容学习与了解,因此本篇文章只是简述系统底层实现。
libubus.so 库接口函数列表
函数 | 含 义 |
---|---|
ubus_add_object | 将对象加入的ubus空间中,即客户端可以访问对象 |
ubus_register_subscriber | 注册订阅通知 |
ubus_connect | 连接指定的路径,创建并返回路径所代表的ubus上下文 |
ubus_send_reply | 执行完成方法调用后、发送响应 |
ubus_notify | 给对象所有的订阅者发送通知 |
ubus_lookup | 查找对象,参数path为对象的路径,如果为空则查找所有的对象。cb为回调函数,对查找结果进行处理 |
ubus_lookup_id | 查找对象的id,并将id参数在指针中返回 |
ubus_invoke | 调用对象的方法 |
ubus_register_event_handler | 注册事件处理句柄 |
ubus_send_event | 发送事件消息 |
2.2 ubus cli 命令行
ubus命令行工具也使用libubus提供的API接口来和ubusd服务器交互。这在调试注册的命名空间对象和编写shell脚本时非常有用。ubus调用参数和返回响应都使用非常友好的JSON格式。
ubus提供5种命令来进行消息通信:
root@LEDE:~# ubus
Usage: ubus [<options>] <command> [arguments...]
Options:
-s <socket>: Set the unix domain socket to connect to
-t <timeout>: Set the timeout (in seconds) for a command to complete
-S: Use simplified output (for scripts)
-v: More verbose output
-m <type>: (for monitor): include a specific message type
(can be used more than once)
-M <r|t> (for monitor): only capture received or transmitted traffic
Commands:
- list [<path>] List objects
- call <path> <method> [<message>] Call an object method
- listen [<path>...] Listen for events
- send <type> [<message>] Send an event
- wait_for <object> [<object>...] Wait for multiple objects to appear on ubus
- monitor Monitor ubus traffic
2.2.1 list 命令
ubus -v list命令 在默认情况下,输出所有注册到ubus RPC服务器的对象;
root@LEDE:~# ubus -v list
'dnsmasq' @e7db42ea
'log' @22474405
"read":{"lines":"Integer","stream":"Boolean","oneshot":"Boolean"}
"write":{"event":"String"}
'mwan3' @8e1fb6b5
"status":{"section":"String","interface":"String"}
'network' @a9177eef
"restart":{}
"reload":{}
"add_host_route":{"target":"String","v6":"Boolean","interface":"String"}
"get_proto_handlers":{}
"add_dynamic":{"name":"String"}
'network.device' @026d8077
"status":{"name":"String"}
"set_alias":{"alias":"Array","device":"String"}
"set_state":{"name":"String","defer":"Boolean"}
'network.interface' @6fbe7b26
,,,,,,
'network.interface.lan' @94745283
,,,,,,
'network.interface.loopback' @c7f1ef34
,,,,,,
'network.interface.wan' @202d4d7b
,,,,,,
'network.interface.wan6' @6b5b8013
"up":{}
"down":{}
"renew":{}
"status":{}
"prepare":{}
"dump":{}
"add_device":{"name":"String","link-ext":"Boolean"}
"remove_device":{"name":"String","link-ext":"Boolean"}
"notify_proto":{}
"remove":{}
"set_data":{}
'network.rrdns' @8475866f
"lookup":{"addrs":"Array","timeout":"Integer","server":"String","port":"(unknown)","limit":"Integer"}
'network.wireless' @5c564752
"up":{}
"down":{}
"status":{}
"notify":{}
"get_validate":{}
'service' @02f45924
"set":{"name":"String","script":"String","instances":"Table","triggers":"Array","validate":"Array","autostart":"Boolean","data":"Table"}
"add":{"name":"String","script":"String","instances":"Table","triggers":"Array","validate":"Array","autostart":"Boolean","data":"Table"}
"list":{"name":"String","verbose":"Boolean"}
"delete":{"name":"String","instance":"String"}
"signal":{"name":"String","instance":"String","signal":"Integer"}
"update_start":{"name":"String"}
"update_complete":{"name":"String"}
"event":{"type":"String","data":"Table"}
"validate":{"package":"String","type":"String","service":"String"}
"get_data":{"name":"String","instance":"String","type":"String"}
"state":{"spawn":"Boolean","name":"String"}
'session' @c16f7027
"create":{"timeout":"Integer"}
"list":{"ubus_rpc_session":"String"}
"grant":{"ubus_rpc_session":"String","scope":"String","objects":"Array"}
"revoke":{"ubus_rpc_session":"String","scope":"String","objects":"Array"}
"access":{"ubus_rpc_session":"String","scope":"String","object":"String","function":"String"}
"set":{"ubus_rpc_session":"String","values":"Table"}
"get":{"ubus_rpc_session":"String","keys":"Array"}
"unset":{"ubus_rpc_session":"String","keys":"Array"}
"destroy":{"ubus_rpc_session":"String"}
"login":{"username":"String","password":"String","timeout":"Integer"}
'system' @1d081f6b
"board":{}
"info":{}
"reboot":{}
"watchdog":{"frequency":"Integer","timeout":"Integer","magicclose":"Boolean","stop":"Boolean"}
"signal":{"pid":"Integer","signum":"Integer"}
"sysupgrade":{"path":"String","prefix":"String","command":"String"}
'uci' @837e4a22
"configs":{}
"get":{"config":"String","section":"String","option":"String","type":"String","match":"Table","ubus_rpc_session":"String"}
"state":{"config":"String","section":"String","option":"String","type":"String","match":"Table","ubus_rpc_session":"String"}
"add":{"config":"String","type":"String","name":"String","values":"Table","ubus_rpc_session":"String"}
显示指定项的内容
root@LEDE:~# ubus -v list network
'network' @a9177eef
"restart":{}
"reload":{}
"add_host_route":{"target":"String","v6":"Boolean","interface":"String"}
"get_proto_handlers":{}
"add_dynamic":{"name":"String"}
2.2.2 call命令
- Call命令在指定对象里调用指定的方法并传递消息参数
- Call命令首先调用ubus_ lookup_id函数找到指定对象的ID,然后通过ubus_invoke函数调用来请求服务器,返回的结果使用 receive_call_result_data来处理
- 消息格式必须是合法的JSON字符串格式,根据函数签名来传递正确的JSON字符串作为方法参数
root@LEDE:~# ubus call network.device status '{"name":"eth0"}'
{
"external": false,
"present": true,
"type": "Network device",
"up": true,
"carrier": true,
"link-advertising": [
"10F",
"100F",
"1000F"
],
"link-supported": [
"10F",
"100F",
"1000F"
],
"speed": "1000F",
"mtu": 1500,
"mtu6": 1500,
"macaddr": "d6:a3:06:68:e7:c2",
"txqueuelen": 1000,
"ipv6": false,
"promisc": false,
"rpfilter": 0,
"acceptlocal": false,
"igmpversion": 0,
"mldversion": 0,
"neigh4reachabletime": 30000,
"neigh6reachabletime": 30000,
"neigh4gcstaletime": 60,
"neigh6gcstaletime": 60,
"neigh4locktime": 100,
"dadtransmits": 1,
"multicast": true,
"sendredirects": true,
"statistics": {
"collisions": 0,
"rx_frame_errors": 0,
"tx_compressed": 0,
"multicast": 0,
"rx_length_errors": 0,
"tx_dropped": 0,
"rx_bytes": 118638994,
"rx_missed_errors": 0,
"tx_errors": 0,
"rx_compressed": 0,
"rx_over_errors": 0,
"tx_fifo_errors": 0,
"rx_crc_errors": 0,
"rx_packets": 807252,
"tx_heartbeat_errors": 0,
"rx_dropped": 191,
"tx_aborted_errors": 0,
"tx_packets": 1062500,
"rx_errors": 0,
"tx_bytes": 1159627009,
"tx_window_errors": 0,
"rx_fifo_errors": 0,
"tx_carrier_errors": 0
}
}
2.2.3 listen命令
- listen命令设置一个监听套接字来接收服务器发出的消息;
- listen 命令是通过ubus_register_event_handler函数来注册事件回调处理函数;
root@LEDE:~# ubus listen &
root@LEDE:~# { "network.interface": {"action":"ifdown","interface":"lan"} }
{ "ubus.object.remove": {"id":-405060886,"path":"dnsmasq"} }
{ "ubus.object.add": {"id":-860544792,"path":"dnsmasq"} }
{ "network.interface": {"action":"ifup","interface":"lan"} }
{ "ubus.object.remove": {"id":-860544792,"path":"dnsmasq"} }
{ "ubus.object.add": {"id":655993169,"path":"dnsmasq"} }
//执行指令
ubus call network.interface.lan down
ubus call network.interface.lan up
2.2.4 send 命令
- send命令用于发出一个通知事件,这个事件可以使用listen命令监听到
- send 命令是 通过调用ubus_send_event函数来实现的
- 命令行的发送数据格式必须为JSON格式,在程序中通过调用 blobmsg_add_json_from_string 函数转换为ubus的TLV格式
如果有多个监听客户端,多个监听客户端会同时收到事件 - 发送通知事件通常需要两个参数,第一个参数为指定对象,第二个参数为事件消息内容
终端1 监听
root@LEDE:~# ubus listen hello
{ "hello": {"name":"hela"} }
终端2 发送
root@LEDE:/lib/netifd/proto# ubus send hello '{"name":"hela"}'
2.2.5 wait_for命令
- wait_for 命令用于等待多个对象注册到ubus中,当等待的对象注册成功后即退出
三、ubus api 应用样例
3.1 服务端程序
注册一个test对象,其中包含三个方法hello、watch和count 。
#include <unistd.h>
#include <signal.h>
#include <libubox/blobmsg_json.h>
#include "libubus.h"
#include "count.h"
static struct ubus_context *ctx;
static struct ubus_subscriber test_event;
static struct blob_buf b;
enum {
HELLO_ID,
HELLO_MSG,
__HELLO_MAX
};
static const struct blobmsg_policy hello_policy[] = {
[HELLO_ID] = { .name = "id", .type = BLOBMSG_TYPE_INT32 },
[HELLO_MSG] = { .name = "msg", .type = BLOBMSG_TYPE_STRING },
};
struct hello_request {
struct ubus_request_data req;
struct uloop_timeout timeout;
int fd;
int idx;
char data[];
};
static void test_hello_fd_reply(struct uloop_timeout *t)
{
struct hello_request *req = container_of(t, struct hello_request, timeout);
char *data;
data = alloca(strlen(req->data) + 32);
sprintf(data, "msg%d: %s\n", ++req->idx, req->data);
if (write(req->fd, data, strlen(data)) < 0) {
close(req->fd);
free(req);
return;
}
uloop_timeout_set(&req->timeout, 1000);
}
static void test_hello_reply(struct uloop_timeout *t)
{
struct hello_request *req = container_of(t, struct hello_request, timeout);
int fds[2];
blob_buf_init(&b, 0);
blobmsg_add_string(&b, "message", req->data);
ubus_send_reply(ctx, &req->req, b.head);
if (pipe(fds) == -1) {
fprintf(stderr, "Failed to create pipe\n");
return;
}
ubus_request_set_fd(ctx, &req->req, fds[0]);
ubus_complete_deferred_request(ctx, &req->req, 0);
req->fd = fds[1];
req->timeout.cb = test_hello_fd_reply;
test_hello_fd_reply(t);
}
static int test_hello(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
struct hello_request *hreq;
struct blob_attr *tb[__HELLO_MAX];
const char format[] = "%s received a message: %s";
const char *msgstr = "(unknown)";
blobmsg_parse(hello_policy, ARRAY_SIZE(hello_policy), tb, blob_data(msg), blob_len(msg));
if (tb[HELLO_MSG])
msgstr = blobmsg_data(tb[HELLO_MSG]);
size_t len = sizeof(*hreq) + sizeof(format) + strlen(obj->name) + strlen(msgstr) + 1;
hreq = calloc(1, len);
if (!hreq)
return UBUS_STATUS_UNKNOWN_ERROR;
snprintf(hreq->data, len, format, obj->name, msgstr);
ubus_defer_request(ctx, req, &hreq->req);
hreq->timeout.cb = test_hello_reply;
uloop_timeout_set(&hreq->timeout, 1000);
return 0;
}
enum {
WATCH_ID,
WATCH_COUNTER,
__WATCH_MAX
};
static const struct blobmsg_policy watch_policy[__WATCH_MAX] = {
[WATCH_ID] = { .name = "id", .type = BLOBMSG_TYPE_INT32 },
[WATCH_COUNTER] = { .name = "counter", .type = BLOBMSG_TYPE_INT32 },
};
static void
test_handle_remove(struct ubus_context *ctx, struct ubus_subscriber *s,
uint32_t id)
{
fprintf(stderr, "Object %08x went away\n", id);
}
static int
test_notify(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
#if 0
char *str;
str = blobmsg_format_json(msg, true);
fprintf(stderr, "Received notification '%s': %s\n", method, str);
free(str);
#endif
return 0;
}
static int test_watch(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
struct blob_attr *tb[__WATCH_MAX];
int ret;
blobmsg_parse(watch_policy, __WATCH_MAX, tb, blob_data(msg), blob_len(msg));
if (!tb[WATCH_ID])
return UBUS_STATUS_INVALID_ARGUMENT;
test_event.remove_cb = test_handle_remove;
test_event.cb = test_notify;
ret = ubus_subscribe(ctx, &test_event, blobmsg_get_u32(tb[WATCH_ID]));
fprintf(stderr, "Watching object %08x: %s\n", blobmsg_get_u32(tb[WATCH_ID]), ubus_strerror(ret));
return ret;
}
enum {
COUNT_TO,
COUNT_STRING,
__COUNT_MAX
};
static const struct blobmsg_policy count_policy[__COUNT_MAX] = {
[COUNT_TO] = { .name = "to", .type = BLOBMSG_TYPE_INT32 },
[COUNT_STRING] = { .name = "string", .type = BLOBMSG_TYPE_STRING },
};
static int test_count(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
struct blob_attr *tb[__COUNT_MAX];
char *s1, *s2;
uint32_t num;
blobmsg_parse(count_policy, __COUNT_MAX, tb, blob_data(msg), blob_len(msg));
if (!tb[COUNT_TO] || !tb[COUNT_STRING])
return UBUS_STATUS_INVALID_ARGUMENT;
num = blobmsg_get_u32(tb[COUNT_TO]);
s1 = blobmsg_get_string(tb[COUNT_STRING]);
s2 = count_to_number(num);
if (!s1 || !s2) {
free(s2);
return UBUS_STATUS_UNKNOWN_ERROR;
}
blob_buf_init(&b, 0);
blobmsg_add_u32(&b, "rc", strcmp(s1, s2));
ubus_send_reply(ctx, req, b.head);
free(s2);
return 0;
}
// 注册 三个方法 分别如下
static const struct ubus_method test_methods[] = {
UBUS_METHOD("hello", test_hello, hello_policy),
UBUS_METHOD("watch", test_watch, watch_policy),
UBUS_METHOD("count", test_count, count_policy),
};
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 server_main(void)
{
int ret;
ret = ubus_add_object(ctx, &test_object);
if (ret)
fprintf(stderr, "Failed to add object: %s\n", ubus_strerror(ret));
ret = ubus_register_subscriber(ctx, &test_event);
if (ret)
fprintf(stderr, "Failed to add watch handler: %s\n", ubus_strerror(ret));
uloop_run();
}
int main(int argc, char **argv)
{
const char *ubus_socket = NULL;
int ch;
while ((ch = getopt(argc, argv, "cs:")) != -1) {
switch (ch) {
case 's':
ubus_socket = optarg;
break;
default:
break;
}
}
uloop_init();
signal(SIGPIPE, SIG_IGN);
ctx = ubus_connect(ubus_socket);
if (!ctx) {
fprintf(stderr, "Failed to connect to ubus\n");
return -1;
}
ubus_add_uloop(ctx);
server_main();
ubus_free(ctx);
uloop_done();
return 0;
}
3.2 客户端程序
#include <sys/time.h>
#include <unistd.h>
#include <libubox/ustream.h>
#include "libubus.h"
#include "count.h"
static struct ubus_context *ctx;
static struct blob_buf b;
static void test_client_subscribe_cb(struct ubus_context *ctx, struct ubus_object *obj)
{
fprintf(stderr, "Subscribers active: %d\n", obj->has_subscribers);
}
static struct ubus_object test_client_object = {
.subscribe_cb = test_client_subscribe_cb,
};
static void test_client_notify_cb(struct uloop_timeout *timeout)
{
static int counter = 0;
int err;
struct timeval tv1, tv2;
int max = 1000;
long delta;
int i = 0;
blob_buf_init(&b, 0);
blobmsg_add_u32(&b, "counter", counter++);
gettimeofday(&tv1, NULL);
for (i = 0; i < max; i++)
err = ubus_notify(ctx, &test_client_object, "ping", b.head, 1000);
gettimeofday(&tv2, NULL);
if (err)
fprintf(stderr, "Notify failed: %s\n", ubus_strerror(err));
delta = (tv2.tv_sec - tv1.tv_sec) * 1000000 + (tv2.tv_usec - tv1.tv_usec);
fprintf(stderr, "Avg time per iteration: %ld usec\n", delta / max);
uloop_timeout_set(timeout, 1000);
}
enum {
RETURN_CODE,
__RETURN_MAX,
};
static const struct blobmsg_policy return_policy[__RETURN_MAX] = {
[RETURN_CODE] = { .name = "rc", .type = BLOBMSG_TYPE_INT32 },
};
static void test_count_data_cb(struct ubus_request *req,
int type, struct blob_attr *msg)
{
struct blob_attr *tb[__RETURN_MAX];
int rc;
uint32_t count_to = *(uint32_t *)req->priv;
blobmsg_parse(return_policy, __RETURN_MAX, tb, blob_data(msg), blob_len(msg));
if (!tb[RETURN_CODE]) {
fprintf(stderr, "No return code received from server\n");
return;
}
rc = blobmsg_get_u32(tb[RETURN_CODE]);
if (rc)
fprintf(stderr, "Corruption of data with count up to '%u'\n", count_to);
else
fprintf(stderr, "Server validated our count up to '%u'\n", count_to);
}
static void test_count(struct uloop_timeout *timeout)
{
enum {
COUNT_TO_MIN = 10000,
COUNT_TO_MAX = 1000000,
PROGRESSION = 100,
};
uint32_t id;
static uint32_t count_to = 100000;
static int count_progression = PROGRESSION;
char *s;
if (count_to <= COUNT_TO_MIN)
count_progression = PROGRESSION;
else if (count_to >= COUNT_TO_MAX)
count_progression = -PROGRESSION;
count_to += count_progression;
s = count_to_number(count_to);
if (!s) {
fprintf(stderr, "Could not allocate memory to count up to '%u'\n", count_to);
return;
}
fprintf(stderr, "Sending count up to '%u'; string has length '%u'\n",
count_to, (uint32_t)strlen(s));
blob_buf_init(&b, 0);
blobmsg_add_u32(&b, "to", count_to);
blobmsg_add_string(&b, "string", s);
if (ubus_lookup_id(ctx, "test", &id)) {
free(s);
fprintf(stderr, "Failed to look up test object\n");
return;
}
ubus_invoke(ctx, id, "count", b.head, test_count_data_cb, &count_to, 5000);
free(s);
uloop_timeout_set(timeout, 2000);
}
static struct uloop_timeout notify_timer = {
.cb = test_client_notify_cb,
};
static struct uloop_timeout count_timer = {
.cb = test_count,
};
static void test_client_fd_data_cb(struct ustream *s, int bytes)
{
char *data, *sep;
int len;
data = ustream_get_read_buf(s, &len);
if (len < 1)
return;
sep = strchr(data, '\n');
if (!sep)
return;
*sep = 0;
fprintf(stderr, "Got line: %s\n", data);
ustream_consume(s, sep + 1 - data);
}
static void test_client_fd_cb(struct ubus_request *req, int fd)
{
static struct ustream_fd test_fd;
fprintf(stderr, "Got fd from the server, watching...\n");
test_fd.stream.notify_read = test_client_fd_data_cb;
ustream_fd_init(&test_fd, fd);
}
static void test_client_complete_cb(struct ubus_request *req, int ret)
{
fprintf(stderr, "completed request, ret: %d\n", ret);
}
static void client_main(void)
{
static struct ubus_request req;
uint32_t id;
int ret;
ret = ubus_add_object(ctx, &test_client_object);
if (ret) {
fprintf(stderr, "Failed to add_object object: %s\n", ubus_strerror(ret));
return;
}
if (ubus_lookup_id(ctx, "test", &id)) {
fprintf(stderr, "Failed to look up test object\n");
return;
}
blob_buf_init(&b, 0);
blobmsg_add_u32(&b, "id", test_client_object.id);
ubus_invoke(ctx, id, "watch", b.head, NULL, 0, 3000);
test_client_notify_cb(¬ify_timer);
blob_buf_init(&b, 0);
blobmsg_add_string(&b, "msg", "blah");
ubus_invoke_async(ctx, id, "hello", b.head, &req);
req.fd_cb = test_client_fd_cb;
req.complete_cb = test_client_complete_cb;
ubus_complete_request_async(ctx, &req);
uloop_timeout_set(&count_timer, 2000);
uloop_run();
}
int main(int argc, char **argv)
{
const char *ubus_socket = NULL;
int ch;
while ((ch = getopt(argc, argv, "cs:")) != -1) {
switch (ch) {
case 's':
ubus_socket = optarg;
break;
default:
break;
}
}
uloop_init();
ctx = ubus_connect(ubus_socket);
if (!ctx) {
fprintf(stderr, "Failed to connect to ubus\n");
return -1;
}
ubus_add_uloop(ctx);
client_main();
ubus_free(ctx);
uloop_done();
return 0;
}
参考链接
https://openwrt.org/docs/techref/ubus
源码链接
https://git.openwrt.org/?a=project_list;pf=project