藍(lán)牙協(xié)議棧允許采用多種方法,包括 RFCOMM 和 Object Exchange(OBEX),在設(shè)備之間發(fā)送和接收文件。如果想發(fā)送和接收流數(shù)據(jù)(而且想采用傳統(tǒng)的串口應(yīng)用程序,并給它加上藍(lán)牙支持),那么 RFCOMM 更好。反過(guò)來(lái),如果想發(fā)送對(duì)象數(shù)據(jù)以及關(guān)于負(fù)載的上下文和元數(shù)據(jù),則 OBEX 最好。在這篇文章中,將熟悉用來(lái)控制藍(lán)牙設(shè)備的 Java 語(yǔ)言庫(kù),并學(xué)習(xí)如何使用 JSR-82 API 和 OBEX 在客戶機(jī)和服務(wù)器之間傳輸文件。
藍(lán)牙是多家移動(dòng)設(shè)備制造商選擇的無(wú)線協(xié)議,擁有多項(xiàng)吸引人的特性,最重要的是它在數(shù)據(jù)傳輸上的低能耗。截止 2004 年 6 月,每周交付的支持藍(lán)牙的設(shè)備超過(guò)五百萬(wàn)臺(tái),這項(xiàng)技術(shù)在消費(fèi)電子市場(chǎng)上擁有強(qiáng)大的滲透率。
藍(lán)牙協(xié)議回顧
目前市場(chǎng)上設(shè)備中運(yùn)行的藍(lán)牙協(xié)議有三個(gè)版本 —— 分別是版本 1.1、1.2 AFH 和 2.0+EDR。這沒(méi)給開(kāi)發(fā)人員帶來(lái)任何問(wèn)題,因?yàn)樾掳姹镜膮f(xié)議與以前的版本兼容。表 1 顯示了目前可用的藍(lán)牙版本的一些相似性與區(qū)別。
表 1. 藍(lán)牙協(xié)議版本
版本 |
原始數(shù)據(jù)速率 |
通信范圍(英尺) |
說(shuō)明 |
Bluetooth 1.1 |
1 Mbps |
30-300 |
市場(chǎng)上部署得最廣泛的藍(lán)牙版本。 |
Bluetooth 1.2 AFH |
1 Mbps |
30-300 |
包含高級(jí)頻率跳躍技術(shù),可以與 WiFi 網(wǎng)絡(luò)更好地并存。 |
Bluetooth 2.0+EDR |
3 Mbps |
30-300 |
包含增強(qiáng)數(shù)據(jù)速率技術(shù),可以用更高的速度傳輸數(shù)據(jù)。 |
現(xiàn)在,讓我們回顧一下控制藍(lán)牙設(shè)備的固件/軟件:棧。
藍(lán)牙協(xié)議棧
藍(lán)牙棧的目的是什么呢?棧是控制藍(lán)牙設(shè)備的軟件(和固件)。圖 1 顯示了協(xié)議棧的細(xì)節(jié)。
圖 1. 藍(lán)牙協(xié)議棧
- 棧的最底層是 HCI,即主機(jī)控制器接口(Host Controller Interface)。這一層顧名思義就是主機(jī)(計(jì)算機(jī))和控制器(藍(lán)牙設(shè)備)之間的接口??梢钥吹?,其他所有的層都要經(jīng)過(guò) HCI。
- HCI 上面的一層是 L2CAP,即邏輯鏈接控制器適配協(xié)議(Logical Link Controller Adaptation Protocol)。這一層充當(dāng)其他所有層的數(shù)據(jù)多路復(fù)用器。
- 接下來(lái)一層是 BNEP,即藍(lán)牙網(wǎng)絡(luò)封裝協(xié)議(Bluetooth Network Encapsulation Protocol)。使用 BNEP,可以在藍(lán)牙上運(yùn)行其他網(wǎng)絡(luò)協(xié)議,例如 IP、TCP 和 UDP。
- RFCOMM 稱作虛擬串口協(xié)議(virtual serial port protocol),因?yàn)樗试S藍(lán)牙設(shè)備模擬串口的功能。
- OBEX 協(xié)議層是在 RFCOMM 層上面實(shí)現(xiàn)的,如果想把數(shù)據(jù)以對(duì)象(例如文件)的形式傳輸,那么 OBEX 很有用。
- SDP 是服務(wù)發(fā)現(xiàn)協(xié)議(Service Discovery Protocol)層,用于在遠(yuǎn)程藍(lán)牙設(shè)備上尋找服務(wù)。
- 最后兩層是 AVCTP 和 AVDTP,用于藍(lán)牙上音頻和視頻的控制 和 發(fā)布。AVCTP 和 AVDTP 是藍(lán)牙協(xié)議中增加的相對(duì)較新的層;如果想控制媒體播放器的功能或者想以立體聲播放音頻流,則要使用它們。
發(fā)送文件:RFCOMM 還是 OBEX?
我們先來(lái)看看棧中用來(lái)發(fā)送數(shù)據(jù)的兩個(gè)簡(jiǎn)單協(xié)議 RFCOMM 和 OBEX,并比較使用它們傳送文件的優(yōu)勢(shì)和不足。
可以采用 RFCOMM 或 OBEX 在藍(lán)牙設(shè)備之間發(fā)送和接收文件。但是,如果想發(fā)送和接收流數(shù)據(jù),則 RFCOMM 是更好的選擇,就像使用傳統(tǒng)的串口一樣。在現(xiàn)實(shí)世界中,如果想使用傳統(tǒng)的串口應(yīng)用程序,并讓它能使用藍(lán)牙,就應(yīng)當(dāng)使用 RFCOMM。如果要在藍(lán)牙設(shè)備之間發(fā)送簡(jiǎn)單的文本字符串(例如在聊天應(yīng)用程序中),那么使用 OBEX 可能沒(méi)有太大優(yōu)勢(shì)。在這種情況下,應(yīng)當(dāng)使用 RFCOMM 或 L2CAP。
另一方面,如果想發(fā)送對(duì)象數(shù)據(jù)(例如文件),則 OBEX 最合適。使用 OBEX 不僅可以發(fā)送數(shù)據(jù),而且還能發(fā)送關(guān)于負(fù)載的上下文或元數(shù)據(jù)。例如,在使用 OBEX 發(fā)送文件時(shí),還能夠發(fā)送關(guān)于文件的其他有用信息,例如文件名稱、文件類型、文件尺寸或者其他任何對(duì)文件進(jìn)行描述的內(nèi)容。
那么,既然已經(jīng)決定了使用藍(lán)牙時(shí)通過(guò) OBEX 發(fā)送對(duì)象數(shù)據(jù)文件,那么我們來(lái)看看使用 Java 語(yǔ)言對(duì)藍(lán)牙設(shè)備進(jìn)行控制的官方庫(kù)。
回頁(yè)首
探索 JSR-82 API
JSR-82 是用于藍(lán)牙無(wú)線技術(shù)的官方 Java API。可使用這個(gè) API 創(chuàng)建可執(zhí)行以下功能的應(yīng)用程序:
- 判斷和檢測(cè)自己的藍(lán)牙設(shè)備的屬性
- 發(fā)現(xiàn)設(shè)備通信范圍內(nèi)的藍(lán)牙設(shè)備
- 在遠(yuǎn)程藍(lán)牙設(shè)備上搜索服務(wù)
- 創(chuàng)建可以與遠(yuǎn)程藍(lán)牙服務(wù)器通信的藍(lán)牙客戶機(jī)應(yīng)用程序
- 創(chuàng)建能夠?yàn)樗{(lán)牙客戶機(jī)的請(qǐng)求提供服務(wù)的藍(lán)牙服務(wù)器應(yīng)用程序
JSR-82 包含兩個(gè)包,即 javax.bluetooth
和 javax.obex
。您自己的藍(lán)牙設(shè)備由 javax.bluetooth.LocalDevice
類表示,所有的遠(yuǎn)程藍(lán)牙設(shè)備由 javax.bluetooth.RemoteDevice
類表示。
javax.bluetooth.DiscoveryAgent
類是個(gè)有幫助的類,它讓您可以發(fā)現(xiàn)附近的遠(yuǎn)程藍(lán)牙設(shè)備,并為區(qū)域內(nèi)的每個(gè)藍(lán)牙設(shè)備返回一個(gè) javax.bluetooth.RemoteDevice
。也可以使用 javax.bluetooth.DiscoveryAgent
在已經(jīng)發(fā)現(xiàn)的遠(yuǎn)程設(shè)備上搜索服務(wù)。
如果想在發(fā)生發(fā)現(xiàn)事件的時(shí)候得到通知,則需要實(shí)現(xiàn) javax.bluetooth.DiscoveryListener
接口的方法 。這一切聽(tīng)都來(lái)都很簡(jiǎn)單,是不是?但是,當(dāng)想要?jiǎng)?chuàng)建 OBEX 應(yīng)用程序的時(shí)候,會(huì)變得復(fù)雜一些。
回頁(yè)首
OBEX 的語(yǔ)義
也許您不知道,OBEX 并不是藍(lán)牙本身的協(xié)議,實(shí)際是由無(wú)線數(shù)據(jù)協(xié)會(huì)創(chuàng)建的。因?yàn)?OBEX 是已采納的 協(xié)議,所以 Java 藍(lán)牙 API 的作者決定為 OBEX 應(yīng)用程序單獨(dú)創(chuàng)建一個(gè)包;這樣,就可以使用 Java 創(chuàng)建出能夠在任何傳輸機(jī)制(例如紅外或 TCP/IP)而不僅僅是在藍(lán)牙上工作的 OBEX 應(yīng)用程序。
在使用藍(lán)牙 API 創(chuàng)建 OBEX 應(yīng)用程序時(shí),將使用 javax.obex
包中的一些類和接口。正如我在前面提到過(guò)的,藍(lán)牙協(xié)議棧中的 OBEX 層實(shí)際是面向藍(lán)牙設(shè)備間的文件傳輸而優(yōu)化的,所以就像傳統(tǒng)的 FTP 一樣,OBEX 應(yīng)用程序擁有像 GET
和 PUT
這樣的操作。
到底什么是 OBEX 操作?
OBEX 操作
當(dāng)客戶機(jī)和服務(wù)器在一個(gè) OBEX 會(huì)話內(nèi)通信時(shí),它們的交互叫做操作。對(duì)于每個(gè)從客戶機(jī)發(fā)出的操作,服務(wù)器給出一個(gè)響應(yīng),指明操作的狀態(tài)。要真正了解 OBEX 客戶機(jī)和服務(wù)器的操作和響應(yīng)的工作方式,請(qǐng)參見(jiàn)圖 2:
圖 2. OBEX 操作和響應(yīng)代碼
如果從電話向打印機(jī)發(fā)送文件,就像圖 2 中表示的那樣,需要什么呢?首先,在創(chuàng)建 OBEX 會(huì)話之前,先要建立一個(gè)傳輸?shù)倪B接(藍(lán)牙、紅外、TCP/IP 等等)。因?yàn)檫@篇文章是關(guān)于藍(lán)牙的,所以毫不奇怪我要使用的底層傳輸機(jī)制是藍(lán)牙。
在傳輸連接已經(jīng)建立之后,藍(lán)牙客戶機(jī)需要發(fā)出 CONNECT
操作。如果藍(lán)牙服務(wù)器(在這個(gè)示例中,是打印機(jī))想接受新客戶機(jī)來(lái)使用它的服務(wù)(例如打印服務(wù)),那么它就用表示成功的響應(yīng)代碼 160 對(duì)客戶機(jī)進(jìn)行響應(yīng)。如果打印機(jī)不想接受新客戶機(jī),那么它可能用響應(yīng)代碼 211 來(lái)響應(yīng),這個(gè)代碼代表 “OBEX SERVICE UNAVAILABLE”。
現(xiàn)在假設(shè)打印機(jī)接受了 CONNECT
操作,那么現(xiàn)在就在客戶機(jī)和服務(wù)器之間創(chuàng)建了一個(gè) OBEX 會(huì)話。現(xiàn)在有了 OBEX 會(huì)話,就能夠向 OBEX 服務(wù)器發(fā)送請(qǐng)求(請(qǐng)注意,請(qǐng)求和操作是同義的)。當(dāng)然,GET
和 PUT
操作是自解釋的。但是,SETPATH
操作主要是在 OBEX 服務(wù)器具有文件系統(tǒng)時(shí)才使用。OBEX 客戶機(jī)發(fā)送 SETPATH
請(qǐng)求以指導(dǎo) OBEX 客戶機(jī)改變工作目錄(類似于 cd
命令)。SETPATH
操作后面通常跟著一個(gè) GET
或 PUT
操作。要終止 OBEX 會(huì)話,客戶機(jī)需要發(fā)送 DISCONNECT
操作。如果成功,OBEX 服務(wù)器會(huì)用成功響應(yīng)代碼 160 進(jìn)行響應(yīng)。
現(xiàn)在對(duì)于 OBEX 的語(yǔ)義有了良好的理解,讓我們看看在 JSR-82 API 中,Java 語(yǔ)言、藍(lán)牙和 OBEX 是如何放在一起的。我先從服務(wù)器代碼開(kāi)始,因?yàn)榭蛻魴C(jī)代碼更復(fù)雜。
回頁(yè)首
創(chuàng)建 OBEX 服務(wù)器應(yīng)用程序
我的服務(wù)器應(yīng)用程序叫做 FileServer.java
,而且因?yàn)?“重要事情優(yōu)先”,所以我們先來(lái)看清單 1 中的 import 語(yǔ)句和類聲明。
清單 1. import 語(yǔ)句和類聲明
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.microedition.io.*;
import java.io.*;
import javax.bluetooth.*;
import javax.obex.*;
public class FileServer extends ServerRequestHandler
implements ActionListener{
|
如果想創(chuàng)建 OBEX 服務(wù)器,就需要擴(kuò)展 javax.obex.ServerRequestHandler
類。因?yàn)檫@個(gè)示例是 J2SE 應(yīng)用程序,所以我還想實(shí)現(xiàn) ActionListener
接口,這樣就能響應(yīng)按鈕點(diǎn)擊。當(dāng)用戶點(diǎn)擊啟動(dòng)服務(wù)器的按鈕時(shí),就會(huì)調(diào)用 actionPerformed()
方法(如清單 2 所示)。
清單 2. FilesServer.actionPerformed()
public void actionPerformed(ActionEvent e) {
startButton.setEnabled(false);
try {
UUID uuid = new UUID("8841", true);
String url = "btgoep://localhost:" + uuid
+ ";name=FTP;authenticate=false;master=false;encrypt=false";
SessionNotifier sn = (SessionNotifier)Connector.open(url);
updateStatus("[server:] Now waiting for a client to connect");
sn.acceptAndOpen(this);
updateStatus("[server:] A client is now connected");
} catch (Exception ex){
}
}
|
機(jī)器上的每臺(tái)藍(lán)牙設(shè)備都需要惟一的標(biāo)識(shí)符,所以我決定將這個(gè)服務(wù)的 UUID (統(tǒng)一惟一標(biāo)識(shí)符)定為 8841 (可以是任意四位數(shù)字)。在創(chuàng)建客戶機(jī)應(yīng)用程序時(shí)需要記住這個(gè) UUID。
因?yàn)檎趧?chuàng)建 JSR-82 服務(wù)器應(yīng)用程序,所以需要調(diào)用 Connector.open()
并傳遞進(jìn) String
,其中包含服務(wù)的 URL。因?yàn)檫@是一個(gè) OBEX 應(yīng)用程序,所以采用的協(xié)議是 btgoep
。而且,也可以看到服務(wù)被命名為 “FTP”(也可以取其他的名稱)。
FileServer.java
是一個(gè) OBEX 服務(wù)器應(yīng)用程序,這意味著 OBEX 客戶機(jī)將發(fā)送 OBEX 操作,例如 CONNECT
、GET
、PUT
等等。所以,類需要恰當(dāng)?shù)靥幚砜蛻魴C(jī)將要發(fā)送的每個(gè)操作(或者我想要處理的操作)。所以,在 FileServer.java
中,我包含了 onPut()
、onConnect()
和 onDisconnect()
方法的實(shí)現(xiàn)。我可能還創(chuàng)建了 onGet()
的實(shí)現(xiàn),但是因?yàn)橹幌霃目蛻魴C(jī)向服務(wù)器發(fā)送簡(jiǎn)單文件,所以實(shí)現(xiàn) onGet()
并不是必需的,而只需要實(shí)現(xiàn) onPut()
方法。
清單 3 演示了 onConnect()
和 onDisconnect()
的實(shí)現(xiàn)??梢钥吹?,onConnect()
要求一個(gè) int
返回值,但是 onDisconnect()
不返回內(nèi)容,因?yàn)椴恍枰?
清單 3. FilesServer.onConnect() 和 FilesServer.onDisconnect()
public int onConnect(HeaderSet request, HeaderSet reply) {
updateStatus("[server:] The client has created an OBEX session");
return ResponseCodes.OBEX_HTTP_OK;
}
public void onDisconnect (HeaderSet req, HeaderSet resp) {
updateStatus("[server:] The client has disconnected the OBEX session");
}
|
現(xiàn)在來(lái)看允許 FileServer.java
在客戶機(jī)發(fā)送 PUT
請(qǐng)求時(shí)從客戶機(jī)接收文件的代碼段。onPut()
的代碼如清單 4 所示。
清單 4. FilesServer.onPut()
public int onPut (Operation op) {
try {
java.io.InputStream is = op.openInputStream();
updateStatus("Got data bytes " + is.available() + " name "
+ op.getReceivedHeaders().getHeader(HeaderSet.NAME) +
" type " + op.getType());
File f =
new File((String)op.getReceivedHeaders().getHeader(HeaderSet.NAME));
FileOutputStream fos = new FileOutputStream (f);
byte b[] = new byte[1000];
int len;
while (is.available() > 0 && (len = is.read(b)) > 0) {
fos.write (b, 0, len);
}
fos.close();
updateStatus("[server:] Wrote data to " + f.getAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
}
return ResponseCodes.OBEX_HTTP_OK;
}
|
正如我前面說(shuō)的,清單 4 的代碼顯示了 OBEX 服務(wù)器如何從遠(yuǎn)程藍(lán)牙設(shè)備接收文件。您可能會(huì)注意到可以得到傳輸?shù)奈募拿Q,在這個(gè)示例中,在實(shí)例化 File
對(duì)象時(shí)使用到了文件名稱。
回頁(yè)首
結(jié)束語(yǔ)
在這篇文章中,我介紹了開(kāi)始創(chuàng)建使用 OBEX 協(xié)議的 Java 藍(lán)牙應(yīng)用程序的許多基礎(chǔ)知識(shí)。您學(xué)習(xí)了對(duì)于數(shù)據(jù)傳輸什么時(shí)候選擇 OBEX 比 RFCOMM 更合適。閱讀完這篇文章時(shí),您應(yīng)當(dāng)很好地掌握了 OBEX 應(yīng)用程序需要的語(yǔ)義的知識(shí)。
在這個(gè)系列的第 2 部分中,我將介紹如何創(chuàng)建能夠與這里創(chuàng)建的服務(wù)器應(yīng)用程序正確通信的客戶機(jī)應(yīng)用程序。當(dāng)您很好地理解了客戶機(jī)代碼之后,我將對(duì)客戶機(jī)代碼稍做修改,創(chuàng)建一個(gè)藍(lán)牙音樂(lè)商店。
參考資料
學(xué)習(xí)
- 您可以參閱本文在 developerWorks 全球站點(diǎn)上的 英文原文。
- Open OBEX Project 正在規(guī)劃 Object Exchange 協(xié)議的開(kāi)放源碼實(shí)現(xiàn),它是一個(gè)會(huì)話協(xié)議,用二進(jìn)制 HTTP 協(xié)議描述最合適。
- “PalmOS data collection series”(developerWorks,2001 年 11 月)探索了為無(wú)線設(shè)備開(kāi)發(fā)數(shù)據(jù)搜集應(yīng)用程序的關(guān)鍵部分。
獲得產(chǎn)品和技術(shù)
關(guān)于作者
Bruce Hopkins 是 Bluetooth for Java (Apress)的作者,也是 JB-22 開(kāi)發(fā)包的創(chuàng)建者。他畢業(yè)于底特律的 Wayne 州立大學(xué),擁有電子和計(jì)算機(jī)工程學(xué)士學(xué)位。他目前是 Gestalt LLC 的技術(shù)架構(gòu)師,專攻分布式計(jì)算、Web 服務(wù)和無(wú)線技術(shù)。您可以通過(guò)他的電子郵件 bhopkins@gestalt-llc.com 與他聯(lián)系。