基于藍(lán)牙的手機(jī)文件傳輸軟件
1 藍(lán)牙通信的關(guān)鍵技術(shù)
藍(lán)牙無(wú)線電技術(shù)基于在工業(yè)、科學(xué)以及醫(yī)學(xué)(ISM)上公用的2.45GHz 開(kāi)放頻段,這一頻段無(wú)需授權(quán)并全球通用。當(dāng)藍(lán)牙設(shè)備互相連接時(shí),他們將組成一個(gè)微微網(wǎng)(piconet),即以一個(gè)主設(shè)備和最大7 個(gè)從設(shè)備的形式動(dòng)態(tài)創(chuàng)建網(wǎng)絡(luò)。其私有化和個(gè)性化特征表現(xiàn)得尤為突出。
1.1 藍(lán)牙協(xié)議棧
藍(lán)牙協(xié)議棧提供了一組的高層協(xié)議和API 以完成發(fā)現(xiàn)服務(wù)和模擬串行I/O,還有一個(gè)關(guān)于包分割和重組的低層協(xié)議以及多路技術(shù)協(xié)議和質(zhì)量服務(wù)。藍(lán)牙協(xié)議棧分為硬件和軟件兩部分,藍(lán)牙硬件協(xié)議棧由設(shè)備硬件提供,藍(lán)牙軟件協(xié)議棧則由軟件實(shí)現(xiàn)。
藍(lán)牙軟件協(xié)議棧是程序開(kāi)發(fā)中的關(guān)鍵部分,其層次從下至上依次是: 宿主控制器接口(HostController Interface,HCI) 是藍(lán)牙軟件協(xié)議棧的最底層,直接和宿主控制器接口固件(Host ControllerInterface FIRmware)交互。邏輯鏈路控制和適配協(xié)議(Logical Link Control and Adaptation Protocol,L2CAP) 該層負(fù)責(zé)處理包分割重組,為上層協(xié)議提供了有保證的服務(wù)。服務(wù)發(fā)現(xiàn)協(xié)議(ServiceDiscovery Protocol,SDP)包含用于發(fā)現(xiàn)服務(wù)是否有效等操作。RFCOMM 位于L2CAP 之上,提供了模擬標(biāo)準(zhǔn)串口通信的能力。對(duì)象交換協(xié)議(Object Exchange Protocol,OBEX)用于實(shí)際程序中的對(duì)象數(shù)據(jù)交換。
圖1 藍(lán)牙協(xié)議棧
1.2 J2ME 對(duì)藍(lán)牙的支持
早在JSR82 規(guī)范中就定義了javax.bluetooth 和javax.obex 兩個(gè)包,其中javax.bluetooth 定義了與藍(lán)牙通信相關(guān)的API , 而javax.obex(Object ExchangeProtocol)是建立在串口通信之上,實(shí)現(xiàn)以對(duì)象為單位的通信。在javax.bluetooth 中,Java 藍(lán)牙API 可以被分解為三個(gè)部分:發(fā)現(xiàn)服務(wù)、設(shè)備管理和藍(lán)牙通信,其主要類及接口有:本地藍(lán)牙管理器LocalDevice、遠(yuǎn)程藍(lán)牙設(shè)備RemoteDevice、搜索代理DiscoveryAgent、搜索偵聽(tīng)DiscoveryListener、描述藍(lán)牙服務(wù)的特征屬性ServiceRecord 及藍(lán)牙服務(wù)屬性的類型DataElement.
1.3 J2ME 平臺(tái)下藍(lán)牙通信流程
圖2 藍(lán)牙通信流程圖
藍(lán)牙通信也是基于通用連接框架,與常見(jiàn)的C/S架構(gòu)類似,只是客戶端不知服務(wù)端的存在,需要通過(guò)無(wú)線搜索去發(fā)現(xiàn)。搜索到遠(yuǎn)程設(shè)備后,還需要進(jìn)行服務(wù)搜索去發(fā)現(xiàn)對(duì)方提供了哪些服務(wù)。
其中,藍(lán)牙通信是基于通用連接框架,對(duì)不同客戶端而言,需要通過(guò)搜索來(lái)獲得與服務(wù)端的連接信息。
藍(lán)牙服務(wù)端使用連接通知者對(duì)象,用于等待遠(yuǎn)程設(shè)備的連接,類似于阻塞式socket 服務(wù)端,它將一直等待直到接收到客戶端的連接請(qǐng)求。對(duì)于藍(lán)牙客戶端的搜索服務(wù)分為設(shè)備搜索和服務(wù)搜索,后者需要基于指定的遠(yuǎn)程設(shè)備才能進(jìn)行??蛻舳撕头?wù)器端在獲得藍(lán)牙協(xié)議連接后,通過(guò)連接創(chuàng)建輸入/輸出流來(lái)進(jìn)行通信。
2 手機(jī)文件傳輸軟件的實(shí)現(xiàn)
2.1 藍(lán)牙服務(wù)端的實(shí)現(xiàn)
2.1.1 獲得本地設(shè)備管理器
獲得本地設(shè)備管理器會(huì)導(dǎo)致系統(tǒng)提示是否需要啟動(dòng)藍(lán)牙服務(wù),該步驟是藍(lán)牙設(shè)備通信最基本的初始化。
通過(guò)LocalDevice 類的getLocalDevice 方法即可獲取本地設(shè)備管理器。
try {
localDevice = LocalDevice.getLocalDevice();
} catch (BluetoothStateException init) {
init.printStackTrace();
}
{$page$}
2.1.2 生成連接字符串
藍(lán)牙通信協(xié)議的連接字符串有兩種:一種用于串口通信;一種用于藍(lán)牙鏈路通信(L2CAPConnection)。
其中串口通信的連接字符串格式為:
btspp://hostname:[CN|UUID];parAMEters.完整的藍(lán)牙通信鏈接字符串的構(gòu)造代碼如下:
StringBuffer url = new StringBuffer("btspp://");
url.append("localhost")。append(':');
url.append(BTConfigure.FILES_SERVER_UUID.toString() ); //服務(wù)UUID
url.append(";name=FileServer"); //服務(wù)名稱
url.append(";authorize=false"); //安全參數(shù)
2.1.3 通過(guò)連接字符串獲得連接通知者(Notifier)
連接通知者類似阻塞式套接字的偵聽(tīng)過(guò)程。該對(duì)象只有在接收到遠(yuǎn)程設(shè)備請(qǐng)求時(shí)才會(huì)返回與該遠(yuǎn)程設(shè)備的連接,否則一直等待下去。
StreamConnectionNotifier notifier = null;
try { notifier = (StreamConnectionNotifier)
Connector.open(url.toString()); //獲取流連接通知者
… … }
2.1.4 設(shè)置本地設(shè)備的服務(wù)記錄屬性
服務(wù)器向外界"暴露"本設(shè)備可提供的服務(wù)信息,客戶端才可能獲取到這些服務(wù),并向服務(wù)端提出請(qǐng)求。
以下代碼描述了服務(wù)端如何設(shè)置本地設(shè)備的服務(wù)記錄。
//獲取服務(wù)記錄(用于添加和編輯服務(wù)記錄)
record = localDevice.getRecord(notifier);
//設(shè)置服務(wù)記錄屬性(文件名)
DataElement fileName = new
DataElement(DataElement.STRING, FILE_NAME);
record.setAttributeValue(BTConfigure.FILES_NAMES_ATTR_ID, fileName);
2.1.5 通過(guò)Notifier 循環(huán)阻塞等待遠(yuǎn)程設(shè)備的連接
當(dāng)有遠(yuǎn)程設(shè)備進(jìn)行連接后,通過(guò)連接通知者獲得連接對(duì)象。當(dāng)接收到遠(yuǎn)程客戶端設(shè)備的連接請(qǐng)求,首先獲取到該客戶端設(shè)備的地址信息,再啟動(dòng)服務(wù)線程進(jìn)行連接的處理。這樣對(duì)每一個(gè)客戶端連接啟用一個(gè)處理線程,可以避免多個(gè)客戶端排隊(duì)訪問(wèn)服務(wù)端的情形。
while(true) {
StreamConnection conn = null;
try { //等待接受客戶端的連接
conn = notifier.acceptAndOpen();
} catch (IOException e)
{ e.printStackTrace(); continue; }
RemoteDevice remoteDevice = RemoteDevice.getRemoteDevice(conn); //獲取遠(yuǎn)程設(shè)備
//啟動(dòng)服務(wù)線程
new BTServerThread(mainPanel, conn)。start();
}
2.1.6 服務(wù)端和客戶端的通信
藍(lán)牙服務(wù)端通信線程是整個(gè)服務(wù)端程序的核心。
每接收到一個(gè)客戶端的請(qǐng)求,都將創(chuàng)建一個(gè)獨(dú)立的線程來(lái)進(jìn)行處理。通過(guò)連接對(duì)象創(chuàng)建輸入/輸出流,實(shí)現(xiàn)服務(wù)端和客戶端的通信。當(dāng)某一客戶端處理完畢,服務(wù)端將關(guān)閉與該客戶端的連接。
public void run() {
String fileName=readFileName(); //獲取請(qǐng)求
sendFile(fileName); //發(fā)送答復(fù)
uninit(); //關(guān)閉連接
}
//從客戶端讀取文件名
private String readFileName() {
String fileName = null;
try { InputStream is = conn.openInputStream();
int length = is.read();
byte [] buffer = new byte[length];
is.read(buffer, 0, length);
fileName = new String(buffer); is.close();
} catch (IOException e) {
e.printStackTrace(); }
return (fileName); }
//發(fā)送文件數(shù)據(jù)
private void sendFile(final String fileName) {
InputStream is = null; byte [] buffer = null;
try {
is = getClass()。getResourceAsStream("/" +fileName); buffer = new byte[is.available() ];
is.read(buffer); is.close();
OutputStream os = conn.openOutputStream();
os.write(buffer.length 》 8);
os.write(buffer.length & 0xFF);
os.write(buffer); os.flush(); os.close();
} catch (IOException e) { e.printStackTrace(); }
}
//釋放連接流
private void uninit() {
try { conn.close(); } catch (IOException e)
{ e.printStackTrace(); }
}
2.2 藍(lán)牙客戶端的實(shí)現(xiàn)
2.2.1 獲得本地設(shè)備管理器
獲得本地設(shè)備管理器,設(shè)置本地設(shè)備管理器的搜索模式,獲取搜索代理實(shí)例,開(kāi)始搜索遠(yuǎn)程設(shè)備。
public void run() { //設(shè)備搜索線程核心
try {
LocalDevice localDevice = LocalDevice. getLocal
Device(); //獲取本地設(shè)備實(shí)例
localDevice.setDiscoverable(DiscoveryAgent.GIAC);
//得到搜索代理
discoveryAgent = localDevice.getDiscoveryAgent();
startDiscover(); //開(kāi)始探索
} catch (BluetoothStateException init) {
init.printStackTrace(); } }
public void startDiscover() { //開(kāi)始搜索
discoveryDevices.removeAllElements();
try {
discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this); } catch (BluetoothStateException e)
{ e.printStackTrace(); } }
{$page$}
2.2.2 記錄設(shè)備搜索結(jié)果
在搜索偵聽(tīng)的設(shè)備搜索事件中記錄設(shè)備搜索結(jié)果。設(shè)備搜索事件是通過(guò)實(shí)現(xiàn)搜索偵聽(tīng)(DiscoveryListener)的接口來(lái)完成回調(diào)處理。
(DiscoveryListener)的接口來(lái)完成回調(diào)處理。
//搜索到設(shè)備
public void deviceDiscovered(RemoteDevice remoteDevice, DeviceClass deviceClass) //添加遠(yuǎn)程設(shè)備
{ discoveryDevices.addElement(remoteDevice); }
//設(shè)備搜索結(jié)束
public void inquiryCompleted(int discType) {
switch(discType) {
case //查詢正常結(jié)束
DiscoveryListener.INQUIRY_COMPLETED:{
mainPanel.setDevices(discoveryDevices);
break; }
case //查詢被取消
DiscoveryListener.INQUIRY_TERMINATED:
break;
case //查詢錯(cuò)誤
DiscoveryListener.INQUIRY_error: {
mainPanel.showMsg("Inquiry error!!");
break; } } }
2.2.3 記錄服務(wù)搜索結(jié)果
對(duì)指定的遠(yuǎn)程設(shè)備搜索其服務(wù),在搜索偵聽(tīng)的服務(wù)搜索事件中記錄服務(wù)搜索結(jié)果。服務(wù)搜索事件的處理也是通過(guò)實(shí)現(xiàn)搜索偵聽(tīng)接口來(lái)完成回調(diào)的。
// 服務(wù)搜索線程執(zhí)行代碼
public void run() { … …
try {
LocalDevice localDevice =LocalDevice.getLocalDevice(); //獲取本地設(shè)備實(shí)例
localDevice.setDiscoverable(DiscoveryAgent.GIAC);
//得到搜索代理
discoveryAgent = localDevice.getDiscoveryAgent();
//開(kāi)始搜索服務(wù)
discoveryAgent.searchServices(attrSet, uuidSet,remoteDevice, this);
} catch (BluetoothStateException init){
init.printStackTrace();
} }
// 搜索到服務(wù)
public void servicesDiscovered(int transID,
ServiceRecord[] servRecord) { //添加探索到的服務(wù)
for(int i = 0; i < servRecord.length; ++i)
serviceRecords.addElement(servRecord[i]);
}
2.2.4 建立與遠(yuǎn)程設(shè)備的連接
通過(guò)服務(wù)記錄來(lái)獲取連接字符串,并建立與遠(yuǎn)程設(shè)備的連接??蛻舳送ㄟ^(guò)搜索到的服務(wù)記錄來(lái)獲取可供連接的URL,并與服務(wù)端進(jìn)行連接。
// 客戶端通信線程核心
public void run() {
//通過(guò)服務(wù)記錄來(lái)獲取建立連接的URL
String url = serviceRecord.getConnectionURL(
ServiceRecord.NOAUTHENTICATE_NOENCRYPT,false); … …
StreamConnection conn = null;
try { // 通過(guò)URL 建立連接
conn = (StreamConnection)Connector.open(url);
} catch (IOException open)
{ open.printStackTrace(); } }
2.2.5 實(shí)現(xiàn)服務(wù)端和客戶端的通信
由連接對(duì)象獲取輸入/輸出流,實(shí)現(xiàn)服務(wù)端和客戶端的通信。請(qǐng)求處理完畢,關(guān)閉與該遠(yuǎn)程設(shè)備的連接。
客戶端發(fā)起與服務(wù)端進(jìn)行通信并從服務(wù)器獲取圖片。
// 發(fā)送文件名稱請(qǐng)求并下載文件
sendFileName(conn, fileName);
downloadFile(conn, fileName);
try { conn.close(); }
catch (IOException close)
{ close.printStackTrace(); }
// 發(fā)送文件名請(qǐng)求
private void sendFileName(StreamConnection conn,
final String fileName) {
try { OutputStream out = conn.openOutputStream();
out.write(fileName.length() );
out.write(fileName.getBytes() );
out.flush(); out.close();
} catch (IOException write)
{ write.printStackTrace(); } }
// 下載文件
public void downloadFile(StreamConnection conn,final String fileName) {
byte [] buffer = null;
try { InputStream is = conn.openInputStream();
//頭兩個(gè)字節(jié)為數(shù)據(jù)長(zhǎng)度
int length = (is.read() 《 8); length |= is.read();
buffer = new byte[length]; length = 0;
while (length != buffer.length) {
int n = is.read(buffer, length, buffer.length - length);
if (n == -1) throw new IOException("Can't
readdata");
length += n; }
is.close();
} catch (IOException read)
{ read.printStackTrace(); }
try { Image img = Image.createImage(buffer, 0,buffer.length);
{$page$}
mainPanel.appendImage(img);
} catch (Exception image) {
image.printStackTrace();
} }
3 手機(jī)文件傳輸軟件的運(yùn)行與測(cè)試
對(duì)軟件打包后,利用手機(jī)安裝管理程序?qū)IDlet應(yīng)用程序從計(jì)算機(jī)下載到支持藍(lán)牙技術(shù)的手機(jī)上,然后執(zhí)行文件傳輸程序,在此可選擇執(zhí)行客戶端或服務(wù)器端應(yīng)用,如圖3 所示。用戶選擇BT Server 開(kāi)啟服務(wù)器端程序,允許對(duì)藍(lán)牙進(jìn)行連接。在另外一手機(jī)上執(zhí)行該程序,選擇BTClient,進(jìn)入文件傳輸?shù)目蛻舳四J剑藛沃袌?zhí)行Search Devices 功能,進(jìn)行本地藍(lán)牙設(shè)備的搜索,結(jié)果如圖4 所示。再執(zhí)行Search Service功能,即搜索服務(wù)器中提供的服務(wù),選中要下載的文件,執(zhí)行Transport 進(jìn)行文件傳輸,如圖5 所示。
當(dāng)下載的圖像文件傳輸完畢,將其顯示出來(lái),如圖6所示。
圖3 BTDemo 主界面
圖4 搜索藍(lán)牙服務(wù)
圖5 獲取Server 端資源
圖6 下載圖像文件并顯示
4 結(jié)束語(yǔ)
支持JAVA 并具備藍(lán)牙功能的手機(jī),給軟件業(yè)提供了新的機(jī)遇。本文開(kāi)發(fā)一種以J2ME 為平臺(tái)的藍(lán)牙文件傳輸軟件,可以進(jìn)行有效的文件傳輸。為建立微微網(wǎng)中文件服務(wù)做了一定的嘗試。在一定程度上拓展了手機(jī)的功能, 具有一定的應(yīng)用價(jià)值。