OpenWRT | Ubus编程技术

本文介绍OpenWRT的Ubus技术,它是进程间通信通用框架,有封装API和易理解的demo。阐述了其实现原理,以client请求修改ip地址为例说明消息流转过程。还给出操作实例,介绍Ubus编程的三种应用场景,并对多个DEMO代码的现象和注意点进行说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

OpenWRT的是进程间通信的通用框架。在项目开发中,利用该技术可以将消息,从一个进程发送给另一个进程,它有封装的API,易于使用与理解的demo。避免了去编写非常底层的进程间通信代码(比如Linux中常用的进程间通信方法,管道,信号量,套接字),可以专注于逻辑实现。

实现原理

下面以一个例子说明ubus的工作原理:
ubusd提供ubus的Server总线功能,其下连接着无数的client客户端。
下图中,client2试图通过ubus修改ip地址,而修改ip地址的函数在client1中定义。
在这里插入图片描述

client2进行请求的整个过程为:

  1. client1向ubusd注册了两个对象:“interface”和“dotalk”,其中“interface”对象中注册了两个method:“getlanip”和“setlanip”,对应的处理函数分别为func1()和func2()。“dotalk”对象中注册了两个method:“sayhi”和“saybye”,对应的处理函数分别为func3()和func4()。

  2. 接着创建一个client2用来与client1通信,注意,两个client之间不能直接通信,需要经ubusd(server)中转。

  3. client2就是shell/lua/C客户端。假设这里使用shell客户端,在终端输入以下命令:

    ubus call interface setlanip ‘{“ip”:10.0.0.1, “mask”:24}

    ubus的call命令带三个参数:请求的对象名,需要调用的方法名,要传给方法的参数。

  4. 消息发到server后,server根据对象名找到应该将请求转发给client1,然后将消息发送到client1,client1进而调用func2()接受参数并处理,如果处理完成后需要回复client2,则发送回复消息。

操作实例

查看挂载在ubus总线上的对象和方法,下图省略一些内容,只查看了system对象和提供的方法。

root@H1:/# ubus -v list
......
'system' @21b07ab8
        "board":{}
        "info":{}
        "reboot":{}
        "watchdog":{"frequency":"Integer","timeout":"Integer","magicclose":"Boolean","stop":"Boolean"}
        "signal":{"pid":"Integer","signum":"Integer"}
        "validate_firmware_image":{"path":"String"}
        "sysupgrade":{"path":"String","force":"Boolean","backup":"String","prefix":"String","command":"String","options":"Table"}
......

其中,

  • ‘system’ @@21b07ab8:表示应用创建了system对象,挂载在ubus总线上,并且其client id为21b07ab8
  • “board”:{}:表示提供方法名’board’,调用时不需要提供参数(以下类推)
  • “signal”:{“pid”:“Integer”,“signum”:“Integer”}:表示提供方法名’signal’,调用时需要提供json参数,分别为pid(数字类型)和signum(数字类型)

例如,我们获取系统的软件版本信息:

root@H1:/# ubus call system board 
{
        "kernel": "5.10.110",
        "hostname": "H1",
        "system": "ARMv8 Processor rev 2",
        "model": "Firefly RK3308-ROC-CC-PLUS Board",
        "board_name": "firefly,rk3308-roc-cc-plus",
        "rootfs_type": "ext4",
        "release": {
                "distribution": "OpenWrt",
                "version": "22.03.3",
                "revision": "r20028-43d71ad93e",
                "target": "rockchip/armv8",
                "description": "OpenWrt 22.03.3 r20028-43d71ad93e"
        }
}

例如:我们终止掉某个应用:

root@H1:/# ps | grep cat
 4154 root      1248 S    cat /proc/kmsg
root@H1:/# ubus call system signal "{'pid':4154, 'signum':2}"
[1]+  Interrupt                  cat /proc/kmsg

Ubus编程

Ubus编程,总结起来分为三种应用场景:

  • invoke调用:上面操作实例就是使用了invoke调用。实际上通过ubus -v list查看到的对象和应用,是系统的netifd进程(OpenWRT的网络管理后端)和hostapd进程(AP无线热点进程),通过invoke接口注册到ubus总线上的,其他客户端(比如shell调用ubus call)发送请求,将参数发送到netifd或hostapd,之后,netifd和hostapd对发送来的消息进行处理,并进行回复。该注意的是,整个过程是同步的,发送请求的客户端会阻塞等待消息的回复。
  • event调用:挂载在Ubus总线上的客户端A广播消息,挂载的其他客户端B,C,D自己去监听。
  • sub和pub调用:挂载在Ubus总线的客户端不定期将消息发送到某个主题,挂载的其他客户单B,C,D去监听对应主题,从而获取信息

DEMO

01_ubus_server.c

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

#include <libubox/blobmsg_json.h>
#include <libubox/list.h>
#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>
#include <libubus.h>

struct ubus_context *ctx;
static struct blob_buf b;

enum {
    LOOP_A,
    LOOP_B,
    LOOP_C,
    _LOOP_MAX,
};

static const struct blobmsg_policy loop_policy[] = {
    { .name = "a", .type = BLOBMSG_TYPE_BOOL },
    { .name = "b", .type = BLOBMSG_TYPE_STRING },
    { .name = "c", .type = BLOBMSG_TYPE_INT32 },
};

// 定时器超时回调
static void ubus_reconn_timer_cb(struct uloop_timeout* timeout)
{
    static struct uloop_timeout reconn_timer = {
        .cb = ubus_reconn_timer_cb
    };

    if (ubus_reconnect(ctx, NULL) != 0) {
        printf("Failed to reconnect, trying again in 2 seconds\n");
        uloop_timeout_set(&reconn_timer, 2000);
        return;
    }

    // 添加ubus到uloop
    ubus_add_uloop(ctx);
    fcntl(ctx->sock.fd, F_SETFD,
          fcntl(ctx->sock.fd, F_GETFD) | FD_CLOEXEC);

    printf("Reconnected to ubus, new id: %d\n", ctx->local_id);
}

// 连接断开回调
static void ubus_conn_lost_cb(struct ubus_context* ctx)
{
    printf("Connection lost, trying to connect again after 2 seconds\n");
    ubus_reconn_timer_cb(NULL);
}

static int hello_cb(struct ubus_context* ctx, struct ubus_object* obj, struct ubus_request_data* req, const char* method, struct blob_attr* msg)
{
    blob_buf_init(&b, 0);
    blobmsg_add_string(&b, "hello", "world");
    ubus_send_reply(ctx, req, b.head);
    return 0;
}

static int loop_cb(struct ubus_context* ctx, struct ubus_object* obj, struct ubus_request_data* req, const char* method, struct blob_attr* msg)
{
    struct blob_attr *tb[_LOOP_MAX];
    blobmsg_parse(loop_policy, _LOOP_MAX, tb, blob_data(msg), blob_len(msg));

    blob_buf_init(&b, 0);

    if (tb[LOOP_A]) {
        bool a = blobmsg_get_bool(tb[LOOP_A]);
        blobmsg_add_u8(&b, "a", a);
    }
    
    if (tb[LOOP_B]) {
        char *str = blobmsg_get_string(tb[LOOP_B]);
        blobmsg_add_string(&b, "b", str);
    }

    if (tb[LOOP_C]) {
        int c = blobmsg_get_u32(tb[LOOP_C]);
        blobmsg_add_u32(&b, "c", c);
    }

    ubus_send_reply(ctx, req, b.head);
    return 0;
}

int main()
{
    static struct ubus_method hello_methods[] = {
        UBUS_METHOD_NOARG("hello", hello_cb),
        UBUS_METHOD("loop", loop_cb, loop_policy),
    };
    static struct ubus_object_type hello_type = {
        .name = "hello",
        .id = 0,
        .methods = hello_methods,
        .n_methods = ARRAY_SIZE(hello_methods),
    };
    static struct ubus_object hello_obj = {
        .name = "hello",
        .type = &hello_type,
        .methods = hello_methods,
        .n_methods = ARRAY_SIZE(hello_methods),
    };

    uloop_init();

    if ((ctx = ubus_connect(NULL))) {
        printf("Connected ubus as id: %d\n", ctx->local_id);
        ctx->connection_lost = ubus_conn_lost_cb;
    }

    // 添加ubus到uloop
    ubus_add_uloop(ctx);
    fcntl(ctx->sock.fd, F_SETFD,
          fcntl(ctx->sock.fd, F_GETFD) | FD_CLOEXEC);

    // 添加object到ubus
    if (ubus_add_object(ctx, &hello_obj)) {
        printf("Failed to add object %s to ubus\n", hello_obj.name);
    }

    uloop_run();
    uloop_done();
    return 0;
}

现象:

root@DataJar-0C7D:~# ubus -v list hello
'hello' @01678e9a
        "hello":{}
        "loop":{"a":"Boolean","b":"String","c":"Integer"}
root@DataJar-0C7D:~# ubus call hello hello
{
        "hello": "world"
}
root@DataJar-0C7D:~# ubus call hello loop '{"a":false}'
{
        "a": false
}
root@DataJar-0C7D:~# ubus call hello loop '{"a":false, "b":"123456"}'
{
        "a": false,
        "b": "123456"
}

注意点:

  • 连接之后,可能会断开,比如 ubusd 被杀掉了,所以在 ubus_connect 之后,需要在 connection_lost 里面添加断开之后,添加定时器进行重连 ubus

02_ubus_client.c

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

#include <libubox/blobmsg_json.h>
#include <libubox/list.h>
#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>
#include <libubus.h>

static struct ubus_context* ctx;
static struct blob_buf b;

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

    // blobmsg_format_json 返回一块动态分配内存,需要释放
    char* json_string = blobmsg_format_json(msg, true);
    char** recv_string = (char**)(req->priv);
    *recv_string = json_string;
}

int main(int argc, char **argv)
{
    unsigned int id;

    char *path = "iwinfo";
    char *method = "scan";
    // 连接ubus
    if (!(ctx = ubus_connect(NULL))) {
        printf("Failed to connect ubus!\n");
        return -1;
    }

    // 查找iwinfo
    if (ubus_lookup_id(ctx, path, &id)) {
        printf("Failed to lookup ubus path: %s\n", path);
        return -1;
    }

    blob_buf_init(&b, 0);
    blobmsg_add_string(&b, "device", "wlan0");
    
    // ubus_invoke的timeout参数为0,表示不进行超时处理
    int ms_timeout = 0;
    char *recv_string = NULL;

    // 等价于:ubus call iwinfo scan '{"device":"wlan0"}'
    // ubus_invoke 确保回调执行完才返回
    int ret = ubus_invoke(ctx, id, "scan", b.head, scan_cb, &recv_string, ms_timeout);
    if (ret) {
        printf("Unable to invoke method: scan");
        return -1;
    }

    if (recv_string) {
        printf("recv: %s\n", recv_string);
        free(recv_string);
    }

    return 0;
}

现象:

等价于调用: ubus call iwinfo scan ‘{“device”:“wlan0”}’

root@DataJar-0C7D:~# ./02_ubus_client 
[ 2872.557492] start_addr=(0x8000), end_addr=(0x10000), buffer_size=(0x8000), smp_number_max=(4096)
recv: {"results":[{"ssid":"sjf_2.4G","bssid":"74:B9:1E:05:E2:B1","mode":"Master","band":2,"channel":1,"mhz":2412,"signal":-54,"quality":56,"quality_max":70,"ht_operation":{"primary_channel":}

注意点:

  • ubus_invoke 是阻塞运行的函数,在ms_timeout设置了这个函数最长阻塞时间,其中的回调函数确保在ubus_invoke回调完成之前执行。

03_ubus_pub.c

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

#include <libubox/blobmsg_json.h>
#include <libubox/list.h>
#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>
#include <libubus.h>

struct ubus_context *ctx;
static struct blob_buf b;
static struct uloop_timeout notify_timer;

static int hello_cb(struct ubus_context* ctx, struct ubus_object* obj, struct ubus_request_data* req, const char* method, struct blob_attr* msg);

static struct ubus_method hello_methods[] = {
    UBUS_METHOD_NOARG("hello", hello_cb),
};
static struct ubus_object_type hello_type = {
    .name = "hello",
    .id = 0,
    .methods = hello_methods,
    .n_methods = ARRAY_SIZE(hello_methods),
};
static struct ubus_object hello_obj = {
    .name = "hello",
    .type = &hello_type,
    .methods = hello_methods,
    .n_methods = ARRAY_SIZE(hello_methods),
};

static int hello_cb(struct ubus_context* ctx, struct ubus_object* obj, struct ubus_request_data* req, const char* method, struct blob_attr* msg)
{
    blob_buf_init(&b, 0);
    blobmsg_add_string(&b, "hello", "world");
    ubus_send_reply(ctx, req, b.head);
    return 0;
}

static void notify_timer_cb(struct uloop_timeout *timeout)
{
    uloop_timeout_set(timeout, 1000);

    // 如果没有人监听,直接退出
    if (!hello_obj.has_subscribers)
        return;

    blob_buf_init(&b, 0);
    blobmsg_add_string(&b, "hello", "world");

    ubus_notify(ctx, &hello_obj, "say Hi!", b.head, -1);
}

// 定时器超时回调
static void ubus_reconn_timer_cb(struct uloop_timeout* timeout)
{
    static struct uloop_timeout reconn_timer = {
        .cb = ubus_reconn_timer_cb
    };

    if (ubus_reconnect(ctx, NULL) != 0) {
        printf("Failed to reconnect, trying again in 2 seconds\n");
        uloop_timeout_set(&reconn_timer, 2000);
        return;
    }

    // 添加ubus到uloop
    ubus_add_uloop(ctx);
    fcntl(ctx->sock.fd, F_SETFD,
          fcntl(ctx->sock.fd, F_GETFD) | FD_CLOEXEC);

    printf("Reconnected to ubus, new id: %d\n", ctx->local_id);
}

// 连接断开回调
static void ubus_conn_lost_cb(struct ubus_context* ctx)
{
    printf("Connection lost, trying to connect again after 2 seconds\n");
    ubus_reconn_timer_cb(NULL);
}

int main()
{
    uloop_init();

    if ((ctx = ubus_connect(NULL))) {
        printf("Connected ubus as id: %d\n", ctx->local_id);
        ctx->connection_lost = ubus_conn_lost_cb;
    }

    // 添加ubus到uloop
    ubus_add_uloop(ctx);
    fcntl(ctx->sock.fd, F_SETFD,
          fcntl(ctx->sock.fd, F_GETFD) | FD_CLOEXEC);

    // 添加object到ubus
    if (ubus_add_object(ctx, &hello_obj)) {
        printf("Failed to add object %s to ubus\n", hello_obj.name);
    }

    // 初始化定时器
    memset(&notify_timer, 0, sizeof(notify_timer));
    notify_timer.cb = notify_timer_cb;
    uloop_timeout_set(&notify_timer, 1000);

    uloop_run();
    uloop_done();
    return 0;
}

现象:

root@DataJar-0C7D:~# ubus -v list hello
'hello' @c66a3a47
        "hello":{}
root@DataJar-0C7D:~# ubus subscribe hello
{ "say Hi!": {"hello":"world"} }
{ "say Hi!": {"hello":"world"} }
{ "say Hi!": {"hello":"world"} }
{ "say Hi!": {"hello":"world"} }

注意点:

  • 如果 ubus 上没有被监听,通过查询 ubus_object 的 has_subscribers 是否为 true,才进行通知,否则通知了也没有作用,这个消息是无用功

04_ubus_sub.c

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

#include <libubox/blobmsg_json.h>
#include <libubox/list.h>
#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>
#include <libubus.h>

static struct ubus_context *ctx;

static uint32_t obj_id;
static struct ubus_subscriber sub_event;
static struct uloop_timeout sub_timer;

static int subscriber_cb(struct ubus_context *ctx, struct ubus_object *obj,
                  struct ubus_request_data *req,
                  const char *method, struct blob_attr *msg)
{

    char* json_string = blobmsg_format_json(msg, true);
    printf("notify_cb: %s\n", json_string);
    free(json_string);
    return 0;
}

static void subscriber_timeout_cb(struct uloop_timeout *timeout)
{
    char* object = "hello";
    int ret = ubus_lookup_id(ctx, object, &obj_id);
    if (ret) {
        printf("Failed to lookup object: %s\n", ubus_strerror(ret));
        uloop_timeout_set(timeout, 1000); 
        return;
    }

    ret = ubus_subscribe(ctx, &sub_event, obj_id);
    if (ret) {
        printf("Failed to subscribe: %s\n", ubus_strerror(ret));
        uloop_timeout_set(timeout, 1000); 
        return;
    }
}

static void subscriber_remove_cb(struct ubus_context *ctx,
                      struct ubus_subscriber *obj, uint32_t id)
{
    ubus_unsubscribe(ctx, obj, id);
    subscriber_timeout_cb(&sub_timer);
}


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到uloop
    ubus_add_uloop(ctx);
    fcntl(ctx->sock.fd, F_SETFD,
          fcntl(ctx->sock.fd, F_GETFD) | FD_CLOEXEC);

    // 初始化结构体
    memset(&sub_event, 0, sizeof(sub_event));
    sub_event.cb = subscriber_cb;               // 这个回调中处理通知
    sub_event.remove_cb = subscriber_remove_cb; // 这个回调一般用来发起重监听操作

    memset(&sub_timer, 0, sizeof(sub_timer));
    sub_timer.cb = subscriber_timeout_cb;

    // 注册监听
    ubus_register_subscriber(ctx, &sub_event);

    // 监听回调 
    subscriber_timeout_cb(&sub_timer);

    uloop_run();
    uloop_done();

    return 0;
}

现象:

root@DataJar-0C7D:~# ./03_ubus_pub &
root@DataJar-0C7D:~# Connected ubus as id: -1614399908

root@DataJar-0C7D:~# ./04_ubus_sub 
notify_cb: {"hello":"world"}
notify_cb: {"hello":"world"}
notify_cb: {"hello":"world"}
notify_cb: {"hello":"world"}

注意点:

  • 监听之前需要用 ubus_lookup_id 查找 ubus,之后调用 ubus_subscribe 开始监听
  • 如果监听失败了,可以添加定时器,超时重新监听
  • 如果被监听的 ubus 断开了,remove_cb 会被回调,可以在里面添加定时器,重新监听

05_ubus_listener.c

#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>
#include <libubus.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;
}

05_ubus_event_notify.c

#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>
#include <libubus.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;
}

06_ubus_rpc.c

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

#include <libubox/blobmsg_json.h>
#include <libubox/list.h>
#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>
#include <libubus.h>

struct ubus_context *ctx;
static struct blob_buf b;

static struct uloop_process proc;
static struct ubus_request_data new_req;

// 定时器超时回调
static void ubus_reconn_timer_cb(struct uloop_timeout* timeout)
{
    static struct uloop_timeout reconn_timer = {
        .cb = ubus_reconn_timer_cb
    };

    if (ubus_reconnect(ctx, NULL) != 0) {
        printf("Failed to reconnect, trying again in 2 seconds\n");
        uloop_timeout_set(&reconn_timer, 2000);
        return;
    }

    // 添加ubus到uloop
    ubus_add_uloop(ctx);
    fcntl(ctx->sock.fd, F_SETFD,
          fcntl(ctx->sock.fd, F_GETFD) | FD_CLOEXEC);

    printf("Reconnected to ubus, new id: %d\n", ctx->local_id);
}

// 连接断开回调
static void ubus_conn_lost_cb(struct ubus_context* ctx)
{
    printf("Connection lost, trying to connect again after 2 seconds\n");
    ubus_reconn_timer_cb(NULL);
}

static int hello_cb(struct ubus_context* ctx, struct ubus_object* obj, struct ubus_request_data* req, const char* method, struct blob_attr* msg)
{
    blob_buf_init(&b, 0);
    blobmsg_add_string(&b, "hello", "world");
    ubus_send_reply(ctx, req, b.head);
    return 0;
}

static void process_cb(struct uloop_process *c, int ret)
{
    // 判断是否是通过exit退出,而不是信号
    if (WIFEXITED(ret)) {
        printf("child process exit with: %d\n", WEXITSTATUS(ret));
        blob_buf_init(&b, 0);
        blobmsg_add_string(&b, "defer", "done");
        // 返回数据
        ubus_send_reply(ctx, &new_req, b.head);
        // 完成延时请求
        ubus_complete_deferred_request(ctx, &new_req, 0);
    }
}

static int defer_cb(struct ubus_context* ctx, struct ubus_object* obj, struct ubus_request_data* req, const char* method, struct blob_attr* msg)
{
    pid_t pid = fork();
    const char* args[] = {"sleep", "10", NULL};

    if (!pid) {
        execvp(args[0], (char **)args);
        exit(127);
    }

    proc.cb =  process_cb;
    proc.pid = pid;
    uloop_process_add(&proc);

    // 存储延迟请求
    ubus_defer_request(ctx, req, &new_req);
    return 0;
}

int main()
{
    static struct ubus_method hello_methods[] = {
        UBUS_METHOD_NOARG("hello", hello_cb),
        UBUS_METHOD_NOARG("defer", defer_cb),
    };
    static struct ubus_object_type hello_type = {
        .name = "hello",
        .id = 0,
        .methods = hello_methods,
        .n_methods = ARRAY_SIZE(hello_methods),
    };
    static struct ubus_object hello_obj = {
        .name = "hello",
        .type = &hello_type,
        .methods = hello_methods,
        .n_methods = ARRAY_SIZE(hello_methods),
    };

    uloop_init();

    if ((ctx = ubus_connect(NULL))) {
        printf("Connected ubus as id: %d\n", ctx->local_id);
        ctx->connection_lost = ubus_conn_lost_cb;
    }

    // 添加ubus到uloop
    ubus_add_uloop(ctx);
    fcntl(ctx->sock.fd, F_SETFD,
          fcntl(ctx->sock.fd, F_GETFD) | FD_CLOEXEC);

    // 添加object到ubus
    if (ubus_add_object(ctx, &hello_obj)) {
        printf("Failed to add object %s to ubus\n", hello_obj.name);
    }

    uloop_run();
    uloop_done();
    return 0;
}

现象:

root@DataJar-0C7D:~# time ubus call hello defer
child process exit with: 0
{
        "defer": "done"
}
real    0m 10.01s
user    0m 0.00s
sys     0m 0.00s
root@DataJar-0C7D:~# ubus call hello defer &
root@DataJar-0C7D:~# ubus call hello hello
{
        "hello": "world"
}
root@DataJar-0C7D:~# ubus call hello hello
{
        "hello": "world"
}
root@DataJar-0C7D:~# ubus call hello hello
{
        "hello": "world"
}
root@DataJar-0C7D:~# ubus call hello hello
{
        "hello": "world"
}
root@DataJar-0C7D:~# child process exit with: 0
{
        "defer": "done"
}

[1]+  Done                       ubus call hello defer

注意:

  • 这个例子,显示ubus也可以被阻塞,在上面的defer请求中,执行了一个子进程,并等待子进程的结果,于是调用 ubus_defer_request 存储请求,在子进程执行完成后,回调函数 process_cb 被调用,最后使用 ubus_complete_deferred_request 标记请求已经完成,ubus最终返回。在这个过程中, hello 请求仍然可以正常调用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值