蓝牙opp系列基本分析完了,但作者本身关于opp仍旧是留下了一些疑问,所以本文是在前文的基础上来分析作者关于opp的几个疑问,本文不计划大体量的贴出代码流程,而且源码流程在前文也已经贴出,读者可自行追踪。

本文也属于关于蓝牙opp源码分析的最后一篇

研究完了opp之后基本上有这么几个疑问是前文未能解答或者未能重点说明的,疑问有四:

  1. 疑问一,我们总说协议协议栈,那么协议究竟是什么?前文也说过opp的协议栈,这些都是理论上的东西,那么协议是真实存在的一个东西吗?我们所说的l2cap和obex到底是个什么东西?

  2. 疑问二,这个连接是如何建立的呢?关于这个连接的建立刚开始是觉得,不就是一个socket的建立吗?没什么特别的,但是很快就注意到这里的socket是BluetoothSocket,虽然和tcp的socket类似,但并不是tcp的socket。所以这个socket的建立到底是如何实现的呢?各个协议层的连接又是如何建立的呢?

  3. 疑问三,如果当前连接已经建立,那么再次发起建立连接请求(针对client)或者是再次接收到连接请求会怎么应对呢?

  4. 疑问四,连接建立成功之后收发数据的规则是怎样的,也就是说server端如何知道文本的数据何时开始又何时结束呢?

如果当你看完opp系列文章后也有这些疑问,可以先自行分析一下。接下来就来研究一下我的疑问。

协议是什么

首先来回答第一个问题,协议是什么。

在了解之前总觉得l2cap和obex是一种物理存在,但其实这只不过是为了便于管理把他们分了一下代码结构而已。

协议是由三个要素组成: 

(1) 语义。语义是解释控制信息每个部分的意义。它规定了需要发出何种控制信息,以及完成的动作与做出什么样的响应。

(2) 语法。语法是用户数据与控制信息的结构与格式,以及数据出现的顺序。

(3) 时序。时序是对事件发生顺序的详细说明。(也可称为“同步”)。 

人们形象地把这三个要素描述为:语义表示要做什么,语法表示要怎么做,时序表示做的顺序。

 

这是百度百科上对于协议这个词的解释,是什么意思呢?说白了,协议就相当于游戏制作者制定的一套游戏规则,一种每个玩家都必须遵守才能玩儿下去的规范

到这儿你大概能明白SIG(蓝牙技术联盟)存在的必要了吧,SIG需要制定一套规则来限制每个蓝牙开发人员,从而保证蓝牙设备的兼容性以及广泛使用性。

举个opp的例子来说的更详细点儿就是,SIG规定了如果要使用蓝牙opp的功能时首先要connect成功l2cap层,之后再连接obex层,方可开始发送数据。    

SIG规定client在建立连接时需要发送什么样的数据、每个数据代表什么意思、这些数据的排列顺序、以及何时去发送这样的数据,同样也规定了当server端设备接收到某些数据时如何解析这些数据、解析之后每个数据表示什么意思、对这些数据需要做什么样的处理和反应等等。

其实就相当于以前地下工作人员的收报员和发报员,一个加密者一个解密者。他们需要使用同一套密码本才能明白对方发送的数字比如12538代表什么意思。

这就是所谓的规则,这就是所谓的协议。蓝牙Opp收发文件也是如此,必须有一些约定俗成的规则,才能明白在干什么。要不然只能是对牛弹琴。也就是我们所谓的协议只不过是蓝牙的一些通信规则而已。

说到这儿就又生出一个疑问了,既然有密码本,那么这个密码本究竟长什么样子呢?现实中的密码本我是不清楚了,但是蓝牙opp的密码本确是可以研究的

猜测蓝牙opp的密码本里应该会规定以下规则

  • 两个设备的建立连接的规则,这就涉及到第二个疑问了,连接是如何建立的

  • 规定收发数据的规则,这就涉及到第四个疑问了,文件收发规则

 

新连接处理策略

关于第三个疑问,如果设备当前已经存在连接的话,那么有连接请求或者是需要再次发起连接请求会如何处理呢?

这个如果仔细看前两篇关于源码分析的博文的话会发现有这套逻辑。

该疑问分为client和server两个角色来分析。

server端:首先是server来说,关于当有新的连接请求时的应对逻辑BluetoothOppService方法中,在接受到连接请求并且l2cap校验成功(这就涉及到密码本的规则,下文分析)后会回调BLuetoothOppService的onConnect方法,该方法就是发送了一个给handler处理的消息MSG_INCOMING_BTOPP_CONNECTION,先来看一下代码中的处理策略。

跟我一起学OPP(六)---密码本_java

可以看到注释所描述的策略

  • 如果当前既没有文件传输ongoing transfer(这个transfer包括当前是否有发送和接收文件),也没有正在尝试创建session的操作on-hold connection(注意,这个onhold connection是指当前是否存在尝试创建session的连接,而不是当前是否有已经创建成功的session),则直接创建session(socket已经connect成功,所以直接创建obex会话窗口)

  • 如果当前有on-hold连接,也就是当前有正在尝试创建会话的connection,则直接决绝本次连接

  • 如果当前有ongoing transfer有正在发送文件,则重试20次每次间隔1s,也就是说20s内如果仍旧无法连接成功则放弃。

client端:那么当client端如果此时正在发送时,再次进行文件分享的话处理也是在BluetoothOppService来处理的,有一个mBatchs的数据结构来管理,按照顺序发送。

如此,client和server端当有连接进来时的处理就分析完了。

举个例子来说明一下。

有人可能会问那么当client此时正在发送文件时,接收到文件传输请求会怎么处理呢怎么没分析呢?当接收到文件传输请求那就表示作为server了,那此时就是遵循server端的策略了,因为目前设备正在发送文件也就是说处于ongoing transfer阶段,所以会重试发送请求20次,20s内当没有文件传输是就会去创建session了。

好,疑问解决了两个了,接下来看第二个疑问,连接的建立规则

连接建立规则

关于连接建立的源码流程早就已经分析过,而这里主要是看一下连接建立时的规则。

这个还是需要先分别来看一下server和client端。

Server:server端是开启一个线程调用socket的accept来监听,这个socket是Bluetooth自己的socket,所以什么条件下会accept成功就是这个所谓的规则了,来看一下socket的accept是如何实现一直阻塞直到有连接请求到来的。在accept中会调用一个waitSocketSignal方法来从inputStream输入流中读入远程蓝牙设备地址,而这个从输入流读入的操作本身就是一个阻塞的过程。也就是说accept会一直堵塞,直到读到他所需要的数据。既然重点是关注规则,那就来看下这个阻塞的过程究竟要读入哪些数据。

跟我一起学OPP(六)---密码本_java_02

从代码中可以看到server端的accept会堵塞的原因是读取inputstream在阻塞,可以看到,代码中共需要读取20个字节,而且每个字节代表什么意思都是很明确的

  • 1~2两个字节,表示目前输入流中数据的长度size

  • 3~8六个字节,表示远端设备(发送连接请求的蓝牙设备)的蓝牙地址address

  • 9~10两个字节,表示channeld的数值

  • 11~12两个字节,只有status为0时才能connection成功

  • 13~16四个字节,Tx,l2cap所支持发送的最大字节数

  • 17~20四个字节,Rx,L2cap所支持接收的最大字节数

很显然,accept所解析的就是20个特定的字节。那么也就是说client在进行connect时需要按照既定的规则来写入这20个字节,这就是钥匙。也就是说serversocket端accept阻塞是在等待client调用connect时写入20个特定字节。至此,server端的socket的连接验证完成。

Client:client就简单了,直接调用BluetoothSocket的connect方法,在connect方法中它会做两件事

  • 写入server所要读取的20个字节:connectSocket

  • 从server读取20个字节验证(和server所验证的20个字节一致)waitSocketSignal.

代码可以自己查看,也就是client端既要向server端的套接字写入20个字节,又要从server端的套接字读取20个字节,那就有个疑问了,server套接字的这20个字节是何时写入的呢?想着既然是在连接时就要读取,那就应该是连接前就有了的,所以又折回去看server开启listen的代码,发现确实有向socket写入20个字节,也就是在server端开启监听时写入的,具体代码流程不再追踪,在此贴出写入的规则:

跟我一起学OPP(六)---密码本_java_03

至于调用逻辑可以在下面两个文件中找到

跟我一起学OPP(六)---密码本_java_04

跟我一起学OPP(六)---密码本_java_05

总结一下连接规则就是:

  • server会在listen时向socket中写入特定的20个字节数据,并循环读取20个字节等待连接请求的到来

  • client通过BluetoothSocket的connect方法发起连接,发起连接所做的事情就是向server端写入20个字节的数据,并从server端读取20个字节的数据

所以,server的accept和client的connect之所以会阻塞耗时,是因为他们都需要从socket读取特定的20个字节的数据。说白了,蓝牙opp的socket连接从java层看其实就是读取20个字节并校验的一个过程。

文件传输规则

socket连接建立成功之后就是obex层的session建立了。这个文件传输规则说白了也就是obex层的协议规范,obex层的传输规定。

对于obex传输规则建议直接去找作为server端时的处理规则,为什么?因为server是被动处理方,也就是说server对于到来的请求只能是被动接收,要么是处理要么是不处理。所以从server端可以清晰的看出这个规则中会处理哪些请求以及针对这些请求都是怎么处理的。

本文是假设你已经看到前文关于opp源码分析的基础上分析,所以不贴步骤代码,直接来看server端的处理规则(ServerSession文件)

跟我一起学OPP(六)---密码本_java_06

 

这里的opcode就是client发过来的请求码,有点儿类似http请求。从代码中可以很明显的看到server端针对请求码有10中处理情况,也就是说obex是支持以下6个功能

  • connect(client端调用connect进行连接,server端回调onConnect)

  • disconnect(client端调用disconnect,server端回调onDisconnect)

  • put(client调用put发送文件,server端回调onPut进行接收文件处理)

  • get

  • setPath

  • abort

server端会从inputstream中读取请求码,针对各个请求码做相应的处理,并将处理结果(response code结果码)返回给client

  1. ObexHelper.OBEX_OPCODE_CONNECT(0x80):client请求创建obex层的session连接,当client的obex层调用connect时会发送连接请求码,server端调用handleConnectRequest进行处理

  2. ObexHelper.OBEX_OPCODE_DISCONNECT(0x81):client请求断开obex层的session连接。当client的obex层调用disconnect时会发送断开请求,server端调用handleDisconnectRequest.

  3. ObexHelper.OBEX_OPCODE_GET(0x03):client请求从server端pull数据(比如导入联系人)

  4. ObexHelper.OBEX_OPCODE_GET_FINAL(0x83):同上

  5. ObexHelper.OBEX_OPCODE_PUT(0x02):client请求向server端发送数据

  6. ObexHelper.OBEX_OPCODE_PUT_FINAL(0x82):同上

  7. ObexHelper.OBEX_OPCODE_SETPATH(0x85):暂时未注意,待以后研究更多profile的使用

  8. ObexHelper.OBEX_OPCODE_ABORT(0xFF):当连接出问题时需要abort断开连接

  9. -1:不做任何处理,也不返回给client任何response,直接关闭session

  10. 其他请求码:obex目前只支持以上9中请求码的处理,所以如果client有其他请求码,只能返回并告知目前obex还未实现对该请求码的支持,将OBEX_HTTP_NOT_IMPLEMENTED作为response code通知给client.

先拿其中一个请求码进行分析,其他请求码直接按照相同的分析方法进行分析即可。所以接下来就拿第一个ObexHelper.OBEX_OPCODE_CONNECT来分析。那就是handleConnectRequest方法了。该方法里详细记录了server接收到connect请求之后的处理,和socket的创建一致,两个设备校验只能是看数据传输上的校验了

  • Byte0:requestType请求码0x80

  • Byte1&2:Connect Packet Length,本次所发送的包的长度,Byte1存储高位,Byte2存储低位

  • Byte3: Obex Version Number(通常为0x10)

  • Byte4:Flags(For Tcp 0x00)

  • Byte5&6:Max Obex packet length :obex 所支持的传输包的最大字节长度

  • Byte7&n: headers可选

这个只能是看到server端在解析这几个字节,具体每个自己表示的什么意思,就需要去看下client发起connect时的说明,如上文。

server端会对接收到的字节进行处理并校验,在校验完成后会将对应的responseCode以及其他信息返回给client。而且也会触发mListener的onConnect方法来通知自身相应进行处理。

server会做如下校验

  • packet length是否大于obex支持的所接受的包的最大值,若大于,则responseCode为:ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE

  • 鉴权校验(HandleAuthResp),client如果有发送鉴权需求,那么server端需要按照一定的算法计算出一个password和client进行对比,看是否一致。即鉴权。如果鉴权失败,则responseCode为:ResponseCodes.OBEX_HTTP_UNAUTHORIZED

最终server会将如下数据反馈给client

跟我一起学OPP(六)---密码本_java_07

connect\disconnect\abort属于command命令类,分析方法大致类似。关于client发送文件,server的处理是在onPut具体可以自己查看代码分析。

好了,本文只是理出个大致的头绪,拎一个架子出来,接下来的路还是得自己走。如果想要知道接收文件的具体创建文件或者其他之类的可以根据画出来的路子继续走下去

 

https://mp.weixin.qq.com/s/Kla9bYeFYfaSW_n23w-irw