一、蓝牙基本概念蓝牙BLE(Bluetooth Low Energy/低功耗蓝牙)一般应用苹果的官方框架基于CoreBluetooth/CoreBluetooth.h框架进行开发。中心设备用于扫描周边蓝牙外设的设备比如我们上面所说的中心者模式此时我们的手机就是中心设备。外设被扫描的蓝牙设备比如我们上面所说的用我们的手机连接小米手环这时候小米手环就是外设。广播外部设备不停的散播的蓝牙信号让中心设备可以扫描到也是我们开发中接收数据的入口。服务(Service)外部设备在与中心设备连接后会有服务可以理解成一个功能模块中心设备可以读取服务筛选我们想要的服务并从中获取出我们想要特征。外设可以有多个服务特征(Characteristic)服务中的一个单位一个服务可以多个特征而特征会有一个value一般我们向蓝牙设备写入数据、从蓝牙设备读取数据就是这个valueUUID区分不同服务和特征的唯一标识使用该字端我们可以获取我们想要的服务或者特征核心类CBCentralManager 中心设备管理类、CBCentral 中心设备、CBPeripheralManager 外设设备管理类、CBPeripheral 外设设备、CBUUID 外围设备服务特征的唯一标志、CBService 外围设备的服务、CBCharacteristic 外围设备的特征。二、发展历史第一代蓝牙主要是指 90 年代的 V1.0V1.2 版本是关于短距离通信的早期探索此时还存在许多问题应用不是特别广泛第二代蓝牙主要是 00 年中 V2.0V2.1 版本新增了 EDREnhanced Data Rate技术提高传输速率以及体验及安全第三代蓝牙主要是 00 年末 V3.0 版本新增了 802.11 WiFi 协议引入了 AMPGeneric Alternate MAC/PHY交替射频技术极大的提高了传输速率并降低功耗第四代蓝牙是 10 年以来的 V4.0V4.2 版本主推 LELow Energy低功耗大约仅消耗十分之一将三种规格包括经典蓝牙、高速蓝牙、和低功耗蓝牙集中在一起形成一套综合协议规范第五代蓝牙是 16 年开始提出的 V5.0 版本主要是为了支持物联网在功耗、传输速率、有效传输距离、数据包容量方面都做了极大的提升蓝牙 4.0 以后的版本分为两种模式单模蓝牙和双模蓝牙。单模蓝牙即低功耗蓝牙模式是蓝牙 4.0 中的重点技术低功耗快连接长距离。双模蓝牙支持低功耗蓝牙的同时还兼容经典蓝牙经典蓝牙的特点是大数据高速率例如音频、视频等数据传输。三、低功耗蓝牙(BLE) vs 经典蓝牙(SPP)BLE 和 SPP 怎么选看应用场景BLE适用于低功耗、轻量级的应用例如穿戴设备、传感器网络等。而SPP适用于需要大容量数据传输的应用例如音频设备、文件传输等。看功耗需求如果你的应用对功耗有严格要求需要长时间运行并且传输的数据量较小那么选择BLE是明智的。如果你的应用对功耗要求不高但需要高速数据传输那么选择SPP可能更合适。看连接距离需求如果你需要在较远距离进行通信经典蓝牙通常具备更广泛的连接范围。而如果通信是在相对较短的距离内进行BLE可能是个更好的选择。三、BLE 协议栈BLE 协议栈一般是指芯片厂家依据Bluetooth SIG发布的Bluetooth Core Specification核心协议的实现的代码固件并提供函数接口由芯片内部程序调用可实现上节BLE工作流程等相关功能。常见的协议栈有德州仪器 TI 的ble-stack和Nordic 的SoftDeviceTI 的 CC26 系列芯片协议栈结构图Nordic 的 nRF52 系列芯片的协议栈结构图协议栈结构无论是哪个芯片厂商实现的 BLE 协议栈其结构都非常的相似均三个部分顶层Application中层Host底层Controller然后每一层又分成若干个子模块。四、GAP和GATT蓝牙协议栈分为两类结构控制器Controller和主机Host。每个类别都有子类别这些子类别执行特定的角色。我们将要研究的两个子类别是 通用访问配置文件GAP和 通用属性配置文件GATTGAPGeneric Access Profile通用访问配置文件。GATTGeneric Attribute Profile通用属性配置文件。通用访问配置文件GAPBLE 设备可以使用两种机制与外界通信广播或连接。这些机制受通用访问配置文件GAP准则的约束。GAP 定义了启用 BLE 的设备如何使其自身可用以及两个设备如何直接相互通信。通用属性配置文件GATTGATT 分为两种类型客户端Client客户端可以发送请求给 GATT 服务端客户端可以读Read/写Write服务端的属性Attributes 通过属性可以通信数据。服务端Server服务端是用来存储属性Attributes 的每当客户端发送请求时服务端会相应这些请求。五、ios 中提控4个框架连接蓝牙1.GameKit.framework只能用于ios设备间连接多用于游戏类 ios7以后开始有接口过期2.MultipeerConnectivity.fremework用于ios间设备通讯主要用于沙盒文件共享 在iOS7中引入了一个全新的框架--Multipeer Connectivity多点连接。利用Multipeer Connectivity框架即使在没有连接到WiFiWLAN或移动网络xG的情况下距离较近的Apple设备iMac / iPad / iPhone之间可基于蓝牙和WiFiP2P WiFi技术进行发现和连接实现近场通信。3.ExternalAccessory.framework可用于第三方蓝牙设备交互但是蓝牙设备必须经过苹果MFi认证4.CoreBluetooth.framework可用于第三方蓝牙设备交互必须要支持蓝牙4.0三、BLE中心模式流程几个概念Central:中心设备,发起蓝牙连接的设备(一般指手机)Peripheral:外设,被蓝牙连接的设备(一般是运动手环/蓝牙模块)Service:服务,每个设备会提供服务,一个设备有很多服务比如手环的震动和亮起来的颜色是两个不同服务Characteristic:特征,每个服务中包含很多个特征,这些特征的权限一般分为:读(read)/写(write)/通知(notice)几种,可以通过特征进行读写数据(重要角色)(中心设备写入数据的时候一定要找到可写入特征才可以写入成功)Descriptor:描述者,每个特征可以对应一个或者多个描述者,用于描述特征的信息或者属性建立中心角色扫描外设(Discover Peripheral)连接外设(Connect Peripheral)扫描外设中的服务和特征获取外设的services获取外设的Characteristics,获取characteristics的值,,获取Characteristics的Descriptor和Descriptor的值利用特征与外设做数据交互(Explore And Interact)订阅Characteristic的通知断开连接(Disconnect)另推荐LightBlue App基于CoreBluetooth。是BLE开发的调试利器该App上能获取的数据你就能用代码实现软硬件工程师蓝牙开发必备。四、蓝牙数据加密在 iOS 蓝牙开发中实现安全的数据传输需要结合硬件层、协议层和应用层的加密机制一、协议层加密推荐方案1. 使用 BLE 的内置加密DTLS通过配对Pairing和绑定Bonding机制实现// 在连接设备时启用安全连接 let options: [String: Any] [ CBConnectPeripheralOptionVerifyServiceKeys: true, CBConnectPeripheralOptionNotifyOnEncryptionChange: true ] centralManager.connect(peripheral, options: options) // 监听加密状态变化 func centralManager(_ central: CBCentralManager, didUpdatePeripheralState peripheral: CBPeripheral) { if peripheral.isEncrypted { print(连接已加密) } else { print(连接未加密可能需要配对) } }2. 配对与密钥交换过程Just Works零交互配对默认数字比较用户确认两端显示的数字是否一致Passkey Entry用户手动输入 6 位数字密码3. 密钥管理iOS 自动处理长期密钥LTK的存储和交换开发者无需手动干预。二、应用层加密补充方案1. 使用 AES-256 加密import CryptoKit func encryptData(_ data: Data, withKey key: SymmetricKey) - Data? { do { let sealedBox try AES.GCM.seal(data, using: key) return sealedBox.combined } catch { print(加密失败: \(error)) return nil } } func decryptData(_ encryptedData: Data, withKey key: SymmetricKey) - Data? { do { let sealedBox try AES.GCM.SealedBox(combined: encryptedData) return try AES.GCM.open(sealedBox, using: key) } catch { print(解密失败: \(error)) return nil } } // 生成随机密钥 let encryptionKey SymmetricKey(size: .bits256)2. 密钥交换机制使用椭圆曲线 Diffie-HellmanECDH算法交换对称密钥// 生成密钥对 let localPrivateKey P256.KeyAgreement.PrivateKey() let localPublicKey localPrivateKey.publicKey // 将公钥发送给外设 func sendPublicKey() { let publicKeyData localPublicKey.rawRepresentation // 通过蓝牙发送publicKeyData } // 接收外设公钥并生成共享密钥 func receiveRemotePublicKey(_ remotePublicKeyData: Data) { do { let remotePublicKey try P256.KeyAgreement.PublicKey(rawRepresentation: remotePublicKeyData) let sharedSecret try localPrivateKey.sharedSecretFromKeyAgreement(with: remotePublicKey) // 派生对称加密密钥 let encryptionKey sharedSecret.hkdfDerivedSymmetricKey( using: SHA256.self, salt: Data(BluetoothData.utf8), outputByteCount: 32 ) // 使用encryptionKey进行数据加密 } catch { print(密钥交换失败: \(error)) } }三、端到端加密E2EE实现1. 消息结构设计2. 安全传输流程建立 BLE 连接启用加密通过 ECDH 交换对称密钥使用 AES-GCM 加密应用数据添加时间戳和序列号防止重放攻击实现消息认证确保数据完整性问题17在 iOS 蓝牙开发中发送大数据如文件、图像需要解决 MTU 限制、数据包丢失和传输效率等问题一、核心挑战与限制MTU最大传输单元限制默认 MTU 为 23 字节有效载荷 20 字节可通过CBPeripheral.requestMtu(_:)扩展至 512 字节传输可靠性BLE 采用不可靠传输需应用层实现确认机制内存管理避免一次性加载大文件到内存应分块处理二、解决方案实现1. 扩展 MTU// 连接成功后立即请求扩展MTU func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { peripheral.requestMtu(512) // 请求最大MTU } // MTU变更回调 func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) { // 检查MTU变更结果 print(当前MTU: \(peripheral.maximumWriteValueLength(for: .withResponse))) }2. 数据分块、编号与重组3. 实现可靠传输协议4. 优化传输效率问题18在 iOS 蓝牙开发中如何保证数据的完整性添加 CRC 校验确保数据完整性实现序列号机制检测乱序包蓝牙与硬件交互比如在 iOS 开发中实现通过蓝牙控制新能源汽车的开关门功能需要结合CoreBluetooth 框架和汽车厂商提供的蓝牙协议。以下是完整的实现方案一、核心技术栈CoreBluetoothiOS 原生框架用于蓝牙低功耗BLE设备通信。协议解析需与汽车厂商合作获取私有通信协议如指令格式、加密方式。后台模式申请bluetooth-central后台模式支持在后台保持连接。二、开发流程1. 权限配置Capabilities中启用Background Modes→Uses Bluetooth LE accessories。Info.plist中添加蓝牙权限描述2.蓝牙中心管理器初始化3. 扫描与连接车辆蓝牙设备4. 发现服务与特征5. 发送开关门指令6. 接收车辆状态反馈三、安全与加密1.数据加密实际应用中指令通常需经过加密如 AES、RSA。密钥可能通过设备配对或用户认证获取。2.身份验证需实现设备配对流程如 PIN 码验证。每次连接时验证车辆身份防止中间人攻击。2.指令格式实际协议可能包含指令头、校验和、时间戳等。// 示例指令格式伪代码 [指令头][命令类型][数据长度][加密数据][校验和]五、实际应用注意事项1.权限申请需向汽车厂商申请 API 访问权限。部分功能可能需通过官方 App 间接实现。2.错误处理网络中断、蓝牙断开时的重连逻辑。超时处理和错误提示。3.后台运行使用CBCentralManager的retrieveConnectedPeripherals恢复连接。通过CBPeripheralManager实现后台广告。四、开发过程中遇到的问题问题1.调用搜索函数时搜索不到设备的问题返回的nil。答当首次调用函数搜索设备外设时无法获取外设设备信息的原因是central的state为CBCentralManagerStateUnknown,这个状态表示手机设备的蓝牙状态为未开启。解决方法需要在此委托方法中监听蓝牙状态的状态改变为ON时去开启扫描操作。问题2.外设蓝牙名称被修改后可能搜索不到的问题答在测试的过程中正常获取蓝牙名称是通过peripheral.name获取但是可能存在这种情况是当修改连接过的蓝牙名称后可能存在搜索不到的情况。解决方法在蓝牙的广播数据中 根据kCBAdvDataLocalName这个key便可获得准确的蓝牙名称。问题3.调用断开蓝牙的接口手机蓝牙并没有马上与外设断开连接而是等待5秒左右的时间后才真正断开。答可以与硬件开发的同事沟通从设备收到数据后主动断开连接即可。问题4.是否能长时间处于后台答可以后台长时间执行需要开启Background Modes。后台扫描设备跟前台扫描周围设备有一点不同 也许是考虑到功耗的原因在后台只能搜索特定的设备所以必须要传Service UUID。不传的 话一台设备都搜不到。而这时就需要外设在广播包中有Service UUID。问题5.蓝牙允许连接的最大距离支持是多少答iOS 蓝牙允许连接的最大距离的限制是10m。问题6.蓝牙连接成功需要多长时间答正常连接周围的蓝牙外设一般时在5秒内问题7.多台设备是否能同时连接答可以iOS 支持多设备同时连接分别管理不同 Peripheral 实例即可。问题8.如何保证发送数据的完整性答做 一个分包发送的操作保证了数据的完整性。问题9.如何实现重连机制答自动重连函数被调用之后会设置一个全局标识为_isAutoConnectYES然后判断手机设备的蓝牙是否开启若开启则重连扫描外设设备当扫到上一次连接的蓝牙设备后就会调用连接的代理函数并停止扫描。如果手动杀掉APP那么再次打开APP的时候APP是不会自动连接设备的但是由于系统蓝牙此时还是与手表连接中的所以需要重新扫描设备因为在扫描的代理函数中添加了自动连接的逻辑经过测试当扫描到上次连接上的蓝牙外设后就会停止。方式一:直接扫描重连方式二通过系统提供的函数retrieveConnectedPeripheralsWithServices方式三通过系统提供的函数retrievePeripheralsWithIdentifiers问题10如何获取已经配对过的蓝牙外设答系统一共提供了两个函数来获取已经配对过的蓝牙外设,NSArray *[_centralManager retrieveConnectedPeripheralsWithServices:#(nonnull NSArrayCBUUID * *)#];( CBUUID指的是ServiceUUID)、[_centralManager retrievePeripheralsWithIdentifiers:#(nonnull NSArrayNSUUID * *)#];参数是个已连接的ServiceUUID或Identifiers的数组是个必填项若传[]空数组则返回值是nil。问题11开发蓝牙 APP有什么工具可以协助蓝牙测试答首先测试蓝牙必须时真机其次安装了蓝牙调试助手或LightBlue等第三方App来调试蓝牙的开发。问题12App作为中心设备端连接到蓝牙设备之后如何获取外设设备的Mac地址答iOS端是无法直接获取设备的Mac地址但是可以间接获取但都需要和硬件工程师进行沟通。1将蓝牙外设广播里提供Mac地址这样中心设备端在扫描阶段可以直接读取广播里的值从而获取到外设设备的Mac地址。2可以在外设设备的某个服务的特征中提供Mac地址但是前提是要确定是读取哪个特征UUID是多少。问题13为什么两个 iPhone 手机的都打开蓝牙之后却相互搜不到彼此手机上的同个蓝牙Demo答在蓝牙通信中分为中心端和设备端。而通常手机蓝牙Demo都处在中心端状态也就是只能接收广播而自己没有向周围发送广播。所以两台手机之间一般是无法发现对方的因为大家都是中心端问题14蓝牙外设设备升级等大数据传输耗时操作数据发送时选择CBCharacteristicWriteWithResponse会影响总交互时间 使用CBCharacteristicWriteWithoutResponse又回出现丢包的现象。答[self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];[self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];如果交互的总时间我们不能接受可以选用CBCharacteristicWriteWithoutResponse模式每包20字节循环发但注意每发12包延迟100毫秒经验值12包240字节小于250即可加快大数据传输速率。问题15蓝牙设备的五种工作状态准备standby广播advertising监听扫描Scanning发起连接Initiating已连接Connected问题16蓝牙为什么有时候扫描不到设备1.没开蓝牙、没给蓝牙权限2.设备没进入广播配对模式3.UUID 不匹配、过滤写错4.手机已绑定系统蓝牙占用连接5.台扫描受限、扫描间隔太短6.手机已绑定系统蓝牙占用连接7.设备广播间隔过大、信号弱问题17:蓝牙连接经常断开是什么原因1.信号弱、距离远、遮挡2.设备休眠、超时断连3.手机后台刷新被系统杀死4.未重连机制、异常没做重连5.读写数据太频繁导致缓存溢出断开问题18:特征值 Write 两种方式区别withResponse写数据后外设必须回应可靠、慢withoutResponse无回应高速发数据、不可靠问题19:CoreBluetooth 代理常用哪些蓝牙状态didUpdateState扫描发现设备didDiscoverPeripheral连接成功 / 失败didConnect、didFailToConnect发现服务didDiscoverServices发现特征didDiscoverCharacteristics接收通知数据didUpdateValueForCharacteristic问题20、蓝牙数据粘包、分包怎么处理BLE 单次传输 MTU 有限大数据会分包做法自定义协议头包头 长度 数据 校验接收方拼接字节流按长度拆分完整包协商 MTU 增大传输单元问题21:外设端 Peripheral 你做过吗用 CBPeripheralManager创建服务、添加特征、开始广播、处理中心端读写请求。面试官压轴题问题21.蓝牙适配 iOS 版本差异坑iOS13 强制蓝牙隐私权限iOS14 扫描权限更严格必须指定 UUID 否则扫不到苹果为了隐私保护防止 App 后台偷偷扫描、偷窥周围智能设备你必须明确告诉系统我只扫描我家硬件的专属 Service UUID后台扫描、后台连接各版本限制不同系统会缓存已连接设备导致手动连不上问题22.怎么保证蓝牙通信数据安全自定义加密协议数据 AES 加密后再发配对绑定、白名单设备 UUID 过滤禁止明文传输关键指令问题23:怎么判断这个设备是「自家设备」第一步判断 广播里的 Service UUID最常用、企业标配硬件出厂固定一个唯一服务 UUID比如FFF0、FFE0// iOS14 必须指定自家设备 Service UUID不能传nil let targetUUID [CBUUID(string: FFF0)] centralManager.scanForPeripherals(withServices: targetUUID, options: nil)第二步取advertisementData中UUID和本地写死的预设 UUID 比对func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi: NSNumber) { // 每扫到一个设备就进这里 // 在这里过滤是不是自家设备 }advertisementData这个字典就是未连接状态下的广播原始数据扫描回调里从advertisementData取出服务 UUID和本地写死的预设 UUID 比对一样就是自家设备BLE 设备没被连接的时候会不停向外发广播包Advertisement 广播数据额外确认操作连接后校验特征值 / 秘钥前面筛选只是初步连上后读一个特定特征值或者下发握手指令设备返回约定秘钥握手成功才认为是合法设备防止仿冒。问题24:蓝牙是一包一包发送数据吗为了省电、提高传输效率回调不是跟硬件发包一一对应的你收到的永远是一段数据流不是独立包。真实情况硬件连续发 3 包→ 系统底层合并→ 只给你回调 1 次→ 你一次性收到 **3 包粘在一起的数据**PBAP电话簿经典蓝牙系统层处理分包 →不用自己拆Protobuf.pb BLEBLE 限制 20B底层粘包 →必须自己循环拆包