[openwrt] ubus实现进程间通信举例
1. invoke的方式实现端对端通信
最简单的情景就是一个提供服务的server端,一个请求服务的client端,client请求server的服务。
下面的例子中,server注册了一个名为“scan_prog”的对象,该对象中提供一个“scan”方法:
ubus_invoke.h:
#ifndef __UBUS_INVOKE_H__
#define __UBUS_INVOKE_H__
#include <json/json.h>
#include <libubox/blobmsg_json.h>struct prog_attr {char name[64];int chn_id;
};
#define PROG_MAX 8#endif /* __UBUS_INVOKE_H__ */
invoke_server.c:
#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>
#include <libubus.h>
#include <json/json.h>
#include <libubox/blobmsg_json.h>
#include "ubus_invoke.h"static struct ubus_context * ctx = NULL;
static struct blob_buf b;
static const char * sock_path;static struct prog_attr uri_list[PROG_MAX] =
{{"program_beijing", 1},{"program_guangzhou", 2},{"program_shanghai", 3},{"", -1},
};enum
{SCAN_CHNID,SCAN_POLICY_MAX,
};static const struct blobmsg_policy scan_policy[SCAN_POLICY_MAX] = {[SCAN_CHNID] = {.name = "chnID", .type = BLOBMSG_TYPE_INT32},
};static int ubus_start_scan(struct ubus_context *ctx, struct ubus_object *obj,struct ubus_request_data *req, const char *method,struct blob_attr *msg)
{int ret = -1;void * json_list = NULL;void * json_uri = NULL;int idx;blob_buf_init(&b, 0);struct blob_attr *tb[SCAN_POLICY_MAX];blobmsg_parse(scan_policy, SCAN_POLICY_MAX, tb, blob_data(msg), blob_len(msg));/*本例子中,如果请求特定的节目号,返回节目名称。如果请求节目号是0,则返回所有节目列表。*/if (tb[SCAN_CHNID] != NULL){int chnid = blobmsg_get_u32(tb[SCAN_CHNID]);if (chnid == 0){json_uri = blobmsg_open_array(&b, "prog_list");for (idx = 0; idx < PROG_MAX; idx++){if ('\0' != uri_list[idx].name[0]){json_list = blobmsg_open_table(&b, NULL);blobmsg_add_string(&b, "name", uri_list[idx].name);blobmsg_add_u32(&b, "channel", uri_list[idx].chn_id);blobmsg_close_table(&b, json_list);}}blobmsg_close_array(&b, json_uri);ret = 0;}else{for (idx = 0; idx < PROG_MAX; idx++){if ('\0' != uri_list[idx].name[0] && uri_list[idx].chn_id == chnid){blobmsg_add_string(&b, "name", uri_list[idx].name);ret = 0;}}}}blobmsg_add_u32(&b, "result", ret);ubus_send_reply(ctx, req, b.head);return 0;
}/* 方法列表 */
static struct ubus_method scan_methods[] =
{UBUS_METHOD("scan", ubus_start_scan, scan_policy),
};/* type目前没有实际用处 */
static struct ubus_object_type scan_obj_type
= UBUS_OBJECT_TYPE("scan_prog", scan_methods);static struct ubus_object scan_obj =
{.name = "scan_prog", /* 对象的名字 */.type = &scan_obj_type,.methods = scan_methods,.n_methods = ARRAY_SIZE(scan_methods),
};static void ubus_add_fd(void)
{ubus_add_uloop(ctx);#ifdef FD_CLOEXECfcntl(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, sock_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 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);ctx->connection_lost = ubus_connection_lost;ubus_add_fd();/* 向ubusd注册对象和方法,供其他ubus客户端调用。 */if (ubus_add_object(ctx, &scan_obj) != 0){printf("ubus add obj failed\n");return -1;}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;}uloop_run();display_ubus_done();return 0;
}
客户端代码invoke_client.c去调用server端的"scan_prog"对象的"scan"方法:
#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>
#include <libubus.h>
#include <json/json.h>
#include <libubox/blobmsg_json.h>
#include "ubus_invoke.h"static struct ubus_context * ctx = NULL;
static struct blob_buf b;
static const char * cli_path;enum
{SCAN_CHNID,SCAN_POLICY_MAX,
};static const struct blobmsg_policy scan_policy[SCAN_POLICY_MAX] = {[SCAN_CHNID] = {.name = "chnID", .type = BLOBMSG_TYPE_INT32},
};static int timeout = 30;
static bool simple_output = false;static void scanreq_prog_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{char *str;if (!msg)return;/* 在这里处理返回的消息。本例子只是将返回的消息打印出来。*/str = blobmsg_format_json_indent(msg, true, simple_output ? -1 : 0);printf("%s\n", str);free(str);
}static int client_ubus_call()
{unsigned int id;int ret;blob_buf_init(&b, 0);/* 需要传递的参数 */blobmsg_add_u32(&b, scan_policy[SCAN_CHNID].name, 0);/*向ubusd查询是否存在"scan_prog"这个对象,如果存在,返回其id*/ret = ubus_lookup_id(ctx, "scan_prog", &id);if (ret != UBUS_STATUS_OK) {printf("lookup scan_prog failed\n");return ret;}else {printf("lookup scan_prog successs\n");}/* 调用"scan_prog"对象的"scan"方法 */return ubus_invoke(ctx, id, "scan", b.head, scanreq_prog_cb, NULL, timeout * 1000);
}/*
ubus_invoke()的声明如下:
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);其中cb参数是消息回调函数,其函数类型定义为:
typedef void (*ubus_data_handler_t)(struct ubus_request *req,int type, struct blob_attr *msg);
参数type是请求消息的类型,如UBUS_MSG_INVOKE,UBUS_MSG_DATA等。
参数msg存放从服务端得到的回复消息,struct blob_attr类型,同样用blobmsg_parse()来解析。
参数req保存了请求方的消息属性,其中req->priv即ubus_invoke()中的priv参数,
用这个参数可以零活的传递一些额外的数据。
*/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);return 0;
}static void client_ubus_done(void)
{if (ctx)ubus_free(ctx);
}int main(int argc, char * argv[])
{/* ubusd创建的unix socket,默认值为"/var/run/ubus.sock" */char * path = NULL;/* 连接ubusd */if (UBUS_STATUS_OK != client_ubus_init(path)){printf("ubus connect failed!\n");return -1;}/* 调用某个ubus方法并处理返回结果 */client_ubus_call();client_ubus_done();return 0;
}
先执行server程序,再执行client程序,可以看到client发出请求后,server返回了相应的节目号,在client打印出了接收到的消息。
也可以通过shell命令来请求server的服务,例如:
ubus call scan_prog scan '{"chnID": 0}'
这条命令和执行client程序的作用相同。
2. subscribe/notify的方式实现订阅
ubus支持以订阅的方式进行进程间通信,通信方式如下图:
被订阅者(server)又称为notifier,订阅者(client)又称为subscriber。这样一来,可以同时有多个client订阅server的某个服务,当server通过该服务广播消息时,所有client都会被通知,并执行各自的处理。
主要过程为:
server进程:
- 连接ubusd。
- 注册一个object,用于client订阅。
- server可以向订阅者广播消息。
- 等待消息。
client进程:
- 连接ubusd。
- 向server订阅object,并定义收到server的消息时的处理函数。
- 等待消息。
代码示例:下面这个例子中,server注册了一个名为“test”的对象,并定期广播消息。而client去订阅这个对象,并对server发出的消息做处理。
notify_server.c:
#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;
}
notify_client.c:客户端订阅“test”对象,在收到3次消息后,随即取消对“test”对象的订阅。
#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;
}
先运行server&,注册可订阅的对象“test”,并随即每2秒向外广播通知消息。这时还没有client订阅这个对象。
运行多个client程序,由于每个client都订阅了“test”对象,则所有client都会收到server发出的消息。当client取消订阅后,则不再收到server端的消息。
3.event的方式实现事件通知
event机制从一对一的进程之间通信来讲,和invoke机制类似。不过event机制中,发送方不需要知道谁要接收这个消息,实际上就是一个广播消息。这类似于U盘的热插拔:当插上或拔出U盘时,内核会广播一个NETLINK事件,之后内核继续做自己的事情,而不关心谁会去监听和处理这个事件。
下面的例子中,client端同时监听了“add_device”和“rm_device”两个事件,而server端会触发“add_device”事件并携带device的一些信息发送出去。
event_client.c:
#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_CLOEXECfcntl(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;
}
event_server.c:
#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;
}
先运行client &注册事件。我们同时启动两个client程序。
再执行server主动触发"add_device"事件,则凡是注册了这个事件的client都会收到该事件并执行各自的处理。
root@NVR:~# ./server
connected as fdecbdc1
{ "add_device": { "major": 3, "minor": 56, "name": "mmc01" } }
{ "add_device": { "major": 3, "minor": 56, "name": "mmc01" } }
也可以通过命令行的ubus send命令触发事件:
root@NVR:~# ubus send "rm_device" '{ "major": 3, "minor": 56, "name": "mmc01" }'
{ "rm_device": { "major": 3, "minor": 56, "name": "mmc01" } }
{ "rm_device": { "major": 3, "minor": 56, "name": "mmc01" } }
在使用ubus时,可根据实际的场景来选择用哪种方式进行进程间通信。如之前所说,ubus是为发送消息而设计的,不合适传输大量数据。
————————————————
版权声明:本文为CSDN博主「落尘纷扰」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jasonchen_gbd/article/details/46055885
[openwrt] ubus实现进程间通信举例相关推荐
- [openwrt] 使用ubus实现
[openwrt] 使用ubus实现 ubus为openwrt平台开发中的进程间通信提供了一个通用的框架.它让进程间通信的实现变得非常简单,并且ubus具有很强的可移植性,可以很方便的移植到其他lin ...
- openwrt之使能WPA3加密方式
openwrt 19.07主要版本带来了对WPA3的初始支持. 但是,默认情况下未启用WPA3 透过ubus查看支持的模式 root@OpenWrt:/# ubus call luci getFeat ...
- openwrt web升级功能介绍
本文简述了openwrt sdk的升级功能流程,从页面传入升级文件到升级文件检测,再到调用升级脚本进行升级,升级完成后,进行系统重启.最后简述了如何添加升级文件标识,标识包括自己定义的字段,以及升级软 ...
- openwrt 网页sysupgrade刷固件流程
一次偶然的机会,阅读了openwrt网页升级的实现细节,以实际操作流程,结合网络资料,整理了这篇流程. 本文按照网页升级固件时涉及到的各个模块的先后顺序进行介绍, openwrt 固件的升级功能流程, ...
- 【路由器】OpenWrt 配置使用
文章目录 Web 界面 汉化 root 密码 ssh 升级 LuCI 美化 锐捷认证 MentoHUST MiniEAP 防火墙 开放端口 端口转发 IPv6 USB 安装 USB 驱动 自动挂载 E ...
- 网络编程+Python
一.网络编程(模块:socket,from socket import *): 1. 网络层的IP地址可以唯一标识网络中的主机,传输层的"协议+端口"则可以唯一标识主机中应用程序( ...
- 微信公众平台开发(110) 微信连Wi-Fi
微信连Wi-Fi是为商家的线下场所提供一套完整和便捷的微信连Wi-Fi的方案.商家接入微信连Wi-Fi后,顾客无需输入繁琐的Wi-Fi密码,通过微信扫二维码等方式即可快速上网.微信连Wi-Fi还帮助商 ...
- LinkIt Smart 7688 问题汇总
一点一滴分析LinkIt Smart 7688 问题汇总 2016年03月19日 12:39:06 阅读数:24737 定义 $ - 指定Ubuntu系统下命令 # - Openwrt下命令 1. 系 ...
- 一点一滴分析LinkIt Smart 7688 问题汇总
目录(?)[+] 定义 $ - 指定Ubuntu系统下命令 # - Openwrt下命令 1. 系统编译 1.1 .config文件 openwrt中,make menuconfig生成.config ...
- 2.凤凰架构:构建可靠的大型分布式系统 --- 访问远程服务
第2章 访问远程服务远程服务将计算机的工作范围从单机扩展至网络,从本地延伸至远程,是构建分布式系统的首要基础.2.1 远程服务调用2.1.1 进程间通信举例,一个正常的本地调用需要完成以下几个工作:1 ...
最新文章
- 中国科学家Cell重要评述文章: 宏基因组学成为病毒分类新方法
- 在Android中使用OpenGL ES开发第(五)节:GLSL基础语法
- sqlservcer行列互转
- 练习div出现的小问题
- 【Spring】1、Spring 中的监听器 Listener
- FISCO BCOS 确定性多合约并行(DMC)
- Docker 取代 VM !是什么让 Docker 比 VM 或裸机更安全?
- Linux故障之内核反向路由检测
- 数据库SQL Server 如何将数据库表名等前缀转换成dbo
- vue单页应用首屏加载速度慢如何解决
- java 四舍五入保留小数点后两位
- 为何谷歌围棋AI AlphaGo可能会把李世石击溃
- case when then else end用法
- 2022年全球市场汽车租赁行业发展前景分析及市场需求调研报告
- (转)利用python、tensorflow、opencv实现人脸识别(包会)!
- 会声会影X5(专业视频编辑软件) v15.0 官方简体中文版
- Django的form组件提交时错误不显示或无法提交
- 一个程序员对另一个程序员的忠告
- php 下载doc文档
- Anroid手机apk安装