由于之前做项目时,一个项目需要由多个app组成,各个app还需要进行通信,当时所采用的是利用socket进行通信。但是采用socket进行通信时,两个客户端无法直接进行通信,导致还需要额外再建立一个server端进行转发,如此一来,浪费了大量的cpu内存资源以及精力来处理数据转发。后来了解到Dbus这个数据总线后,果断采用dbus来处理。
本来是不想写这篇文章的,因为网上关于dbus的资料很多,但是网上的通信案例大多是单向通信的,最多的也就是接收端收到数据后给个应答,并没有同时双向通信的例子(至少笔者是没找到的)。这里所说的双向通信,比如appA和appB,A可以主动向B发送数据,同时,B也可以主动向A发送数据。两个数据流同时进行,互不干扰。
从网上的资料来看,dbus是一个数据总线,其内部实现依旧采用socket进行通信。详细的原理这里不做介绍,自行了解即可。接下来,将主要介绍dbus的编译以及对原有的dbus进行c++封装成dbus类,并进行双向通信测试的实现。
注意:以下内容均在pc上,ubuntu进行测试,arm端在am57xx测试。两者不同的地方会有说明。
Dbus的编译
dbus的编译,其实与其他第三方库的编译一样,都是遵循configure,make, make install三个步骤。dbus版本:dbus-1.10.20.tar.gz。
解压源码,在dbus源码路径下进行configure配置:
./configure --prefix=/home/xurc/am57xx/dbus_pc/install (pc端)
./configure --prefix=/home/xurc/am57xx/dbus_pc/install CC=arm-linux-gnueabihf- --host=arm-linux-gnueabihf (arm端)
然后进行make;make install 即可。
dbus的编译其实网上很多,这里仅简单说明。
注意:在ubuntu环境下,dbus的安装其实可以直接apt-get安装,这里之所以要进行额外编译,是因为如果没有编译源码,则编译两个测试例子时,会提示缺少dbus.h等头文件,笔者由于找不到更好的解决办法,索性把源码编译一下,然后利用-I -L指定dbus相关的库以及头文件。
dbus编译完成后,如下图:
dbus库的c++封装
之所以对原有的dbus进行c++封装,是因为项目采用c++实现,出于方便的考虑,决定自行封装一层接口。对外接口实现了两个,分别是发送数据DbusSend以及接收数据DbusRecv。
Dbus类,拥有一个结构体成员:
struct recvst
{
string bus_name;
string object_path_name;
string interface_name;
string method_name;
string request_bus_name;
DBusError dbus_error; // 原来为DBusError &err = *perr;
DBusConnection *connection;
DBusMessage *message;
pthread_mutex_t dbus_recv_mutex;
uint8_t recvdata[RECV_DATA_SIZE];
int32_t recvpos;
int32_t recvlen;
};
还有一个静态成员函数static DBusHandlerResult ws_dbus_message_handler,该函数主要用于在后台接收数据。目前该静态成员函数允许接收字符串和uint8_t数组两种格式的数据,其他dbus允许的数据格式这里没有实现,因为笔者都将其他类型(int8,int32)的数据转成uint8_t 数组。如有需要,读者可以参考网上的其他资料,添加即可。
构造函数Dbus(string bus_name, string path_name, string interface_name, string method_name, string request_bus_name, uint8_t dbus_type)
构造函数主要是获取总线,生成Dbus。注意,以下是实现双向通信的关键点,也是其他资料没有提到的。
如果采用dbus_bus_get去获取总线,将无法进行双向通信,或者说,如果采用dbus_bus_get获取总线,则appA发送数据给appB,appB可以被动的进行应答,但是无法主动向appA发送数据。而在实际应用中,appB大多数情况下也需要主动向appA发送一些数据。因此,必须采用dbus_bus_get_private获取总线,利用这个函数获取的将是一个私有总线,可以独立的进行数据传输。另外,在构造函数的参数中,还传入了一个总线类型dbus_type,这个dbus_type用于指明总线是用于发送还是接收,即下面的两个宏:
#define DBUS_RECV 1 //接收
#define DBUS_SEND 2 //发送
如果是DBUS_RECV,则还会在后台创建一个线程,用于接收数据:
if (dbus_type == DBUS_RECV)
{
st.recvpos = 0;
st.recvlen = 0;
memset(&st.recvdata, 0x00, RECV_DATA_SIZE);
pthread_mutex_init(&st.dbus_recv_mutex, NULL);
pthread_create(&precv_id, NULL, precv_func, &st);
}
后台接收线程precv_func
当总线是用于接收时,才会创建后台接收线程precv_func,该线程是向总线注册一个回调函数,当总线有数据需要接收时,调用回调函数将总线上的数据存入缓冲区recvdata,方便上层读取。
接收函数DbusRecv(void *buff, int32_t buff_len)
接收函数的功能其实很简单,就是将缓冲区的数据提取出来。
发送函数DbusSend(const void *data, int32_t data_len, int32_t data_type)
发送函数需要指明所要发送的数据类型是字符串还是uint8_t数组,因为发送这两种不同的数据时,略有差异:发送字符串时,需要先创建一个DBusMessage,然后将DBusMessage绑定到DBusMessageIter,最后dbus_message_iter_append_basic即可;但发送uint8_t数组时,还需要一个额外的数组迭代器才能实现。具体差异如下:
以上就是Dbus类的主要功能,目前Dbus类发送和接收只支持字符串和uint8_t数组,因为实际应用中这两种可以使用最多,其他类型的数据可以自行添加实现,或者转成字符串,或者转成uint8_t数组。
测试案例appA:
appA创建两个线程,hardware_send_dataprocess以及hardware_recv_dataprocess。hardware_send_dataprocess占有总线hardware.dataprocess,该线程向总线发送数据;
hardware_recv_dataprocess占有总线dataprocess.hardware,该线程从总线上读取数据。
测试案例appB:
appB同样创建两个线程:dataprocess_send_hardware以及dataprocess_recv_hardware。dataprocess_send_hardware占有总线dataprocess.hardware,该线程向总线发送数据。dataprocess_recv_hardware占有总线hardware.dataprocess,该线程从总线读取数据。
需要特别注意的是: appA的发送即是appB的接收,appA的接收即是appB的发送。发送和接收的总线名称务必注意。
测试案例的编译
编译测试案例时,需要注意头文件和库的链接地址。如果是arm端,注意修改编译器。
测试效果
整个dbus的使用过程,其实没什么太难的地方,主要还是在于双向通信的实现。这里笔者仅仅是对原有的资料进行了一层c++封装,便于使用。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <ctype.h>
#include <dbus/dbus.h>
#include <iostream>
#include <string>
using namespace std;
#define DBUS_RECV 1 //接收
#define DBUS_SEND 2 //发送
#define RECV_DATA_SIZE (512 * 20)
//DBusError dbus_error;
struct recvst
{
string bus_name;
string object_path_name;
string interface_name;
string method_name;
string request_bus_name;
DBusError dbus_error; //xurc 原来为DBusError &err = *perr;
DBusConnection *connection;
DBusMessage *message;
pthread_mutex_t dbus_recv_mutex;
uint8_t recvdata[RECV_DATA_SIZE];
int32_t recvpos;
int32_t recvlen;
};
class Dbus
{
public:
pthread_t precv_id;
struct recvst st;
Dbus(string bus_name, string path_namae, string interface_name, string method_name, string request_bus_name, uint8_t dbus_type);
int32_t DbusSend(const void *data, int32_t data_len, int32_t data_type);
int32_t DbusRecv(void *buff, int32_t buff_len);
//private:
static DBusHandlerResult ws_dbus_message_handler(DBusConnection *connection, DBusMessage *message, void *user_data)
{
DBusMessageIter recvIter;
DBusMessageIter arrayIter;
int ret = 0;
dbus_int32_t len = 0;
struct recvst *st = (struct recvst *)user_data;
//DBusMessage *message = st->message;
DBusMessage *reply = NULL;
const char * path =dbus_message_get_path(message);
if(NULL==path)
{
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
if (strcmp(path, st->object_path_name.c_str()) == 0)
{
if (dbus_message_is_method_call(message, st->interface_name.c_str(), st->method_name.c_str()))
{
dbus_message_iter_init(message, &recvIter);
do
{
ret = dbus_message_iter_get_arg_type(&recvIter);
if (DBUS_TYPE_STRING == ret)
{
char *pdata = NULL;
dbus_message_iter_get_basic(&recvIter, &pdata);
printf("%s\n", pdata);
pthread_mutex_lock(&st->dbus_recv_mutex);
memcpy((char *)st->recvdata + st->recvpos, pdata, strlen(pdata));
st->recvlen = strlen(pdata);
st->recvpos += st->recvlen;
pthread_mutex_unlock(&st->dbus_recv_mutex);
}
else if (DBUS_TYPE_ARRAY == ret)
{
uint8_t *pdata = NULL;
dbus_message_iter_recurse(&recvIter, &arrayIter);
dbus_message_iter_get_fixed_array(&arrayIter, &pdata, &len);
if (len > 0)
{
pthread_mutex_lock(&st->dbus_recv_mutex);
memcpy(st->recvdata + st->recvpos, pdata, len);
st->recvlen = len;
st->recvpos += len;
pthread_mutex_unlock(&st->dbus_recv_mutex);
}
}
} while (dbus_message_iter_next(&recvIter));
char answer [40];
sprintf (answer, "I have get your message!");
if ((reply = dbus_message_new_method_return (message)) == NULL)
{
fprintf (stderr, "Error in dbus_message_new_method_return\n");
exit (1);
}
DBusMessageIter iter;
dbus_message_iter_init_append (reply, &iter);
char *ptr = answer;
if (!dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &ptr))
{
fprintf (stderr, "Error in dbus_message_iter_append_basic\n");
exit (1);
}
}
}
if(reply)
{
// send the reply && flush the connection
if (!dbus_connection_send(connection, reply, NULL))
{
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
dbus_connection_flush(connection);
// free the reply
dbus_message_unref(reply);
return DBUS_HANDLER_RESULT_HANDLED ;
}
else
{
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
}
};
static void *precv_func(void *arg)
{
struct recvst *st = (struct recvst *)arg;
dbus_error_init(&st->dbus_error);
if (!dbus_connection_add_filter(st->connection, Dbus::ws_dbus_message_handler, st, NULL))
{
fprintf(stderr, "dbus_connection_add_filter\n");
exit(1);
}
while (dbus_connection_read_write_dispatch (st->connection, -1))
{
if (st->recvlen > 0)
{
//printf("555555555st->len = %d\n", st->recvlen);
//printf("555555555st->recvdata = %s\n", st->recvdata);
//
//memset(&st->recvdata, 0x00, sizeof(st->recvdata));
//st->recvlen = 0;
printf("get new %d bytes\n", st->recvlen);
st->recvlen = 0;
}
//sleep(1);
}
}
Dbus::Dbus(string bus_name, string path_name, string interface_name, string method_name, string request_bus_name, uint8_t dbus_type)
{
int32_t ret = 0;
st.bus_name = bus_name;
st.object_path_name = path_name;
st.interface_name = interface_name;
st.method_name = method_name;
st.request_bus_name = request_bus_name;
//if (st.connection == NULL)
{
dbus_error_init(&st.dbus_error);
//st.connection = dbus_bus_get (DBUS_BUS_SESSION, &st.dbus_error); //获取公共的connection
st.connection = dbus_bus_get_private(DBUS_BUS_SESSION, &st.dbus_error); //用dbus_bus_get_private获取独立的connection
}
if (dbus_error_is_set (&st.dbus_error))
printf("dbus_bus_get\n");
if (!st.connection)
exit (1);
ret = dbus_bus_request_name (st.connection, st.request_bus_name.c_str(), DBUS_NAME_FLAG_DO_NOT_QUEUE, &st.dbus_error);
if (dbus_error_is_set (&st.dbus_error))
printf("dbus_bus_get\n");
if (ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
{
fprintf (stderr, "Dbus: not primary owner, ret = %d\n", ret);
exit (1);
}
//dbus_error_init(&dbus_error);
//if (!dbus_connection_add_filter (connection, ws_dbus_message_handler, NULL, NULL)) {
// fprintf(stderr, "dbus_connection_add_filter\n");
// exit(1);
//}
//while (dbus_connection_read_write_dispatch (connection, -1));
if (dbus_type == DBUS_RECV)
{
st.recvpos = 0;
st.recvlen = 0;
memset(&st.recvdata, 0x00, RECV_DATA_SIZE);
pthread_mutex_init(&st.dbus_recv_mutex, NULL);
pthread_create(&precv_id, NULL, precv_func, &st);
}
}
int32_t Dbus::DbusRecv(void *buff, int32_t buff_len)
{
int32_t len = 0;
if ((st.recvpos > 0) && (buff_len >= st.recvpos))
{
pthread_mutex_lock(&st.dbus_recv_mutex);
memcpy((uint8_t *)buff, st.recvdata, st.recvpos);
len = st.recvpos;
st.recvpos = 0;
pthread_mutex_unlock(&st.dbus_recv_mutex);
}
return len;
}
int32_t Dbus::DbusSend(const void *data, int32_t data_len, int32_t data_type)
{
DBusMessage *msg = NULL;
DBusPendingCall *pending;
DBusMessageIter sendIter;
DBusMessageIter arrayIter;
char buff[2];
char *ptype = buff;
dbus_bool_t ret = 0;
//针对目的地地址,请参考图,创建一个method call消息。Constructs a new message to invoke a method on a remote object.
msg = dbus_message_new_method_call(st.bus_name.c_str(), st.object_path_name.c_str(), st.interface_name.c_str(), st.method_name.c_str());
if(msg == NULL){
fprintf(stderr, "MessageNULL");
return 0;
}
//为消息添加参数。Appendarguments
dbus_message_iter_init_append(msg, &sendIter);
if (data_type == DBUS_TYPE_STRING)
{
if(!dbus_message_iter_append_basic(&sendIter, DBUS_TYPE_STRING, &data)){
fprintf(stderr, "Out of Memory!");
exit(1);
}
}
else if (data_type == DBUS_TYPE_BYTE)
{
buff[0] = DBUS_TYPE_BYTE;
buff[1] = '\0';
dbus_message_iter_open_container(&sendIter, DBUS_TYPE_ARRAY, ptype, &arrayIter);
ret = dbus_message_iter_append_fixed_array(&arrayIter, DBUS_TYPE_BYTE, &data, data_len);
dbus_message_iter_close_container(&sendIter, &arrayIter);
}
//dbus_connection_send(st.connection, msg, NULL);
//dbus_connection_flush(st.connection);
//dbus_message_unref(msg);
#if 1
//发送消息并获得reply的handle。Queues amessage to send, as withdbus_connection_send() , but also returns aDBusPendingCall used to receive a reply to the message.
if(!dbus_connection_send_with_reply(st.connection, msg, &pending, -1)){
fprintf(stderr, "Out of Memory!");
exit(1);
}
if(pending == NULL){
fprintf(stderr, "Pending CallNULL: connection is disconnected ");
dbus_message_unref(msg);
return 0;
}
dbus_connection_flush(st.connection);
dbus_message_unref(msg);
dbus_pending_call_block(pending);
//接收返回数据
char *s;
DBusMessage *reply;
reply= dbus_pending_call_steal_reply(pending);
if (reply== NULL)
{
fprintf (stderr,"call steal reply failed\n");
dbus_message_unref(reply);
exit(1);;
}
dbus_pending_call_unref(pending);
//从服务器端返回一个简单的字符串
if (dbus_message_get_args (reply, &st.dbus_error, DBUS_TYPE_STRING, &s, DBUS_TYPE_INVALID)) {
printf ("Receive from server: %s\n", s);
}else{
fprintf (stderr, "Did not get arguments in reply\n");
exit (1);
}
dbus_message_unref (reply);
/****************逐个解析获取信息***********
dbus_error_init(&err);
dbus_message_iter_init(msg, &iter);//成功返回1
if(DBUS_TYPE_INT32 == dbus_message_iter_get_arg_type(&iter)){
dbus_message_iter_get_basic(&iter, &status);//获取DBUS_TYPE_INT32类型数据存放到status
}
dbus_message_iter_next(&iter);//获取下一数据
if(DBUS_TYPE_INT32 == dbus_message_iter_get_arg_type(&iter)){
dbus_message_iter_get_basic(&iter, &count);
}
//解析获取的数组数据
DBusMessageIter sub_iter;
if(dbus_message_iter_next(&iter){
if(DBUS_TYPE_ARRAY == dbus_message_iter_get_arg_type(&iter)){
dbus_message_iter_recurse(&iter, &sub_iter);
dbus_message_iter_get_fixed_array(&sub_iter, &mac, &mac_len);
}
}
*******************************************/
#endif
if (dbus_bus_release_name (st.connection, st.request_bus_name.c_str(), &st.dbus_error) == -1) {
fprintf (stderr, "Error in dbus_bus_release_name\n");
exit (1);
}
return 0;
}
#if 0
int32_t main(int32_t argc, char *argv[])
{
struct recvst rcst;
const string recv_bus_name = "in.softprayog.add_server_recv";
const string recv_object_path_name = "/in/softprayog/adder";
const string recv_interface_name = "in.softprayog.dbus_example";
const string recv_methoe_name = "test_dbus_damon";
const string recv_request_name = "in.softprayog.add_server_recv";
const string send_bus_name = "in.softprayog.add_server_send";
const string send_object_path_name = "/in/softprayog/adder";
const string send_interface_name = "in.softprayog.dbus_example";
const string send_methoe_name = "test_dbus_damon";
const string send_request_name = "in.softprayog.add_server_send";
uint8_t recvbuff[1024];
int32_t recvlen = 0;
Dbus recv(recv_bus_name, recv_object_path_name, recv_interface_name, recv_methoe_name, recv_request_name, DBUS_RECV);
Dbus send(send_bus_name, send_object_path_name, send_interface_name, send_methoe_name, send_request_name, DBUS_SEND);
while (1)
{
send.DbusSend("hello", strlen("hello"), DBUS_TYPE_STRING);
recvlen = recv.DbusRecv(recvbuff, sizeof(recvbuff));
for (int i = 0; i < recvlen; i++)
printf("%x ", recvbuff[i]);
printf("\n");
sleep(3);
}
}
#endif
#if 1
static void *hardware_send_dataprocess(void *arg)
{
string send_BusName = "hardware.dataprocess";
string send_Path = "/hardware_dataprocess/method/Object";
string send_InterFace = "hardware_dataprocess.method.Type";
string send_Method = "Method";
string send_requestName = "hardware.dataprocess.source";
uint8_t senddata[512];
uint8_t i = 0;
Dbus dbus_hardware_send_dataprocess(send_BusName, send_Path, send_InterFace, send_Method, send_requestName, DBUS_SEND);
while (1)
{
memset(senddata, i, sizeof(senddata));
dbus_hardware_send_dataprocess.DbusSend(senddata, sizeof(senddata), DBUS_TYPE_BYTE);
i++;
//sleep(1);
usleep(1000 * 50);
}
}
static void *hardware_recv_dataprocess(void *arg)
{
string recv_BusName = "dataprocess.hardware";
string recv_Path = "/dataprocess_hardware/method/Object";
string recv_InterFace = "dataprocess_hardware.method.Type";
string recv_Method = "Method";
string recv_requestName = recv_BusName;
uint8_t recvdata[8200];
int32_t recvlen = 0;
Dbus dbus_hardware_recv_dataprocess(recv_BusName, recv_Path, recv_InterFace, recv_Method, recv_requestName, DBUS_RECV);
while (1)
{
memset(recvdata, 0x00, sizeof(recvdata));
recvlen = dbus_hardware_recv_dataprocess.DbusRecv(recvdata, sizeof(recvdata));
if (recvlen > 0)
{
printf("tellen = %d\n", recvlen);
for (int i = 0; i < 8; i++)
{
printf("%x ", recvdata[i]);
}
}
sleep(1);
}
}
int main(int argc, char *argv[])
{
pthread_t hardware_send_dataprocess_id;
pthread_t hardware_recv_dataprocess_id;
pthread_create(&hardware_send_dataprocess_id, NULL, hardware_send_dataprocess, NULL);
pthread_create(&hardware_recv_dataprocess_id, NULL, hardware_recv_dataprocess, NULL);
while (1)
{
sleep(1);
}
}
#endif
完整的源码见链接:https://download.csdn.net/download/qq_36731830/60418661