一、蓝牙连接数据包

BLE 连接过程中有三个重要的数据包:SCAN_REQ, SCAN_RSP 和 CONNECT_REQ。

  • SCAN_REQ:
    扫描请求,由主设备(MASTER DEVICE)向从设备(SLAVE DEVICE)发出,目的是为了获得从设备的响应以得到更多的从设备广播数据信息(包括设备名字,或者服务UUID,及其它如厂家特定格式的信息(如硬件版本,软件版本号,设备系列号等等)。
  • SCAN_RSP:
    从设备对就主设备发起的SCAN_REQ的响应,作为广播包的补充,从设备可以给主设备更多的广播数据,比如说,有些设备在广播包里面没有设备名字,这个时候就可以把设备名字放在这个包里面发给主设备。
  • CONNECT_REQ:
    主设备向从设备发出连接请求。至此连接建立完成(从设备不会响应这个请求),如果从设备没有连接上面的问题的话,以后主从双方会开始相互交换有效数据(基于GAP,GATTSMP协议)或者交换空包。

更多内容参见一分钟读懂低功耗蓝牙(BLE)连接数据包


二、什么是MTU

MTUMAXIMUM TRANSMISSION UNIT)最大传输单元,指在一个PDUProtocol Data Unit:协议数据单元,在一个单元中的有效传输数据)能够传输的最大数据量(多少字节可以一次传输给对方)。


三、MTU交换的意义(设置reqeustMtu的意义)

MTU交换是为了在主从双方设置一个PDU中最大能够交换的数据量,通过MTU的交换和双方确认(注意这个MTU是不可以协商的,只是通知对方,双方在知道对方的极限后会选择一个较小的值作为以后的MTU

比如说,主设备发出一个150个字节MTU请求,但是从设备回应MTU23字节,那么今后双方要以较小的值23字节作为以后的MTU),主从双方约定每次在做数据传输时不超过这个最大数据单元,MTU交换通常发生在主从双方建立连接关系后。

更多内容移步一分钟读懂低功耗蓝牙(BLE)MTU交换数据包

四、如何妥善处理数据包大小(MTU)限制

Android系统从4.3(API 18)开始支持BLE,且从5.1(API 21)才开始支持MTU修改(默认MTU仅为23字节,而且传输本身用掉3字节)。

1.为什么BLE默认限制数据传输长度为20个字节?

core spec里面定义了ATT的默认MTU23个bytes,除去ATTopcode一个字节以及ATThandle 2个字节之后,剩下的20个字节便是留给GATT的了

考虑到有些Bluetooth smart设备功能弱小,不敢太奢侈的使用内存空间,因此core spec规定每一个设备都必须支持MTU23

在两个设备连接初期,大家都像新交的朋友一样,不知对方底细,因此严格的按照套路来走,即最多一次发20个字节,是最保险的。

由于ATT的最大长度为512byte,因此一般认为MTU的最大长度为512个byte就够了,再大也没什么意义,你不可能发一个超过512ATT的数据,就像是孙猴子跑不过五行山一样。

所以ATTMTU的最大长度可视为512个bytes

2.如何突破MTU 20字节的限制?

当我们需要传输 / 接收超过20字节长度的数据时,就需要在连接设备成功后通过BluetoothGatt#requestMtu(int mtu)方法进行对应的请求设置,示例代码如下:

private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @Override
        public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
            super.onMtuChanged(gatt, mtu, status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                LogUtil.e(TAG, "request mtu success.约定后的MTU值为:" + mtu);
            } else {
                LogUtil.e(TAG, "request mtu failed.");
            }
        }

        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {//蓝牙连接成功
                gatt.requestMtu(39);
            }
        }
}

注意:上述代码的onMtuChanged可以发挥关键作用。MTU默认取的是23,当收到onMtuChanged后,会根据传递的值修改MTU,注意由于传输用掉3个字节,因此传递的值需要减3。

3.为什么专门记录MTU

记录MTU主要是用于传输过程中判断数据是否需要分包发送。按照MTU的大小严格约束每次发送的数据包大小,如果不这么做,很可能远端接收就会出错。除非你的数据包大小本身就很小。

4.设置完MTU后无法在BluetoothGattCallback#onConnectionStateChange方法中立即发现设备服务列表

当我们设置完MTU后,发现在BluetoothGattCallback#onConnectionStateChange方法中调用BluetoothGatt.discoverServices()无法立即触发BluetoothGattCallback#onServicesDiscovered回调。
我之前尝试过通过延迟调用BluetoothGatt#discoverServices()方法(经测大概需要延迟1000ms-1200ms)后才能够正常的获取到服务列表及发现相关特征,后来发现正确的解决方法应该是在BluetoothGattCallback#onMtuChanged回调中调用BluetoothGatt#discoverServices()方法即可解决问题。


#【本内内容参考自以下文章,侵删】


#【写在最后】

经测,iOS9.0以后能够正常的处理数据包过长的问题,CoreBluetooth针对长数据已经内部做出了处理,不需要额外的多次调用getValue方法,详见BLE:使用Android / iOS读取长特征值(BLE: Read Long Characteristics Value using Android / iOS)