1. RFCOMM
先來看看RFCOMM在協議棧層次體系中的位置。從下圖可以看出RFCOMM處於傳輸層。與AVCTP,TCS-BIN處於同一層次。處於其上層的會話層中的OBEX,SPP等大部分協議通常都采用RFCOMM作為傳輸協議。因此RFCOMM傳輸協議在藍牙協議棧中占據重要一席。

RFCOMM提供了基於L2CAP協議的串行(9針RS-232)模擬,支持在兩個藍牙設備間高達60路的通信連接。
1.1 RFCOMM使用示例——SPP
SPP(Serial Port Profile)定義了一系列協議和過程,藍牙設備通過該協議實現RS232串行線纜的仿真。很多老式設備都工作在串行模塊下面,使用SPP協議可以幫組連接這些老式設備。先來卡納看SPP在藍牙協議體系里面的位置:

從上圖可以看出,SPP協議是大部分應用層協議都采用的會話層協議,常用的GOBEX以及HSP都采用了SPP協議。再來看看SPP協議所處的層次以及服務模型:

SPP按角色分為DevA和DevB,其中DevA是連接的發起者(initiator),DevB是等待連接的到來。從應用層角度看,SPP涉及到三個主要的過程:建立鏈路並設置虛擬串口連接、接收鏈路和建立虛擬串口連接、本地SDP數據庫注冊服務記錄,其中第一項對DevA來說是強制實現的,后兩項對DevB是強制實現的。
1.1.1 核心過程
1、建立鏈路並設置虛擬串口連接
該過程包括以下步驟:
1. Submit a query using SDP to find out the RFCOMM Server channel number of the desired application in the remote device. This might include a browsing capability to let the user select among available ports (or services) in the peer device. Or , if it is known exactly which service to contact, it is sufficient look up the necessary parameters using the Service Class ID associated with the desired service.
2. Optionally, require authentication of the remote device to be performed. Also optionally, require encryption to be turned on.
3. Request a new L2CAP channel to the remote RFCOMM entity.
4. Initiate an RFCOMM session on the L2CAP channel.
5. Start a new data link connection on the RFCOMM session, using the aforementioned server channel number.
After step 5, the virtual serial cable connection is ready to be used for communication between applications on both sides.
Note: If there already exists an RFCOMM session between the devices when setting up a new data link connection, the new connection must be established
on the existing RFCOMM.
2、接收鏈路和建立虛擬串口連接
該過程主要包括以下步驟:
1. If requested by the remote device, take part in authentication procedure and, upon further request, turn on encryption.
2. Accept a new channel establishment indication from L2CAP.
3. Accept an RFCOMM session establishment on that channel.
4. Accept a new data link connection on the RFCOMM session. This may trigger a local request to authenticate the remote device and turn on encryption, if the user has required that for the emulated serial port being connected to (and authentication/encryption procedures have not already been carried out ).
Note: steps 1 and 4 may be experienced as isolated events when there already exists an RFCOMM session to the remote device.
3、本地SDP數據庫注冊服務記錄
該過程涉及到向本地SDP數據庫為虛擬串口注冊一條服務記錄。這也意味着服務數據庫的存在,以及對SDP查詢的支持。
1.1.2 連接消息序列
先來看看DevB的初始化流程圖。上層應用通過調用SppStartService()開始注冊和初始化SPP服務,其中涉及到RFCOMM通道注冊,SDP服務搜索等交互過程,在上層應用收到SPP_START_SERVICE_CFM表示服務初始化完成,下一步是等待DevA的連接到來。

再來看看典型的連接過程涉及到的消息交互流程圖。DevA通過調用SppConnectRequest ()開啟連接過程,DevB在接收到SPP_CONNECT_IND消息時決定是否接受該連接,並作出響應SppConnectResponse()。在DevA收到SPP_CLIENT_CONNECT_CFM,DevB收到SPP_SERVER_CONNECT_CFM后,表示SPP通信會話正式建立。

在走讀ADK代碼時候,你會發現一個奇怪的現象——通信模塊(如L2CAP,RFCOMM,SPP等)里找不到數據收發的接口,傳統的通信模塊,比如SOCKET通信例程中,當通信鏈路建立后,會提供read/write,read_from/write_to等之類的顯示讀寫函數幫助收發數據。這是因為ADK提供了流的機制,引入sink/source/transform概念。這樣,當一條L2CAP/RFCOMM數據鏈路建立的同時會創建對應的sink實體,用於收發數據。Sink實體在有數據達到或者數據可以接受狀態時向已注冊任務(task)發送MESSAGE_MORE_DATA/MESSAGE_MORE_SPACE消息。以SPP為例,在DevA收到SPP_CLIENT_CONNECT_CFM,DevB收到SPP_SERVER_CONNECT_CFM后,例程將會保存已創建的sink實體,並進行配置。
Sink sink = (Sink) StreamRfcommSink(cfm->conn_id); /*從RFCOMM會話ID獲取sink引用*/
SourceConfigure(StreamSourceFromSink(sink), VM_SOURCE_MESSAGES, VM_MESSAGES_ALL);
其實真正的sink實體創建於L2CAP鏈路創建成功的時候??這一點還只是猜測。
void connectionHandleL2capConnectCfm(const L2CA_AUTO_CONNECT_CFM_T *cfm){
Sink sink = StreamL2capSink(cfm->cid);
MessageSinkTask(sink, appTask); /* Associate the task with its sink */
}
目前還不清楚sink實體是何時創建的,總之,在SPP建立連接后,就擁有了一個數據收發的sink實體,通過該實體,配合BlueCore發送過來的MESSAGE_MORE_DATA和MESSAGE_MORE_SPACE等消息進行數據傳輸。
1.1.3 消息處理
SPP庫例程中,DevA(Client)定義了一個回調函數——sppcConnectionHandler,用來接收下層的消息,並進行處理和轉發。DevB(Server)定義了兩個回調函數,一個用來處理連接時候的消息,一個用於處理服務相關(如SDP查詢等)的消息。
spp->c.task.handler = sppcConnectionHandler;
spp->c.client_task = theAppTask;
TaskData sppsServiceTask = { sppServiceHandler };
spp->c.client_task = theAppTask;
SppConnectResponse()->
spp->c.task.handler= sppsConnectionHandler;
1.1.4 多串口仿真
兩個使用RFCOMM通信的藍牙設備可以同時打開多個串口仿真 ,RFCOMM支持多大60路,但是一個設備實際能打開的數據依實現而定。
ADK例程中怎么沒有發現多串口復用相關的內容呢???
