一:Bluetooth基本概念:

Bluetooth是爱立信、诺基亚、东芝、IBMIntel 5家公司在1998年联合推出的一项无线网络技术。其宗旨是提供一种短距离、低成本的无线传输应用技术。在行业协会筹备阶段,需要一个极具有表现力的名字来命名这项高新技术。行业组织人员,在经过一夜关于欧洲历史和未来无限技术发展的讨论后,有些人认为用Blatand国王的名字命名再合适不过了。Blatand国王将现在的挪威,瑞典和丹麦统一起来;就如同这项即将面世的技术,将标准不一的短距离无线传输技术统一起来。

Intel负责半导体芯片和传输软件的开发,爱立信负责无线射频和移动电话软件的开发,IBM和东芝负责笔记本电脑接口规格的开发。

蓝牙是无线数据和语音传输的开放式标准,它将各种通信设备、计算机及其终端设备、各种数字数据系统、甚至家用电器采用无线方式联接起来。它的传输距离为10cm10m,如果增加功率或是加上某些外设便可达到100m的传输距离。它采用2.4GHz ISM频段和调频、跳频技术,使用权向纠错编码、ARQTDD和基带协议。TDMA每时隙为0.625μs,基带符合速率为1Mb/s。蓝牙支持64kb/s实时语音传输和数据传输,语音编码为CVSD,发射功率分别为1mW2.5mW100mW,并使用全球统一的48比特的设备识别码。由于蓝牙采用无线接口来代替有线电缆连接,具有很强的移植性,并且适用于多种场合,加上该技术功耗低、对人体危害小,而且应用简单、容易实现,所以易于推广。

蓝牙技术的系统结构分为三大部分:底层硬件模块、中间协议层和高层应用。底层硬件部分包括无线跳频(RF)、基带(BB)和链路管理(LM)。无线跳频层通过2.4GHz无需授权的ISM频段的微波,实现数据位流的过滤和传输,本层协议主要定义了蓝牙收发器在此频带正常工作所需要满足的条件。基带负责跳频以及蓝牙数据和信息帧的传输。链路管理负责连接、建立和拆除链路并进行安全控制。

关于bluetooth协议栈,接下来再谈。

当前已经实现的Bluetooth协议栈有以下各种:

1. Widcomm: 第一个windows上的协议栈,由Widcomm公司开发,也就是现在的Broadcom.

2. Microsoft Windows stack: Windows XP SP2中包括了这个内建的协议栈,开发者也可以调用其API开发第三方软件。

3. Toshiba stack: 它也是基于Windows的,不支持第三方开发,但它把协议栈授权给一些laptop(sony,asus等,我的本本上就是Toshiba的)。它支持的Profile有: SPP, DUN, FAX, LAP, OPP, FTP, HID, HCRP, PAN, BIP, HSP, HFP ,A2DP, AVRCP, GAVDP

4. BlueSoleil: 著名的IVT公司的产品.该产品可以用于桌面和嵌入式,他也支持第三方开发,DUN, FAX, HFP, HSP, LAP,OBEX, OPP, PAN SPP, AV, BIP, FTP, GAP, HID, SDAP, and SYNC

5. Bluez: Linux官方协议栈,该协议栈的上层用Socket封装,便于开发者使用,通过DBUS与其它应用程序通信。

6. Affix: NOKIA公司的协议栈,在Symbian系统上运行.

7. BlueDragon:东软公司产品,好像20026月就通过了蓝牙的认证,支持的ProfileSDPSerial-DevBAVCTPAVRCP-ControllerAVRCP-TargetHeadset-AGHeadset-HSOPP-ClientOPP-ServerCT-GWCT-TermIntercomFT-ServerFT-ClientGAPSDAPSerial-DevAAVDTPGAVDPA2DP-SourceA2DP-Sink.

8. BlueMagic:美国OpenInterface 公司for portable embedded divce的协议栈,iphone(apple)nav-u(sony)等很多电子产品都用该商业的协议栈,BlueMagic 3.0是第一个通过bluetooth协议栈1.1认证的协议栈,那么我现在就在用它,那么该栈用起来简单,API清晰明了。实现了的profile:HCI,L2CAP,RFCOMM,A/V,Remote,Control,A/V,Streaming,BIP,BPP,DUN,FAX,FTP,GAP,Hands-Free,and,Headset,HCRP,HID,OBEX,OPP,PAN,BNEP,PBAP,SAP,SPP,Synchronization,SyncML,Telephony,XML.

9. BCHS-Bluecore Host Software: 蓝牙芯片CSR的协议栈,同时他也提供了一些上层应用的Profile的库,当然了它也是为嵌入式产品了,支持的Profile有:A2DP,AVRCP,PBAP,BIP,BPP,CTP,DUN,FAX,FM API,FTPGAP,GAVDP,GOEP,HCRP,Headset,HF1.5,HID,ICP,JSR82,LAP Message AccessProfile,OPP,PAN,SAP,SDAP,SPP,SYNC,SYNC ML

10. Windows CE:微软给WindowsCE开发的协议栈,但是windows ce本身也支持其它的协议栈

11. BlueLetIVT公司for embedded product的清量级协议栈。

Linux下开放的蓝牙协议栈主要包括IBM公司的BlueDrekarNokia公司的Affix, Axis公司的OpenBT和官方协议栈BlueZ。我们主要对Bluez进行探讨。

BlueZ基础代码均是由Maxim Krasnyansky完成的。包括:HCIL2CAPRFCOMM和基本socket的实现。他就职于Qualcomm(高通)Marcel Holtmann开发层的协议和应用,包括:BNEP,CMTP等。当然,这些中也有MaximKrasnyansky的参预。有部分代码由Nokia提供的。

Bluez是如何实现Bluetooth协议栈的呢?它分2部分实现:

1.Kernel层实现:

正如上一篇所谈到的,bluetooth协议栈有多层结构,最底层的硬件协议在硬件中就已经实现了。(例如broadcom的芯片中,底层硬件协议已经包含于芯片之中了)。软件级别的协议实现,从HCI这一层起就可以了。 BlueZ对各层协议的实现是依托于Socket的。BlueZ首先创建了一个新的Socket中的协议--PF_BLUETOOTH AF_BLUETOOTH=31). (也就是说,Socket()的第一个参数:domain必须是:PF_BLUETOOTH )。这也意味着,地址类型需要使用Bluetooth所定义的。

其实很简单,就是在net_families(网络协议列表)中添加了PF_BLUETOOTH这一项。如果对Linux Kernel有了解的话,就知道这个注册动作一定在Bluetooth init部分作的。

同样,各个协议层(如:HCIL2CAP,HID等)都将自己的行为规范添加到PF_BLUETOOTH协议中。

2.应用程序层实现:

虽然Kernel层已经将Bluetooth协议栈完全实现了,但如果要使用起来,还是非常不方便的。毕竟应用程序与kernel最方便的交流通道就是ioctl().这非常不直观。于是,BlueZ又提供了一套API,这个API帮助开发者方便的与Kernel层协议打交道。当然,这些API底层的实现其实就是ioctl.

BlueZ的实现,基本就是这样了。下面咱们具体研究如何使用BlueZ所提供的这套API

1. HCI层协议概述:

HCI提供一套统一的方法来访问Bluetooth底层。如图所示:

从图上可以看出,HostController Interface(HCI)就是用来沟通HostModuleHost通常就是PC Module则是以各种物理连接形式(USB,serial,pc-card等)连接到PC上的bluetooth Dongle

Host这一端:application,SDP,L2cap等协议都是软件形式提出的(Bluez中是以kernel层程序)。在Module这一端:LinkManager, BB, 等协议都是硬件中firmware提供的。

HCI则比较特殊,它一部分在软件中实现,用来给上层协议和程序提供访问接口(Bluez,hci.c hci_usb.chci_sock.c等).另一部分也是在Firmware中实现,用来将软件部分的指令等用底层协议明白的方式传递给底层。

居于PC的上层程序与协议和居于Modules的下层协议之间通过HCI沟通,有4种不同形式的传输:Commands, Event, ACL Data,SCO/eSCO Data

1.1. HCI Command

HCI CommandHostModules发送命令的一种方式。HCICommand Packet结构如下:

OpCode用来唯一标识HCICommand.它由2部分组成,10bitOpcode Command. 6bitOpcodeGroup

1.1.1: OpCode Group:

Linux KernelBlueZ)中,~/include/net/bluetooth/hci.h中定义了OpCodeGroup

#define OGF_LINK_CTL 0x01

#define OGF_LINK_POLICY 0x02

#define OGF_HOST_CTL 0x03

#define OGF_INFO_PARAM 0x04

#define OGF_STATUS_PARAM 0x05

它们代表了不同的Command Group

OGF_LINK_CTL:Linkcontrol,这个Command Group中的Command允许Host控制与其它bluetooth device 的连接。

OGF_LINK_POLICYLinkPolicy。这个Command Group中的Command允许调整LinkManager control.

OGF_HOST_CTL:Controland Baseband.

1.1.2: Opcode Command:

用来在同一个Group内唯一识别Command~/include/net/bluetooth/hci.h中定义。

1.2: HCI Event

ModulesHost发送一些信息,使用HCI EventEventPacket结构如下:

HCI Event3种:Command complete Event, Command States EventCommand SubsequentlyCompletend.

Command complete Event: 如果Host发送的Command可以立刻有结果,则会发送此类Event。也就是说,如果发送的Command只与本地Modules有关,不与remote设备打交道,则使用Command complete Event。例如:HCI_Read_Buffer_Size.

Command States Event如果Host发送的Command不能立刻得知结果,则发送此类EventHost发送的Command执行要与Remote设备打交道,则必然无法立刻得知结果,所以会发送Command States Event.例如:

HCI Connect

Command Subsequently Completend:Command延后完成Event。例如:连接已建立。

下图是一个Command-Event例子:

从这里可以看出,如果Host发送的Command是与Remotedevice有关的,则会先发送CommandStates Event。等动作真正完成了,再发送CommandSubsequently Completend

HCI ACLSCO数据,这里就不多讲了。只需要明白,l2cap数据是通过ACL数据传输给remote device的。

下图很明白的展示了l2cap数据如何一步一步转化为USB数据并传递给底层协议的。

很明显,一个l2cap包会按照规则先切割为多个HCI数据包。HCI数据包再通过HCI-usb这一层传递给USB设备。每个包又通过USB driver发送到底层。

2. HCI protocol的实现:

(稍后添加)

3. HCI 层的编程:

正如上一节所说,HCI是沟通上层协议以及程序与底层硬件协议的通道。所以,通过HCI发送的Command都是上层协议或者应用程序发送给Bluetooth Dongle的。它命令BluetoothDongle(或其中的硬件协议)去做什么何种动作。

3.0:得到Host上插入Dongle数目以及Dongle信息:

我们先复习一下socket的概念:

使用函数socket()建立一个Socket,就如同你有一部电话.bind()则是把这个电话和某个电话号码(网络地址)对应起来。

类似的,我们可以把Host理解为一个房间,这个房间有多部电话(Dongle)。

当使用socket() 打开一个HCI protocolsocket,表明得到这个房间的句柄。HOST可能会有多个Dongle。换句话说,这个房间可以有多个电话号码。所以HCI会提供一套指令去得到这些Dongle

// 0.分配一个空间给hci_dev_list_req。这里面将放所有Dongle信息。

struct hci_dev_list_req *dl;
struct hci_dev_req *dr;

struct hci_dev_info di;

int i;

if (!(dl = malloc(HCI_MAX_DEV * sizeof(struct hci_dev_req)+ sizeof(uint16_t)))) {
  perror("Can't allocate memory");
  exit(1);
 }
 dl->dev_num = HCI_MAX_DEV;
 dr = dl->dev_req;

//1. 打开一个HCI socket.socket相当于一个房间。

if ((ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)) < 0){
  perror("Can't open HCI socket.");
  exit(1);
 }

// 2. 使用HCIGETDEVLIST,得到所有dongleDeviceID。存放在dl中。

if (ioctl(ctl, HCIGETDEVLIST, (void *) dl) < 0) {
  perror("Can't get device list");
  exit(1);
 }

// 3 使用HCIGETDEVINFO,得到对应Device IDDongle信息。

di.dev_id = (dr+i)->dev_id;
ioctl(ctl, HCIGETDEVINFO, (void *) &di)

这样就能得到所有Dongle信息。

struct hci_dev_info {
 uint16_t dev_id;
//dongleDevice ID
 char
name[8];//Donglename

bdaddr_t bdaddr;//Donglebdaddr

uint32_t flags;//DongleFlags:如:UPRUNINGDown等。uint8_ttype;//Dongle连接方式:如USBPC CardUARTRS232等。

uint8_tfeatures[8];

uint32_t pkt_type;
 uint32_t link_policy;
 uint32_t link_mode;

uint16_t acl_mtu;
 uint16_t acl_pkts;
 uint16_t sco_mtu;
 uint16_t sco_pkts;

structhci_dev_statsstat;//Dongle的数据信息,如发送多少个ACL Packet,正确多少,错误多少,等等。};

3.0.1: UPDownBluetooth Dongle

ioctl(ctl, HCIDEVUP, hdev)

ioctl(ctl, HCIDEVDOWN, hdev)

ctl:为使用socket(AF_BLUETOOTH,SOCK_RAW, BTPROTO_HCI)打开的Socket.

hdev: Dongle Device ID.(所以上面的Socket不需要bind,因为这边指定了)

3.1 BlueZ提供的HCI编程接口一(针对本地DongleAPI系列):

3.11 打开一个HCI Socket---inthci_open_dev(int dev_id):

这个function用来打开一个HCI Socket。它首先打开一个HCIprotocolSocket(房间),并将此Socketdevice ID=参数dev_idDongle绑定起来。只有bind后,它才将Socket句柄与Dongle对应起来。

注意,所有的HCI Command发送之前,都需要使用hci_open_dev打开并绑定。

3.1.2: 关闭一个HCISocket

int hci_close_dev(int dd) //简单的关闭使用hci_open_dev打开的Socket

3.1.3: HCISocket(对应一个Dongle)发送 request:

int hci_send_req(int dd, struct hci_request *r, int to)

BlueZ提供这个function非常有用,它可以实现一切HostModules发送Command的功能。

参数1HCISocket

参数2Command内容。

参数3:以milliseconds为单位的timeout.

下面详细解释此function和用法:

当应用程序需要向Dongle(对应为一个bind后的Socket)发送Command时,调用此function.

其中,参数一dd对应一个使用hci_open_dev()打开的SocketDongle)。

参数三to则为等待Dongle执行并回复命令结果的timeout.以毫秒为单位。

参数二hci_request * r 最为重要,首先看它的结构:

struct hci_request {
 uint16_t ogf;    //Opcode Group
 uint16_t ocf;    //Opcode Command
 int
event;//Command产生的Event类型。void*cparam;//Command 参数intclen;//Command参数长度void*rparam;//Response参数intrlen;//Response参数长度};

ogf,ocf不用多说,对应前面的图就明白这是GroupCodeCommand Code。这两项先确定下来,然后可以查HCISpec。察看输入参数(cparam)以及输出参数(rparam)含义。至于他们的结构以及参数长度,则在~/include/net/bluetooth/hci.h中有定义。

至于event.如果设置,它会被setsockopt设置于Socket

1:得到某个连接的PolicySetting.

HCI Spec以及~/include/net/bluetooth/hci.h中均可看到,OGF=OGF_LINK_POLICY(0x02). OCF=OCF_READ_LINK_POLICY(0x0C).

因为这个Command用来读取某个ACL连接的Policy Setting。所以输入参数即为此连接Handle.

返回参数则包含3部分,statusCommand是否顺利执行), handle(连接Handle) policy(得到的policy值)

这就又引入了一个新问题,如何得到某个ACL连接的Handle

可以使用ioctl HCIGETCONNINFO得到ACL 连接Handle

ioctl(dd, HCIGETCONNINFO, (unsigned long) cr)

Connect_handle = htobs(cr->conn_info->handle);

所以完整的过程如下:

struct hci_request HCI_Request;
 read_link_policy_cp Command_Param;
 read_link_policy_rp Response_Param;

// 1.得到ACL Connect Handle

if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) 
 {  
  return -1
}
 Connect_handle = htobs(cr->conn_info->handle);

memset(&HCI_Request, 0, sizeof(HCI_Request));
 memset(&Command_Param, 0 , sizeof(Command_Param));
 memset(&Response_Param, 0 , sizeof(Response_Param));

// 2.填写Command输入参数Command_Param.handle = Connect_handle;

HCI_Request.ogf = OGF_LINK_POLICY;//CommandID
 HCI_Request.ocf = OCF_READ_LINK_POLICY; //Command ID
 HCI_Request.cparam = &Command_Param;
 HCI_Request.clen = READ_LINK_POLICY_CP_SIZE;
 HCI_Request.rparam = &Response_Param;
 HCI_Request.rlen = READ_LINK_POLICY_RP_SIZE;

if (hci_send_req(dd, &HCI_Request, to) < 0)
 {
  perror("\nhci_send_req()");
  return -1;
 }

//如果返回值状态不对

if (Response_Param.status) {
  return -1;
 }

//得到当前policy
 *policy = Response_Param.policy;

3.1.4:几个更基础的function:

static inline void bacpy(bdaddr_t *dst, const bdaddr_t *src)//bdaddr copy

static inline int bacmp(const bdaddr_t *ba1, const bdaddr_t*ba2)//bdaddr 比较

3.1.5: 得到指定DongleBDAddr

int hci_read_bd_addr(int dd, bdaddr_t *bdaddr, int to)

参数1HCISocket,使用hci_open_dev()打开的SocketDongle)。

参数2:输出参数,其中会放置bdaddr.

参数3:以milliseconds为单位的timeout.

3.1.6: 读写DongleName

int hci_read_local_name(int dd, int len, char *name, int to)

int hci_write_local_name(int dd, const char *name, int to)

参数1HCISocket,使用hci_open_dev()打开的SocketDongle)。

参数2:读取或设置Name

参数3:以milliseconds为单位的timeout.

注意:这里的NameIOCTL HCIGETDEVINFO 得到hci_dev_info中的name不同。

3.1.7:得到HCIVersion:

int hci_read_local_version(int dd, struct hci_version *ver, intto)

3.1.8:得到已经UPDongle BDaddr

int hci_devba(int dev_id, bdaddr_t *bdaddr)

dev_id: Dongle Device ID.

bdaddr:输出参数,指定Dongle如果UP则放置其BDAddr

3.1.9: 得到DongleInfo

int hci_devinfo(int dev_id, struct hci_dev_info *di)

dev_id: Dongle Device ID.

di: Dongle信息。

出错返回 -1

注意,这个Function的做法与3.0的方法完全一致。

3.1.10:从hciX中得到X

int hci_devid(const char *str)

str: 类似 hci0这样的字串。

如果hciX对应的DeviceID(X)是现实存在且UP。则返回此设备Device ID

3.1.11:得到BDADDR不等于参数bdaddrDongleDevice ID

int hci_get_route(bdaddr_t *bdaddr)

查找Dongle,发现DongleBdaddr不等于参数bdaddr的第一个Dongle,则返回此Dongle Device ID

所以,如果: int hci_get_route(NULL),则得到第一个可用的Dongle Device ID

3.1.12: BDADDR转换为字符串:

int ba2str(const bdaddr_t *ba, char *str)

3.1.13: 将自串转换为BDADDR

int str2ba(const char *str, bdaddr_t *ba)

3.2 BlueZ提供的HCI编程接口二(针对Remote DeviceAPI系列):

3.2.1inquiry远程BluetoothDevice

int hci_inquiry(int dev_id, int len, int nrsp, const uint8_t*lap, inquiry_info **ii, long flags)

hci_inquiry()用来命令指定的Dongle去搜索周围所有bluetooth device.并将搜索到的BluetoothDevice bdaddr 传递回来。

参数1dev_id:指定Dongle Device ID。如果此值小于0,则会使用第一个可用的Dongle

参数2len: 此次inquiry的时间长度(每增加1,则增加1.25秒时间)

参数3nrsp:此次搜索最大搜索数量,如果给0。则此值会取255

参数4lap:BDADDRLAP部分,Inquiry时这块值缺省为0X9E8B33.通常使用NULL。则自动设置。

参数5ii:存放搜索到Bluetooth Device的地方。给一个存放inquiry_info指针的地址,它会自动分配空间。并把那个空间头地址放到其中。

参数6flags:搜索flags.使用IREQ_CACHE_FLUSH,则会真正重新inquiry。否则可能会传回上次的结果。

返回值是这次Inquiry到的Bluetooth Device 数目。

注意:如果*ii不是自己分配的,而是让hci_inquiry()自己分配的,则需要调用bt_free()来帮它释放空间。

3.2.2:得到指定BDAddrreomte device Name:

int hci_read_remote_name(int dd, const bdaddr_t *bdaddr, intlen, char *name, int to)

参数1:使用hci_open_dev()打开的Socket

参数2:对方BDAddr.

参数3name 长度。

参数4(out)放置name的位置。

参数5:等待时间。

3.2.3: 读取连接的信号强度:

int hci_read_rssi(int dd, uint16_t handle, int8_t *rssi, int to)

注意,所有对连接的操作,都会有一个参数,handle.这个参数是连接的Handle。前面讲过如何得到连接Handle的。

一:L2CAP协议简介:

Logical Link Controland Adaptation Protocol(L2CAP)

逻辑连接控制和适配协议(L2CAP)为上层协议提供面向连接和无连接的数据服务,并提供多协议功能和分割重组操作。L2CAP充许上层协议和应用软件传输和接收最大长度为64KL2CAP数据包。

L2CAP基于通道(channel)的概念。通道(Channel)是位于基带(baseband)连接之上的逻辑连接。每个通道以多对一的方式绑定一个单一协议(single protocol)。多个通道可以绑定同一个协议,但一个通道不可以绑定多个协议。每个在通道里接收到的L2CAP数据包被传到相应的上层协议。多个通道可共享同一个基带连接。

L2CAP处于Bluetooth协议栈的位置如下:

也就是说,所有L2CAP数据均通过HCI传输到Remote Device。且上层协议的数据,大都也通过L2CAP来传送。

L2CAP可以发送Command。例如连接,断连等等。

下面看Command例子:Connection Request:

其中PSM比较需要注意,L2CAP 使用L2CAP连接请求(Connection Request )命令中的PSM字段实现协议复用。L2CAP可以复用发给上层协议的连接请求,这些上层协议包括服务发现协议SDPPSM = 0x0001)、RFCOMMPSM = 0x0003)和电话控制(PSM = 0x0005)等。

Protocol

PSM

Reference

SDP

0x0001

SeeBluetooth Service Discovery Protocol (SDP), Bluetooth SIG.

RFCOMM

0x0003

See RFCOMM with TS 07.10, Bluetooth SIG.

TCS-BIN

0x0005

SeeBluetooth Telephony Control Specification / TCS Binary, Bluetooth SIG.

TCS-BIN-CORDLESS

0x0007

SeeBluetooth Telephony Control Specification / TCS Binary, Bluetooth SIG.

BNEP

0x000F

SeeBluetooth Network Encapsulation Protocal, Bluetooth SIG.

HID_Control

0x0011

See Human Interface Device , Bluetooth SIG.

HID_Interrupt

0x0013

See Human Interface Device, Bluetooth SIG.

UPnP

0x0015

See [ESDP] , Bluetooth SIG.

AVCTP

0x0017

See Audio/Video Control Transport Protocol , Bluetooth SIG.

AVDTP

0x0019

See Audio/Video Distribution Transport Protocol , Bluetooth SIG.

AVCTP_Browsing

0x001B

See Audio/Video Remote Control Profile, Bluetooth SIG

UDI_C-Plane

0x001D

See the Unrestricted Digital Information Profile [UDI], Bluetooth SIG

二:L2CAP编程方法:

L2CAP编程非常重要,它和HCI基本就是Linux Bluetooth编程的基础了。几乎所有协议的连接,断连,读写都是用L2CAP连接来做的。

1.创建L2CAP Socket

socket(PF_BLUETOOTH,SOCK_RAW, BTPROTO_L2CAP);

domain=PF_BLUETOOTH,type可以是多种类型。protocol=BTPROTO_L2CAP.

2.绑定:

// Bind to localaddress
 memset(&addr, 0, sizeof(addr));
 addr.l2_family = AF_BLUETOOTH;
 bacpy(&addr.l2_bdaddr, &bdaddr); //bdaddr
为本地Dongle BDAddr

if (bind(sk,(struct sockaddr *) &addr, sizeof(addr)) < 0) {
  perror("Can't bind socket");
  goto error;
 }

3.连接

memset(&addr, 0,sizeof(addr));
addr.l2_family = AF_BLUETOOTH;
bacpy(addr.l2_bdaddr, src);

addr.l2_psm =xxx;

if (connect(sk,(struct sockaddr *) &addr, sizeof(addr)) < 0) {
  perror("Can't connect");
  goto error;
 }

注意:

struct sockaddr_l2 {
 sa_family_t l2_family;
//必须为 AF_BLUETOOTH
 unsigned short l2_psm;
//与前面PSM对应,这一项很重要bdaddr_t l2_bdaddr;     //Remote DeviceBDADDR
 unsigned short l2_cid; 
};

4. 发送数据到Remote Device

send()write()都可以。

5. 接收数据:

revc() read()

以下为实例:

注:在Bluetooth下,主动去连接的一端作为主机端。被动等别人连接的作为Client端。

背景知识1Bluetooth设备的状态

之前HCI编程时,是用ioctl(HCIGETDEVINFO)得到某个Device Infohci_dev_info).其中flags当时解释的很简单。其实它存放着BluetoothDevice(例如:USB Bluetooth Dongle)的当前状态:

其中,UP,Down状态表示此Device是否启动起来。可以使用ioctl(HCIDEVUP)等修改这些状态。

另外:就是Inquiry Scan, PAGE Scan这些状态:

Sam在刚开始自己做L2CAP层连接时,使用另一台Linux机器插USB Bluetooth DongleRemote Device。怎么也没法使用inquiry扫描到remote设备,也没法连接remote设备,甚至无法使用l2ping pingremote设备。觉得非常奇怪,后来才发现RemoteDevice状态设置有问题。没有设置PSCANISCAN

InquiryScan状态表示设备可被inquiry. Page Scan状态表示设备可被连接。

#hciconfighci0 iscan

#hciconfighci0 pscan

或者:#hciconfig hci0 piscan

就可以设置为PSCAN或者iSCAN状态了。

编程则可以使用ioctl(HCISETSCAN) .dev_opt = SCAN_INQUIRY;dr.dev_opt = SCAN_PAGE;dr.dev_opt = SCAN_PAGE |SCAN_INQUIRY;

则可以inquiry或者connect了。

一:理解Socket

在使用手机与女朋友联系时,必须用手机拨她的号码,然后心情坎坷的等待她的应答。当双方通话时,就建立了一个具有两个端点的通信线路。

Linux中的Socket与电话非常相似。具体问题,稍后再分析。

二:Socket域(domain),类型(type),协议(protoclo)以及Bluetooth中的具体使用:

Berkeley小组在构思BSDSocket时,TCP/IP协议也还处在发展之中,其他一些很有竞争力的协议如X.25等也在发展,其它很多协议还在构思与研究阶段(Bluetooth还没出生)。为了使Socket可以应用于各种不同协议,domain的作用就在于此。

domain指出想要使用的协议族。

不得不佩服Berkeley小组的前瞻力。他们考虑在指定Socket时,可能还需要进一步的细分类目:

1.某个协议族(Domain)中的一个或多个协议。

2.某个协议中的一个或多个地址格式。

这个规则在TCP/IP等协议栈时并不明显,因为某个协议族只有同一种地址格式。但在Bluetooth中则非常有用。

protocol则用来指出在此协议族中的具体某个协议。

虽然在TCP/IP协议栈中,因为协议族中某个type的协议栈只有一种,所以此项为0,但Bluetooth中,这一项则非常有用。

type用来指出此协议族中的具体协议的Socket类型为何种:SOCK_STREAM,SOCK_DGRAM,SOCK_SEQPACKET,SOCK_RAW.

三:Socket地址:

每一种通信协议都对网络地址格式作了明确规定。协议族(Domain+ 协议(protocol)的作用就是指明使用哪种地址类型。

BSD Socket是在ANSI C 标准被采纳之前开发的,所以没有使用(void*)数据类型来接收结构化的地址。BSD的解决方案是定义了一个通用的地址结构:

struct sockaddr

{

sa_family_t sa_family;  //地址族

char sa_data[14];   //地址数据

};

sa_family长度2字节,用来存放地址族。

sa_data长度14字节,用来存放具体的协议的地址数据。

如果是用AF_INET(IPV4),则它的地址类型sockaddr_in如下,刚好与structsockaddr对应

struct sockaddr_in

{

sa_family_t sin_family;    //地址族

uint16_tsip_port;         //端口

struct in_addr sin_addr;   //Internel 地址

unsigned char sin_zero[8]; //占位字节

};

如果是用Bluetooth协议族(PF_BLUETOOTH)中的协议l2capBTPROTO_L2CAP),则地址格式如下:

struct sockaddr_l2

{
 sa_family_t l2_family;  //
地址族unsigned short l2_psm;  //PSM
 bdaddr_t l2_bdaddr;     //Bluetooth
地址unsigned short l2_cid;
};

四:Bluetooth Socket的建立和地址绑定:

int socket(int domain, int type, int protocol);

domain:使用PF_BLUETOOTH

protocol:使用想要建立的Socketprotocol.如果想建立HCI SocketBTPROTO_HCIL2cap:BTPROTO_L2CAP

type:SOCK_SEQPACKET,Packet为单位读取。SOCK_SAW:原始Socket

int bind(int sockfd, const struct sockaddr *my_addr, socklen_taddrlen);

socket与某个地址绑定。

嘿嘿,接着前面Socket与手机的话题,建立一个Socket。就相当于是一个手机,地址,则相当于手机号码。

一个手机想要别人打进来,就需要让别人知道电话号码。而一个Bluetooth 设备想要别人能够连接,也需要将SocketBluetooth地址绑定。

山寨机让我们知道了双卡双待,Bluetooth也可以实现这一点。建立一个Socket,只是一个手机,它可以与多个bdaddr绑定。这就是hci0,hci1等等。

五:理解网络字序:

对于多字节数据,不同的CPU有不同的组织方式,最基本的字节序位:

小端(little-endian: 将低序字节存储在起始位置。

大端(big-endian:将高序字节存储在其实位置。

Intel CPU使用小端。MotorolaCPU使用大端,网络上传输数据的标准顺序为大端。

他们之间的转化:

htobs(), htonl() 主机到网络

ntohl() , ntohs() 网络到主机。

例一:发送SignalingPacket

Signaling Command2Bluetooth实体之间的L2CAP层命令传输。所以得Signaling Command使用CID0x0001.

多个Command可以在一个C-framecontrolframe)中发送。

如果要直接发送Signaling Command.需要建立SOCK_RAW类型的L2CAP连接Socket。这样才有机会自己填充CommandCodeIdentifier等。

以下是一个发送signaling Command以及接收Response的简单例子:

int main(int argc, char** argv)
{
 int l2_sck = 0;
 int iRel
= 0;
 struct sockaddr_l2 local_l2_addr;
 struct sockaddr_l2 remote_l2_addr;
 char str[24] ={0};
 int len = 0;
 int size = 50;
 char* send_buf;
 char* recv_buf;
 int i = 0;
 int id = 1; //
不要为0
 
 send_buf = malloc(L2CAP_CMD_HDR_SIZE + size);
 recv_buf = malloc(L2CAP_CMD_HDR_SIZE + size);

if(argc < 2)
 {
  printf("\n%s <bdaddr>\n", argv[0]);
  exit(0);
 }

// create l2cap raw socket
 l2_sck = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP); //
创建L2CAP protocolRAWPacket
 if(l2_sck < 0)
 {
  perror("\nsocket:");
  return -1;
 }

//bind
 memset(&local_l2_addr, 0, sizeof(struct sockaddr_l2));
 local_l2_addr.l2_family = PF_BLUETOOTH;
 bacpy(&local_l2_addr.l2_bdaddr , BDADDR_ANY);

iRel = bind(l2_sck, (struct sockaddr*) &local_l2_addr,sizeof(struct sockaddr_l2));
 if(iRel < 0)
 {
  perror("\nbind()");
  exit(0);
 }

//connect
 memset(&remote_l2_addr, 0 , sizeof(struct sockaddr_l2));
 remote_l2_addr.l2_family = PF_BLUETOOTH;
 //printf("\nConnect to %s\n", argv[1]);
 str2ba(argv[1], &remote_l2_addr.l2_bdaddr);

iRel = connect(l2_sck, (structsockaddr*)&remote_l2_addr, sizeof(struct sockaddr_l2));
 if(iRel < 0)
 {
  perror("\nconnect()");
  exit(0);
 }

//get local bdaddr
 len = sizeof(struct sockaddr_l2);
 memset(&local_l2_addr, 0, sizeof(struct sockaddr_l2));

//注意,getsockname()参数三是一个输入输出参数。输入时,为参数二的总体长度。输出时,

//为实际长度。iRel = getsockname(l2_sck, (struct sockaddr*) &local_l2_addr,&len);
 if(iRel < 0)
 {
  perror("\ngetsockname()");
  exit(0);
 }
 ba2str(&(local_l2_addr.l2_bdaddr), str);
 //printf("\nLocal Socket bdaddr:[%s]\n", str);
 printf("l2ping: [%s] from [%s](data size %d) ...\n", argv[1],str, size);

for (i = 0; i < size; i++)
  send_buf[L2CAP_CMD_HDR_SIZE + i] = 'A';

l2cap_cmd_hdr *send_cmd = (l2cap_cmd_hdr *) send_buf;
 l2cap_cmd_hdr *recv_cmd = (l2cap_cmd_hdr *) recv_buf;

send_cmd->ident = id;  //如上图所示,这一项为此Command Identifier
 send_cmd->len
=htobs(size);
 send_cmd->code = L2CAP_ECHO_REQ;
//如上图所示,此项为Command code.这项定为:

//Echo Request。对端会发送Response回来。code=L2CAP_ECHO_RSP

while(1)
 {
  send_cmd->ident = id;
  if(send(l2_sck, send_buf, size + L2CAP_CMD_HDR_SIZE, 0) <= 0)
  {
   perror("\nsend():");
  }
  
  while(1)
  {
   if(recv(l2_sck, recv_buf, size + L2CAP_CMD_HDR_SIZE, 0) <=0)
   {
    perror("\nrecv()");
   }
   
   if (recv_cmd->ident != id)
    continue;

if( recv_cmd->code == L2CAP_ECHO_RSP)
   {
    //printf("\nReceive Response Packet.\n");
    printf("%d bytes from [%s] id %d\n",recv_cmd->len, argv[1], recv_cmd->ident);
    break;
   }
   
  }
  sleep(1);
  id ++;
  
 }

close(l2_sck);

return 0;
}

所以说,如果想要发送接收signalingCommand。只需要建立l2cap RAW socket. 并按规则填充command id, command code等。就可以接收发送了。

Command Code: 这个值放在l2cap.h中。

#define L2CAP_COMMAND_REJ 0x01
#define L2CAP_CONN_REQ  0x02
#define L2CAP_CONN_RSP  0x03
#define L2CAP_CONF_REQ  0x04
#define L2CAP_CONF_RSP  0x05
#define L2CAP_DISCONN_REQ 0x06
#define L2CAP_DISCONN_RSP 0x07
#define L2CAP_ECHO_REQ  0x08
#define L2CAP_ECHO_RSP  0x09
#define L2CAP_INFO_REQ  0x0a
#define L2CAP_INFO_RSP  0x0b

例二:任意PSML2CAP连接间数据的传输:

此例子中:Serverclient其实是使用网络的概念定义的。

server用来监听指定PSM的连接,并监听数据。同时,利用poll来查看peer是否断掉了。

Server

#include <stdio.h>
#include<sys/types.h>         
#include <sys/socket.h>
#include <stdlib.h>
#include <poll.h>

#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <bluetooth/l2cap.h>

void * Read_thread(void* pSK);

int main(int argc, char** argv)
{
 int iRel = 0;
 int sk = 0;
 struct sockaddr_l2 local_addr;
 struct sockaddr_l2 remote_addr;
 int len;
 int nsk = 0;
 pthread_t nth = 0;
 struct l2cap_options opts;
 int optlen = 0;
 int slen = 0;
 char str[16] = {0};

if(argc < 2)
 {
  printf("\nUsage:%s psm\n", argv[0]);
  exit(0);
 }

// create l2cap socket
 sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
//发送数据,使用SOCK_SEQPACKET为好if(sk < 0)
 {
  perror("\nsocket():");
  exit(0);
 }

//bind
 local_addr.l2_family = PF_BLUETOOTH;
 local_addr.l2_psm = htobs(atoi(argv[argc -1]));
//lastpsm
 bacpy(&local_addr.l2_bdaddr, BDADDR_ANY);
 iRel = bind(sk, (struct sockaddr *)&local_addr, sizeof(struct sockaddr));
 if(iRel < 0)
 {
  perror("\nbind()");
  exit(0);
 }

//get opts

// in mtu outmtu.每个包的最大值memset(&opts, 0, sizeof(opts));
 optlen = sizeof(opts);
 getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, &optlen);
 printf("\nomtu:[%d]. imtu:[%d]. flush_to:[%d]. mode:[%d]\n",opts.omtu, opts.imtu, opts.flush_to, opts.mode);

//set opts. default value
 opts.omtu = 0;
 opts.imtu = 672;
 if (setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts))< 0)
 {
  perror("\nsetsockopt():");
  exit(0);
 }

//listen
 iRel = listen(sk, 10);
 if(iRel < 0)
 {
  perror("\nlisten()");
  exit(0);
 }

len = sizeof(struct sockaddr_l2);
 while(1)
 {
  memset(&remote_addr, 0, sizeof(struct sockaddr_l2));
  nsk = accept(sk, (struct sockaddr*)(&remote_addr), &len);
  if(nsk < 0)
  {
   perror("\naccept():");
   continue;
  }
  ba2str(&(remote_addr.l2_bdaddr), str);
  printf("\npeer bdaddr:[%s].\n", str);
//得到peer的信息

iRel = pthread_create(&nth, NULL, Read_thread,&nsk);
  if(iRel != 0)
  {
   perror("pthread_create():");
   continue;
  }
  pthread_detach(nth);
// 分离之}

return 0;
}

void * Read_thread(void* pSK)
{
 //struct pollfd fds[10];
 struct
pollfdfds[100];
 char buf[1024] = {0};
 int iRel = 0;
 int exit_val = 0;

//fds[0].fd = *(int*)pSK;
 //fds[0].events = POLLIN | POLLHUP;

fds[0].fd=(int)(*(int*)pSK);
 fds[0].events
=POLLIN|POLLHUP;

while(1)
 {
  if(poll(fds, 1, -1) < 0)
  {
   perror("\npoll():");
  }
  if(fds[0].revents & POLLHUP)
  {
   //hang up
   printf("\n[%d] Hang up\n", *(int*)pSK);
   close(*(int*)pSK);
   pthread_exit(&exit_val);

break;
  }

if(fds[0].revents & POLLIN)
  {
   memset(buf, 0 , 1024);
   //read data
   iRel = recv(*(int*)pSK, buf, 572, 0);
   //printf("\nHandle[%d] Receive [%d] data:[%s]",*(int*)pSK, iRel, buf);
  }
  
 }

return 0;
}

client:

#include <stdio.h>
#include<sys/types.h>         
#include <sys/socket.h>
#include <unistd.h>

#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <bluetooth/l2cap.h>

int main(int argc, char** argv)
{
 int sk;
 int i = 0;
 char buf[24] = "Sam is Good Guy!";
 struct sockaddr_l2 local_addr;
 struct sockaddr_l2 remote_addr;
 int iRel = 0;

if(argc < 3)
 {
  printf("\nUsage:%s <bdaddr> <PSM>\n",argv[0]);
  exit(0);
 }

sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
 if(sk < 0)
 {
  perror("\nsocket():");
  exit(0);
 }

//bind. bluetooth好像不许有无名Socket
 local_addr.l2_family = PF_BLUETOOTH;
 bacpy(&local_addr.l2_bdaddr, BDADDR_ANY);
 iRel = bind(sk, (struct sockaddr *)&local_addr, sizeof(structsockaddr));
 if(iRel < 0)
 {
  perror("\nbind()");
  exit(0);
 }

memset(&remote_addr, 0, sizeof(struct sockaddr_l2));
 remote_addr.l2_family = PF_BLUETOOTH;
 str2ba(argv[1], &remote_addr.l2_bdaddr);
 remote_addr.l2_psm = htobs(atoi(argv[argc -1]));
 
 connect(sk, (struct sockaddr*)&remote_addr, sizeof(structsockaddr_l2));

for(i = 0; i < 60; i++)
 {
  iRel = send(sk, buf, strlen(buf)+1, 0);
  printf("Send [%d] data\n", strlen(buf)+1);
  sleep(1);
 }

close(sk);
 return 0;
}

注意:

1. Linux 网络编程中,主动发起连接方,因为不关心地址具体是什么,所以可以作为无名socket,也就是说可以不bind. Bluetooth则不可以,一定需要bind.

2. poll可以查出连接断连,但需要注意:断开的revent值为:11001B。也就是说:POLLIN | POLLERR |POLLHUP

3. 被连接一方,一定要指定PSM

Service Discovery Protocol(SDP)提供一种能力,让应用程序有方法发现哪种服务可用以及这种服务的特性。

服务发现协议(SDPBluetooth SDP)在蓝牙协议栈中对蓝牙环境中的应用程序有特殊的含意,发现哪个服务是可用的和确定这些可用服务的特征。SDP定义了bluetooth client发现可用bluetoothserver服务和它们的特征的方法。这个协议定义了客户如何能够寻找基于特定属性的服务而不让客户知道可用服务的任何知识。SDP提供发现新服务的方法,在当客户登录到正在操作的蓝牙服务器的一个区域时是可用的时。

Service discovery机制提供client应用程序侦测server应用程序提供的服务的能力,并且能够得到服务的特性。服务的品质包含服务type或服务class.

SDP也提供SDP serverSDP client之间的通讯。SDPserver维护着一个服务条目(service record)列表.每个服务条目描述一个单独的服务属性。 SDPclient可以通过发送SDP request来得到服务条目

如果一个client或者依附于client之上的应用程序决定使用某个service. 它创建一个单独的连接到service提供者。 SDP 只提供侦测Service的机制,但不提供如何利用这些Service的机制。Sam觉得,这里其实是说:SDP只提供侦测Service的办法,但如何用,SDP不管。

每个Bluetooth Device最多只能拥有一个SDP Server。如果一个BluetoothDevice只担任Client,那它不需要SDP Server。但一个Bluetooth Device可以同时担当SDPServerSDP client.

Service Record(Service 条目)

一个service是一个实体为另一个实体提供信息,执行动作或控制资源。一个service可以由软件,硬件或软硬件结合提供。

所有的Service信息都包含于一个Service Record内。一个ServiceRecord包含一个Service attribute(Service属性) list.

在一个SDP Server内,每个Service Record拥有一个32-bit的唯一性数据。通常,这个唯一性只是在每个SDP Server内部。如果SDPServer S1 SDP Server S2拥有同样的一个Service Record。那他们在不同SDPSever内的独特数值并不一定相同。

SDPSDP Server增加或减少Service Record时,并不会通知SDPclient.

Service Attribute(Service 属性)

每个Service属性描述servcie的特性.一个Service Attribute2部分:

Attribute ID + Attribute Value

Attribute ID16-bit无符号整数,用于区别一个Service Record内的其它属性。

Attribute ValueAttribute值。

Service Class

每个Service 都是某个Service Class的实例.Service Class定义了Service Record中包含的Service 属性。属性ID,属性值都被定义好了。

每个Service Class也有一个独特ID。这个Service Class标识符包含在属性值ServiceClassIDList属性中。并描绘为UUID。自从Service Record中的属性格式以及含义依赖于ServiceClass后,ServiceClassIDList属性变得非常重要。

Searching For Service:

Service Search transaction(事务?)允许client得到Service Record Handle。一旦SDP Client得到ServiceRecord Handle,它就可以请求这个Record内具体属性的值。

如果某个属性值UUID,则可以通过查找UUID查到这个属性。

UUID universally uniqueidentifier.(唯一性标识符)

SDP协议栈使用request/response模式工作,每个传输过程包括一个request protocol data unit(PDU)和一个response PDU. SDP使用L2CAP连接传输数据。在发送Request PDU但未收到ResponsePDU之前,不能向同一个server再发送RequestPDU

PDUprotocolData unit

PDU ID:用来识别PDU

TransactionID:

用来识别Request PUD以及Response PUD。并用来对比某个ResponsePUD是否对应着Request PUD

BlueZ提供的SDP API,常见的如下:

1.

sdp_session_t*sdp_create(int sk, uint32_t flags)

参数1sk: socket

参数2SDP flags. 取值如下:

#defineSDP_RETRY_IF_BUSY 0x01 #define SDP_WAIT_ON_CLOSE 0x02 #define SDP_NON_BLOCKING 0x04

创建一个新的Session为了异步查找。

Session结构如下:

typedef struct {
 int sock;
 int state;
 int local;
 int flags;
 uint16_t tid; // Current transaction ID
 void *priv;
} sdp_session_t;

sdp_create只是简单的创建一个空间将Session指针返回。并将skflags传入Session对应值。

同时,会创建transaction并将指针给priv.

struct sdp_transaction{
 sdp_callback_t *cb; 
 void *udata;  
 uint8_t *reqbuf; 
 sdp_buf_t rsp_concat_buf;
 uint32_t reqsize; 
 int err;  
};

2.

staticinline int sdp_is_local(const bdaddr_t *device)

察看参数bdaddr是否为本地bdaddr--{0, 0, 0,0xff, 0xff, 0xff}

如果是本地,则返回1。否则返回0

3.

staticint sdp_connect_local(sdp_session_t *session)

连接本地。并将socket赋予参数session对应数据。

创建 socket如下。socket(PF_UNIX,SOCK_STREAM, 0)

sa.sun_family =AF_UNIX;
 strcpy(sa.sun_path, SDP_UNIX_PATH);

并连接之。

4.

staticint sdp_connect_l2cap(const bdaddr_t *src,const bdaddr_t *dst, sdp_session_t*session)

参数1src: bdaddr.

参数2dst: 目标bdaddr.

参数3: session: sdp_create所创建的session.

动作:

创建l2cap socket.并连接,同时PSM=0x01(SDP)

并将socket 存入session. 只有与对端连接后,才可以得到SDP信息。

5.

sdp_session_t*sdp_connect(const bdaddr_t *src, const bdaddr_t *dst, uint32_t flags)

参数1src BDAddr

参数2dst 目标BDAddr

参数3flags 取值如下:

#defineSDP_RETRY_IF_BUSY 0x01 #define SDP_WAIT_ON_CLOSE 0x02 #define SDP_NON_BLOCKING 0x04

注意,SDP_RETRY_IF_BUSYSDP_NON_BLOCKING互斥。

此函数会创建session.并创建l2cap socket,连接远端dst. PSM1SDP)。

6.

uuid_t*sdp_uuid16_create(uuid_t *u, uint16_t val)

将参数2 val copy到参数1 value.uuid16中去。

7.

sdp_list_t*sdp_list_append(sdp_list_t *p, void *d)

将参数2 加入参数1 的链表中。

参数1是个单向链表。将参数2加入此单向链表中。如果参数1为空,则创建一个单向链表。

8.

intsdp_service_search_attr_req(sdp_session_t *session, const sdp_list_t *search,sdp_attrreq_type_t reqtype, const sdp_list_t *attrids, sdp_list_t **rsp)

这个function非常重要。

参数1sdp_session_t *sessionsession 中的sock为已SDP连接(l2cap, psm=0x01.

参数2const sdp_list_t *searchsearch是想要查找SDP Record的链表。如PNP,HID等。

参数3sdp_attrreq_type_t reqtype

typedef enum {
 
 SDP_ATTR_REQ_INDIVIDUAL = 1,
 
 SDP_ATTR_REQ_RANGE
} sdp_attrreq_type_t;

参数4const sdp_list_t *attridssearch中指定的SDP Record中的特征链表。如果想要得到某record中所有特征。则使用0x0000ffff为内容创建链表。

参数5sdp_list_t **rsp: 得到的Attr的信息。

这个function是用来client发送requestserver。得到符合service search pattern(参数2)的SDP Record中的Attribute。例如:可以得到PNPHID record中的属性。如VIDPID,以及report等。

function发送SDP_ServiceSearchAttributeRequestPDU ID=0x06),并将search中包含的特征以及attrids放入参数。并等待SDP_ServiceSearchAttributeResponse。并将返回的信息放入参数5中。

参数5的具体解析,则看search是什么。PNP则查PNP的文档。HID则查HID——SPEC

9.

intsdp_close(sdp_session_t *session)

关闭session->sock

附录1

PDU格式:(PROTOCOL DATA UNITFORMAT

转载_Linux下Bluetooth编程相关推荐

  1. Linux下Bluetooth编程

    一:Bluetooth基本概念: Bluetooth是爱立信.诺基亚.东芝.IBM和Intel 5家公司在1998年联合推出的一项无线网络技术.其宗旨是提供一种短距离.低成本的无线传输应用技术.在行业 ...

  2. 实战Linux Bluetooth编程

    实战Linux Bluetooth编程(一) 协议栈概述 Sam一年前在Linux下写了一个类似Windows下BTW的库--BTX.现在需要添加新功能时发现很多知识点都忘记 了.所以决定在这次学习中 ...

  3. 实战Linux Bluetooth编程(四) L2CAP层编程

    2019独角兽企业重金招聘Python工程师标准>>> (L2CAP协议简介,L2CAP在BlueZ中的实现以及L2CAP编程接口) 一:L2CAP协议简介: Logical Lin ...

  4. Windows环境下Unicode编程总结和将ANSI转换到Unicode 将Unicode转换到ANSI

    Windows环境下Unicode编程总结 UNICODE环境设置 在安装Visual Studio时,在选择VC++时需要加入unicode选项,保证相关的库文件可以拷贝到system32下. UN ...

  5. [zz]Linux 下 socket 编程示例

    本示例为 Client/Server 结构,通过代码演示 Client 如何建立连接,并向远程端发送数据:Server 端如何侦听系统连接请求,接收请求并建立连接,进而获取客户端发来的数据.代码虽短, ...

  6. 《转载》Python并发编程之线程池/进程池--concurrent.futures模块

    本文转载自 Python并发编程之线程池/进程池--concurrent.futures模块 一.关于concurrent.futures模块 Python标准库为我们提供了threading和mul ...

  7. linux下 C编程改变输出字体颜色

    linux下 C编程改变输出字体颜色 分类: Linux基础编程 Linux操作系统 2012-09-19 10:53 1409人阅读 评论(0) 收藏 举报 linux编程c 格式: echo &q ...

  8. 02、体验Spark shell下RDD编程

    02.体验Spark shell下RDD编程 1.Spark RDD介绍 RDD是Resilient Distributed Dataset,中文翻译是弹性分布式数据集.该类是Spark是核心类成员之 ...

  9. linux下Bash编程until语句及格式化硬盘分区等编写脚本(十)

    linux下Bash编程until语句及格式化硬盘分区等编写脚本(十) 1.循环语句结构总结 1.1.while语句当条件满足时,进入循环语句 while 条件; do 语句 done 1.2.unt ...

最新文章

  1. qt获取combobox的值_Qt官方示例嵌套甜甜圈
  2. BREW应用的分发流程
  3. 【Linux】gcc和g++的区别
  4. 如何在SAP Spartacus里增添自定义的配置条目
  5. 蓝桥杯基础模块6_2:定时器进阶
  6. TCP/IP网络协议栈:IP协议
  7. 运行出现Server Tomcat v8.5 Server at localhost failed to start.和A child container failed during start...
  8. 如何查看SQL的执行计划
  9. SCI-hub论文下载器(附下载方法)
  10. 开发悬赏平台APP心得
  11. 001 第一季:SpringBoot2核心技术
  12. html如何添加音乐火狐,电脑如何将HTML书签导入进火狐浏览器中
  13. 传说中程序员都是直男,程序员适合做老公吗?
  14. 对接抖音开发之售后消息实时通知订单部分退款
  15. 项目管理证书 PMP 考试费用要多少?
  16. 机器学习:美国50K工资分类(改)
  17. 【正交调制】数字调制技术之正交调制
  18. 计算机物理安全策略,涉密单机物理安全策略.doc
  19. 新玺配资:能耗双控双刃剑 造纸板块一飞冲天
  20. Unity 编辑器ScrollView滚动卡顿优化

热门文章

  1. retrofit原理面试,2021最新百度、头条等公司Android社招面试题目,含答案解析
  2. Three.js盖房子 点击开关门
  3. 支付宝微信的数字经营项目是风口还是割韭菜(带项目评测)
  4. 经纬财富:乐山稳健投资之道,积小胜为大胜
  5. 计算机蜂鸣无法开机,计算机开机后会时常出现3声蜂鸣是什么原因
  6. SFP光模块电气接口参数详解
  7. 计算机音乐作曲专业,计算机音乐创作专业组
  8. PMBOK项目管理九大知识领域和五大流程 --美国IT项目管理硕士笔记(二)
  9. 为postgreSQL添加man帮助
  10. nodejs解压缩zip文件:adm-zip