Bluez SPP实现代码分析(转)

源:http://blog.csdn.net/walkingman321/article/details/7218705

本文分析蓝牙协议栈中蓝牙转串口(SPP)部分的实现。

1.  基本概念

Bluez提供了蓝牙转串口的功能,应用程序可以通过dbus接口控制bluez的串口功能。

1.1 启动SPP服务等待远端设备连接的过程:

org.bluez.SerialProxyManager->CreateProxy         // 得到一个serial proxy

org.bluez.SerialProxy->SetSerialParameters          // 设置串口参数

org.bluez.SerialProxy->Enable                // 启动串口

之后,bluez就会等待远端设备连接本地的串口服务

1.2 主动连接远端设备的SPP服务的过程:

当远端设备与本地设备之间建立连接,并且本地设备发现远端设备包含SPP功能,则Bluez将会注册一个“org.bluez.Serial”接口的实例。应用程序可通过调用其中的”Connect”接口,建立与远端设备之间的串口连接。

2.  代码分析

与SPP有关的代码在bluez的serial目录中,其初始化函数为serial_init。

static int serial_init(void)

      。。。

      serial_manager_init(connection);

 

int serial_manager_init(DBusConnection *conn)

      。。。

      btd_register_adapter_driver(&serial_proxy_driver);

      btd_register_device_driver(&serial_port_driver);

serial_manager_init函数中包含两个操作,一个是注册serial_proxy_driver,另一个是注册serial_port_driver。

2.1 serial_proxy_driver

static struct btd_adapter_driver serial_proxy_driver = {

      .name   = "serial-proxy",

      .probe  = proxy_probe,

      .remove = proxy_remove,

};

Bluez启动后,对于每一个蓝牙适配器设备,都会调用其中的proxy_probe函数。

static int proxy_probe(struct btd_adapter *adapter)

      。。。

      proxy_register(connection, adapter);

int proxy_register(DBusConnection *conn, struct btd_adapter *btd_adapter)

      。。。

struct serial_adapter *adapter = g_new0(struct serial_adapter, 1);

adapter->conn = dbus_connection_ref(conn);

adapter->btd_adapter = btd_adapter_ref(btd_adapter);

path = adapter_get_path(btd_adapter);

// 注册SerialProxyManager接口

g_dbus_register_interface(conn, path, SERIAL_MANAGER_INTERFACE,

       manager_methods, manager_signals, NULL,

       adapter, manager_path_unregister);

adapters = g_slist_append(adapters, adapter);

// 这里是根据配置文件注册默认的SerialProxyManager接口,可以跳过

serial_proxy_init(adapter);

SerialProxyManager接口的方法如下:

static GDBusMethodTable manager_methods[] = {

      { "CreateProxy",      "ss",     "s", create_proxy },

      { "ListProxies",        "",  "as",     list_proxies },

      { "RemoveProxy",         "s", "",  remove_proxy },

      { },

};

当应用程序需要建立一个serial proxy实例时,可以调用其中的CreateProxy方法,此方法会映射到create_proxy函数。

static DBusMessage *create_proxy(DBusConnection *conn,

                      DBusMessage *msg, void *data)

      // 得到应用程序传下来的参数

dbus_message_get_args(msg, NULL,

                      DBUS_TYPE_STRING, &pattern,

                      DBUS_TYPE_STRING, &address,

                      DBUS_TYPE_INVALID);

      // 得到uuid

uuid_str = bt_name2string(pattern);

register_proxy(adapter, uuid_str, address, &proxy);

。。。

 

static int register_proxy(struct serial_adapter *adapter,

                      const char *uuid_str, const char *address,

                      struct serial_proxy **proxy)

      // 根据应用程序传下来的地址名称,判断地址类型。类型可以是unix socket、串口或者tcp socket。此socket用于bluez与应用程序之间的串口数据通信。如果应用程序发送数据,就写入此socket;如果应用程序接收数据,就由Bluez写入此socket。address到类型的转换细节可以参考函数的具体实现。

      type = addr2type(address);

      。。。

      // 以下是根据地址类型进入相应的初始化函数

      switch (type) {

      case UNIX_SOCKET_PROXY:

           err = proxy_socket_register(adapter, uuid_str, address, proxy);

           break;

      case TTY_PROXY:

           err = proxy_tty_register(adapter, uuid_str, address, NULL,

                            proxy);

           break;

      case TCP_SOCKET_PROXY:

           err = proxy_tcp_register(adapter, uuid_str, address, proxy);

           break;

      default:

           err = -EINVAL;

      }

      。。。

由于三种地质类型的初始化大同小异,所以这里只研究unix socket类型的情况:

当上层应用使用unix socket时,由应用程序建立一个此类型的socket,然后把socket地址传给Bluez,Bluez同样会根据此地址创建socket。之后,Bluez和应用程序之间就可以通过此socket像操作管道一样互相传递数据。在Bluez的实现中,此socket被用于传递蓝牙的串口数据。

static int proxy_socket_register(struct serial_adapter *adapter,

                      const char *uuid128, const char *address,

                      struct serial_proxy **proxy)

      struct serial_proxy *prx;

prx->address = g_strdup(address);

      prx->uuid128 = g_strdup(uuid128);

      prx->type = UNIX_SOCKET_PROXY;

      adapter_get_address(adapter->btd_adapter, &prx->src);

      prx->adapter = adapter;

      register_proxy_object(prx);

static int register_proxy_object(struct serial_proxy *prx)

      。。。

      g_dbus_register_interface(adapter->conn, path,

                            SERIAL_PROXY_INTERFACE,

                            proxy_methods, NULL, NULL,

                            prx, proxy_path_unregister);

      。。。

以上就是create_proxy的全部操作。到这里仅仅建立了一个串口proxy,但此proxy还未使能。使能操作由应用程序调用上面新注册的接口SERIAL_PROXY_INTERFACE中的Enable方法完成。

static GDBusMethodTable proxy_methods[] = {

      { "Enable",             "",  "",  proxy_enable },

      { "Disable",             "",  "",  proxy_disable },

      { "GetInfo",            "",  "a{sv}",proxy_get_info },

      { "SetSerialParameters",  "syys",  "",  proxy_set_serial_params },

      { },

};

static DBusMessage *proxy_enable(DBusConnection *conn,

                      DBusMessage *msg, void *data)

      。。。

enable_proxy(prx);

。。。

static int enable_proxy(struct serial_proxy *prx)

      。。。

      // 监听RFCOMM通道

      prx->io = bt_io_listen(BT_IO_RFCOMM, NULL, confirm_event_cb, prx,

                      NULL, &gerr,

                      BT_IO_OPT_SOURCE_BDADDR, &prx->src,

                      BT_IO_OPT_INVALID);

      // 当调用listen时,Bluez(本地蓝牙适配器)会分配一个channel,然后在这个channel上等待对方连接。这个channel很重要,因为蓝牙协议中根据channel号区分RFCOMM上的各种应用

      bt_io_get(prx->io, BT_IO_RFCOMM, &gerr,

                  BT_IO_OPT_CHANNEL, &prx->channel,

                 BT_IO_OPT_INVALID);

。。。

sdp_record_t *record;

// 分配一个SDP record,这里把channel号设置到了SDP record里面

record = proxy_record_new(prx->uuid128, prx->channel);

// 将record加入到SDP service中,到这里远端设备就能够看到本地设备的蓝牙服务了。该服务在RFCOMM的prx->channel中

add_record_to_server(&prx->src, record);

prx->record_id = record->handle;

。。。

到这里,本地设备已经准备好接受远端设备的串口连接,对方连接时,上面调用的bt_io_listen中的confirm_event_cb回调函数将被调用。

在confirm_event_cb中,为执行鉴权操作,鉴权成功后调用bt_io_accept建立连接。Accept成功后,回调函数connect_event_cb将被调用。

static void connect_event_cb(GIOChannel *chan, GError *conn_err, gpointer data)

      。。。

      switch (prx->type)

      case UNIX_SOCKET_PROXY:

           sk = unix_socket_connect(prx->address);

           break;

      case TTY_PROXY:

           。。。

      case TCP_SOCKET_PROXY:

           。。。

      g_io_add_watch(prx->rfcomm,

                 G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,

                 forward_data, prx);

      g_io_add_watch(prx->local,

                 G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,

                 forward_data, prx);

      。。。

unix_socket_connect函数中将根据address建立一个unix socket,并执行了connect。注意到上层应用同样建立了这么一个socket,如果此时上层应用调用select等待在此socket中,那么这里调用connect后,上层应用将得到通知,从而得知与远端设备的连接已经建立。

接下来再看看g_io_add_watch的调用。这里调用两次,一次是针对远端设备的socket;一次是针对与上层应用之间的socket,即刚建立的unix socket。这两个socket之间有一个配对关系,一个用于与远端设备的串口数据传输,另一个用于与上层应用的数据传输。这两个通道关联后就建立了远端设备与上层应用之间的连接,这个关联就由两次g_io_add_watch调用的回调函数forward_data实现。

forward_data被调用时,说明相应的通道上有数据,其伪代码如下:

1.    得到通道上的数据

2.    将数据发送到另一个通道。

通过此种方式,两个通道之间实现了关联。

2.2 serial_port_driver

static struct btd_device_driver serial_port_driver = {

      .name   = "serial-port",

      .uuids   = BTD_UUIDS(RFCOMM_UUID_STR),

      .probe  = port_probe,

      .remove      = port_remove,

};

当包含UUID RFCOMM_UUID_STR的远端设备连接到本地时,Bluez就会调用serial_port_driver的port_probe函数。

static int port_probe(struct btd_device *device, GSList *uuids)

      while (uuids) {

           serial_probe(device, uuids->data);

           uuids = uuids->next;

      }

static int serial_probe(struct btd_device *device, const char *uuid)

      const sdp_record_t * rec = btd_device_get_record(device, uuid);

      // 从SDP record中得到channel号,此channel用于串口通信

      int ch = sdp_get_proto_port(protos, RFCOMM_UUID);

      。。。

port_register(connection, path, &src, &dst, uuid, ch);

 

int port_register(DBusConnection *conn, const char *path, bdaddr_t *src,

                 bdaddr_t *dst, const char *uuid, uint8_t channel)

      。。。

      create_serial_device(conn, path, src, dst);

      。。。

static struct serial_device *create_serial_device(DBusConnection *conn,

                            const char *path, bdaddr_t *src,

                            bdaddr_t *dst)

      // 新建一个serial_device

      Struct serial_device* device = g_new0(struct serial_device, 1);

      。。。

      // 注册一个SERIAL_PORT_INTERFACE接口

      g_dbus_register_interface(conn, path, SERIAL_PORT_INTERFACE,

                      port_methods, NULL, NULL, device, path_unregister);

以上为本地设备发现一个支持SPP的远端设备之后执行的操作,最后就是对这个设备新建了一个SERIAL_PORT_INTERFACE接口的实例。此接口的定义为:

static GDBusMethodTable port_methods[] = {

      { "Connect",    "s", "s", port_connect, G_DBUS_METHOD_FLAG_ASYNC },

      { "Disconnect", "s", "",  port_disconnect },

      { }

};

当上层应用需要主动连接远端设备,而不是被动等待远端设备连接本地设备时,就可以调用这里的connect方法,此方法最终映射到port_connect函数。

Port_connect函数最终是调用到bt_io_connect函数。

bt_io_connect (BT_IO_RFCOMM, rfcomm_connect_cb, port,

                      NULL, NULL,

                      BT_IO_OPT_SOURCE_BDADDR, &device->src,

                      BT_IO_OPT_DEST_BDADDR, &device->dst,

                      BT_IO_OPT_CHANNEL, port->channel,

                      BT_IO_OPT_INVALID);

其中指定的连接成功后的回调函数为rfcomm_connect_cb。

在rfcomm_connect_cb中,调用了ioctl RFCOMMCREATEDEV。

此ioctl会在/dev目录下创建一个设备节点rfcomm%d。此设备节点的名称会被传给上层应用。这样,上层应用就能通过此节点发送、接收数据,其数据与远端设备之间通过蓝牙串口传输。

时间: 2024-11-06 03:55:21

Bluez SPP实现代码分析(转)的相关文章

java代码分析及分析工具

java代码分析及分析工具 一个项目从搭建开始,开发的初期往往思路比较清晰,代码也比较清晰.随着时间的推移,业务越来越复杂.代码也就面临着耦合,冗余,甚至杂乱,到最后谁都不敢碰. 作为一个互联网电子商务网站的业务支撑系统,业务复杂不言而喻.从09年开始一直沿用到现在,中间代码经过了多少人的手,留下了多少的坑,已经记不清楚了,谁也说不清了. 代码的维护成本越来越高.代码已经急需做调整和改善.最近项目组专门设立了一个小组,利用业余时间做代码分析的工作,目标对核心代码进行分析并进行设计重构. 代码分析

Java静态代码分析工具Infer

Java静态代码分析工具Infer 作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chszs 一.Infer介绍 Infer是Facebook最新开源的静态程序分析工具,用于在发布移动应用之前对代码进行分析,找出潜在的问题.目前Facebook使用此工具分析Facebook的App,包括Android.iOS.Facebook Messenger和Instagram等. Facebook称该工具帮助其每个月检查出应用潜在的数百个Bug,例如一些空指针访问.资源

$*和[email protected]之间区别代码分析

#!/bin/bash set 'apple pie' pears peaches for i in $*           /*单引号被去掉,循环单个字符输出*/ do echo $i done [[email protected] Ex_14.02-14.31]# sh 14-14-1 apple pie pears peaches -------------------------------------------------------------- #!/bin/bash set

《linux 内核完全剖析》 keyboard.S 部分代码分析(key_map)

keyboard.S 部分代码分析(key_map) keyboard中间有这么一段,我一开始没看明白,究竟啥意思 key_map: .byte 0,27 .ascii "1234567890-=" .byte 127,9 .ascii "qwertyuiop[]" .byte 13,0 .ascii "asdfghjkl;'" .byte '`,0 .ascii "\\zxcvbnm,./" .byte 0,'*,0,32

20145234黄斐《网络对抗技术》实验四,恶意代码分析

恶意代码 概述 恶意代码是指故意编制或设置的.对网络或系统会产生威胁或潜在威胁的计算机代码.最常见的恶意代码有计算机病毒(简称病毒).特洛伊木马(简称木马).计算机蠕虫(简称蠕虫).后门.逻辑炸弹等. 特征: 恶意的目的,获取靶机权限.用户隐私等 本身是计算机程序,可以执行,并作用于靶机 通过执行发生作用,一般来说不运行是没问题的 恶意代码分析 在大多数情况下,进行恶意代码分析时,我们将只有恶意代码的可执行文件本身,而这些文件并不是我们人类可读的.为了了解这些文件的意义,你需要使用各种工具和技巧

20145326蔡馨熠《网络对抗》——恶意代码分析

20145326蔡馨熠<网络对抗>--恶意代码分析 1.实验后回答问题 (1)如果在工作中怀疑一台主机上有恶意代码,但只是猜想,所以想监控下系统一天天的到底在干些什么.请设计下你想监控的操作有哪些,用什么方法来监控.. 需要监控什么? 系统中各种程序.文件的行为. 还需要注意是否会出现权限更改的行为. 注册表. 是否有可疑进程. 如果有网络连接的情况,需要注意这个过程中的IP地址与端口. 用什么来监控? 最先想到的肯定是使用wireshark抓包了,再进行进一步分析. Sysinternals

代码分析—“CA0052 没有选择要分析的目标”(VS2012)

情况: 1.未采用代码分析时程序正常编译 2.采用代码分析,会提示"没有选择分析目标"或"未加载制定版本的程序集"...的错误 分析: 是由于代码分析依赖程序集的强签名,包括版本 解决方案: 1.修改代码分析工具的配置项: FxCopCmd.exe.config里节点AssemblyReferenceResolveMode的Value值StrongName修改为StrongNameIgnoringVersion或None 2.修改当前分析的项目: .csproj增加

常用 Java 静态代码分析工具的分析与比较

转载自: http://www.oschina.net/question/129540_23043 简介: 本文首先介绍了静态代码分析的基本概念及主要技术,随后分别介绍了现有 4 种主流 Java 静态代码分析工具 (Checkstyle,FindBugs,PMD,Jtest),最后从功能.特性等方面对它们进行分析和比较,希望能够帮助 Java 软件开发人员了解静态代码分析工具,并选择合适的工具应用到软件开发中. 引言 在 Java 软件开发过程中,开发团队往往要花费大量的时间和精力发现并修改代

驱动相关的内核代码分析

arch\arm\include\asm\Io.h #define __raw_readl(a) (__chk_io_ptr(a), *(volatile unsigned int __force   *)(a)) #define __raw_writel(v,a) (__chk_io_ptr(a), *(volatile unsigned int __force   *)(a) = (v)) 注:(volatile unsigned int __force   *)指针强制转换为unsigne