30 --> 详解 OpenWRT系统框架基础软件模块之ubus

一、简介

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(&notify_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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值