级别: 初级 Bruce Hopkins, 技术架构师, Gestalt LLC
2005 年 12 月 15 日 熟悉用来控制蓝牙设备的 Java™ 语言库,并学习如何用 JSR-82 API 和 Object Exchange 在客户机和服务器之间传输文件。
蓝牙协议栈允许采用多种方法,包括 RFCOMM 和 Object
Exchange(OBEX),在设备之间发送和接收文件。如果想发送和接收流数据(而且想采用传统的串口应用程序,并给它加上蓝牙支持),那么
RFCOMM 更好。反过来,如果想发送对象数据以及关于负载的上下文和元数据,则 OBEX 最好。在这篇文章中,将熟悉用来控制蓝牙设备的
Java 语言库,并学习如何使用 JSR-82 API 和 OBEX 在客户机和服务器之间传输文件。
蓝牙是多家移动设备制造商选择的无线协议,拥有多项吸引人的特性,最重要的是它在数据传输上的低能耗。截止 2004 年 6 月,每周交付的支持蓝牙的设备超过五百万台,这项技术在消费电子市场上拥有强大的渗透率。
目前市场上设备中运行的蓝牙协议有三个版本 —— 分别是版本 1.1、1.2 AFH 和 2.0+EDR。这没给开发人员带来任何问题,因为新版本的协议与以前的版本兼容。表 1 显示了目前可用的蓝牙版本的一些相似性与区别。
表 1. 蓝牙协议版本
版本 | 原始数据速率 | 通信范围(英尺) | 说明 |
---|
Bluetooth 1.1 | 1 Mbps | 30-300 | 市场上部署得最广泛的蓝牙版本。 | Bluetooth 1.2 AFH | 1 Mbps | 30-300 | 包含高级频率跳跃技术,可以与 WiFi 网络更好地并存。 | Bluetooth 2.0+EDR | 3 Mbps | 30-300 | 包含增强数据速率技术,可以用更高的速度传输数据。 |
现在,让我们回顾一下控制蓝牙设备的固件/软件:栈。
蓝牙栈的目的是什么呢?栈是控制蓝牙设备的软件(和固件)。图 1 显示了协议栈的细节。
-
栈的最底层是 HCI,即主机控制器接口(Host Controller Interface)。这一层顾名思义就是主机(计算机)和控制器(蓝牙设备)之间的接口。可以看到,其他所有的层都要经过 HCI。
-
HCI 上面的一层是 L2CAP,即逻辑链接控制器适配协议(Logical Link Controller Adaptation Protocol)。这一层充当其他所有层的数据多路复用器。
-
接下来一层是 BNEP,即蓝牙网络封装协议(Bluetooth Network Encapsulation Protocol)。使用 BNEP,可以在蓝牙上运行其他网络协议,例如 IP、TCP 和 UDP。
-
RFCOMM 称作虚拟串口协议(virtual serial port protocol),因为它允许蓝牙设备模拟串口的功能。
-
OBEX 协议层是在 RFCOMM 层上面实现的,如果想把数据以对象(例如文件)的形式传输,那么 OBEX 很有用。
-
SDP 是服务发现协议(Service Discovery Protocol)层,用于在远程蓝牙设备上寻找服务。
-
最后两层是 AVCTP 和 AVDTP,用于蓝牙上音频和视频的控制 和 发布。AVCTP 和 AVDTP 是蓝牙协议中增加的相对较新的层;如果想控制媒体播放器的功能或者想以立体声播放音频流,则要使用它们。
我们先来看看栈中用来发送数据的两个简单协议 RFCOMM 和 OBEX,并比较使用它们传送文件的优势和不足。
可以采用 RFCOMM 或 OBEX 在蓝牙设备之间发送和接收文件。但是,如果想发送和接收流数据,则 RFCOMM
是更好的选择,就像使用传统的串口一样。在现实世界中,如果想使用传统的串口应用程序,并让它能使用蓝牙,就应当使用
RFCOMM。如果要在蓝牙设备之间发送简单的文本字符串(例如在聊天应用程序中),那么使用 OBEX 可能没有太大优势。在这种情况下,应当使用
RFCOMM 或 L2CAP。
另一方面,如果想发送对象数据(例如文件),则 OBEX 最合适。使用 OBEX
不仅可以发送数据,而且还能发送关于负载的上下文或元数据。例如,在使用 OBEX
发送文件时,还能够发送关于文件的其他有用信息,例如文件名称、文件类型、文件尺寸或者其他任何对文件进行描述的内容。
那么,既然已经决定了使用蓝牙时通过 OBEX 发送对象数据文件,那么我们来看看使用 Java 语言对蓝牙设备进行控制的官方库。
JSR-82 是用于蓝牙无线技术的官方 Java API。可使用这个 API 创建可执行以下功能的应用程序:
- 判断和检测自己的蓝牙设备的属性
- 发现设备通信范围内的蓝牙设备
- 在远程蓝牙设备上搜索服务
- 创建可以与远程蓝牙服务器通信的蓝牙客户机应用程序
- 创建能够为蓝牙客户机的请求提供服务的蓝牙服务器应用程序
JSR-82 包含两个包,即 javax.bluetooth 和 javax.obex 。您自己的蓝牙设备由 javax.bluetooth.LocalDevice 类表示,所有的远程蓝牙设备由 javax.bluetooth.RemoteDevice 类表示。
javax.bluetooth.DiscoveryAgent 类是个有帮助的类,它让您可以发现附近的远程蓝牙设备,并为区域内的每个蓝牙设备返回一个 javax.bluetooth.RemoteDevice 。也可以使用 javax.bluetooth.DiscoveryAgent 在已经发现的远程设备上搜索服务。
如果想在发生发现事件的时候得到通知,则需要实现 javax.bluetooth.DiscoveryListener 接口的方法 。这一切听都来都很简单,是不是?但是,当想要创建 OBEX 应用程序的时候,会变得复杂一些。
也许您不知道,OBEX 并不是蓝牙本身的协议,实际是由无线数据协会创建的。因为 OBEX 是已采纳的 协议,所以 Java 蓝牙 API 的作者决定为 OBEX 应用程序单独创建一个包;这样,就可以使用 Java 创建出能够在任何传输机制(例如红外或 TCP/IP)而不仅仅是在蓝牙上工作的 OBEX 应用程序。
在使用蓝牙 API 创建 OBEX 应用程序时,将使用 javax.obex 包中的一些类和接口。正如我在前面提到过的,蓝牙协议栈中的 OBEX 层实际是面向蓝牙设备间的文件传输而优化的,所以就像传统的 FTP 一样,OBEX 应用程序拥有像 GET 和 PUT 这样的操作。
到底什么是 OBEX 操作?
当客户机和服务器在一个 OBEX 会话内通信时,它们的交互叫做操作。对于每个从客户机发出的操作,服务器给出一个响应,指明操作的状态。要真正了解 OBEX 客户机和服务器的操作和响应的工作方式,请参见图 2:
如果从电话向打印机发送文件,就像图 2 中表示的那样,需要什么呢?首先,在创建 OBEX 会话之前,先要建立一个传输的连接(蓝牙、红外、TCP/IP 等等)。因为这篇文章是关于蓝牙的,所以毫不奇怪我要使用的底层传输机制是蓝牙。
在传输连接已经建立之后,蓝牙客户机需要发出 CONNECT
操作。如果蓝牙服务器(在这个示例中,是打印机)想接受新客户机来使用它的服务(例如打印服务),那么它就用表示成功的响应代码 160
对客户机进行响应。如果打印机不想接受新客户机,那么它可能用响应代码 211 来响应,这个代码代表 “OBEX SERVICE
UNAVAILABLE”。
现在假设打印机接受了 CONNECT 操作,那么现在就在客户机和服务器之间创建了一个 OBEX 会话。现在有了 OBEX 会话,就能够向 OBEX 服务器发送请求(请注意,请求和操作是同义的)。当然,GET 和 PUT 操作是自解释的。但是,SETPATH 操作主要是在 OBEX 服务器具有文件系统时才使用。OBEX 客户机发送 SETPATH 请求以指导 OBEX 客户机改变工作目录(类似于 cd 命令)。SETPATH 操作后面通常跟着一个 GET 或 PUT 操作。要终止 OBEX 会话,客户机需要发送 DISCONNECT 操作。如果成功,OBEX 服务器会用成功响应代码 160 进行响应。
现在对于 OBEX 的语义有了良好的理解,让我们看看在 JSR-82 API 中,Java 语言、蓝牙和 OBEX 是如何放在一起的。我先从服务器代码开始,因为客户机代码更复杂。
我的服务器应用程序叫做 FileServer.java ,而且因为 “重要事情优先”,所以我们先来看清单 1 中的 import 语句和类声明。
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{
|
如果想创建 OBEX 服务器,就需要扩展 javax.obex.ServerRequestHandler 类。因为这个示例是 J2SE 应用程序,所以我还想实现 ActionListener 接口,这样就能响应按钮点击。当用户点击启动服务器的按钮时,就会调用 actionPerformed() 方法(如清单 2 所示)。
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){ } }
|
机器上的每台蓝牙设备都需要惟一的标识符,所以我决定将这个服务的 UUID (统一惟一标识符)定为 8841 (可以是任意四位数字)。在创建客户机应用程序时需要记住这个 UUID。
因为正在创建 JSR-82 服务器应用程序,所以需要调用 Connector.open() 并传递进 String ,其中包含服务的 URL。因为这是一个 OBEX 应用程序,所以采用的协议是 btgoep 。而且,也可以看到服务被命名为 “FTP”(也可以取其他的名称)。
FileServer.java 是一个 OBEX 服务器应用程序,这意味着 OBEX 客户机将发送 OBEX 操作,例如 CONNECT 、GET 、PUT 等等。所以,类需要恰当地处理客户机将要发送的每个操作(或者我想要处理的操作)。所以,在 FileServer.java 中,我包含了 onPut() 、onConnect() 和 onDisconnect() 方法的实现。我可能还创建了 onGet() 的实现,但是因为只想从客户机向服务器发送简单文件,所以实现 onGet() 并不是必需的,而只需要实现 onPut() 方法。
清单 3 演示了 onConnect() 和 onDisconnect() 的实现。可以看到,onConnect() 要求一个 int 返回值,但是 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"); }
|
现在来看允许 FileServer.java 在客户机发送 PUT 请求时从客户机接收文件的代码段。onPut() 的代码如清单 4 所示。
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; }
|
正如我前面说的,清单 4 的代码显示了 OBEX 服务器如何从远程蓝牙设备接收文件。您可能会注意到可以得到传输的文件的名称,在这个示例中,在实例化 File 对象时使用到了文件名称。
在这篇文章中,我介绍了开始创建使用 OBEX 协议的 Java 蓝牙应用程序的许多基础知识。您学习了对于数据传输什么时候选择 OBEX 比 RFCOMM 更合适。阅读完这篇文章时,您应当很好地掌握了 OBEX 应用程序需要的语义的知识。
在这个系列的第 2 部分中,我将介绍如何创建能够与这里创建的服务器应用程序正确通信的客户机应用程序。当您很好地理解了客户机代码之后,我将对客户机代码稍做修改,创建一个蓝牙音乐商店。
学习
获得产品和技术
-
这篇文章中的示例是用 创建的。JB-22 是一个提供蓝牙硬件和软件特性的完整的 Java 蓝牙开发包。
-
可以在 得到蓝牙规范。
|