蓝牙协议栈允许采用多种方法,包括 RFCOMM 和 Object Exchange(OBEX),在设备之间发送和接收文件。如果想发送和接收流数据(而且想采用传统的串口应用程序,并给它加上蓝牙支持),那么 RFCOMM 更好。反过来,如果想发送对象数据以及关于负载的上下文和元数据,则 OBEX 最好。在这篇文章中,将熟悉用来控制蓝牙设备的 Java 语言库,并学习如何使用 JSR-82 API 和 OBEX 在客户机和服务器之间传输文件。
蓝牙是多家移动设备制造商选择的无线协议,拥有多项吸引人的特性,最重要的是它在数据传输上的低能耗。截止 2004 年 6 月,每周交付的支持蓝牙的设备超过五百万台,这项技术在消费电子市场上拥有强大的渗透率。
蓝牙协议回顾
目前市场上设备中运行的蓝牙协议有三个版本 —— 分别是版本 1.1、1.2 AFH 和 2.0+EDR。这没给开发人员带来任何问题,因为新版本的协议与以前的版本兼容。表 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 显示了协议栈的细节。
图 1. 蓝牙协议栈
发送文件:RFCOMM 还是 OBEX?
我们先来看看栈中用来发送数据的两个简单协议 RFCOMM 和 OBEX,并比较使用它们传送文件的优势和不足。
可 以采用 RFCOMM 或 OBEX 在蓝牙设备之间发送和接收文件。但是,如果想发送和接收流数据,则 RFCOMM 是更好的选择,就像使用传统的串口一样。在现实世界中,如果想使用传统的串口应用程序,并让它能使用蓝牙,就应当使用 RFCOMM。如果要在蓝牙设备之间发送简单的文本字符串(例如在聊天应用程序中),那么使用 OBEX 可能没有太大优势。在这种情况下,应当使用 RFCOMM 或 L2CAP。
另一方面,如果想发送对象数据(例如文件),则 OBEX 最合适。使用 OBEX 不仅可以发送数据,而且还能发送关于负载的上下文或元数据。例如,在使用 OBEX 发送文件时,还能够发送关于文件的其他有用信息,例如文件名称、文件类型、文件尺寸或者其他任何对文件进行描述的内容。
那么,既然已经决定了使用蓝牙时通过 OBEX 发送对象数据文件,那么我们来看看使用 Java 语言对蓝牙设备进行控制的官方库。
|
|
探索 JSR-82 API
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 并不是蓝牙本身的协议,实际是由无线数据协会创建的。因为 OBEX 是已采纳的 协议,所以 Java 蓝牙 API 的作者决定为 OBEX 应用程序单独创建一个包;这样,就可以使用 Java 创建出能够在任何传输机制(例如红外或 TCP/IP)而不仅仅是在蓝牙上工作的 OBEX 应用程序。
在使用蓝牙 API 创建 OBEX 应用程序时,将使用 javax.obex
包中的一些类和接口。正如我在前面提到过的,蓝牙协议栈中的 OBEX 层实际是面向蓝牙设备间的文件传输而优化的,所以就像传统的 FTP 一样,OBEX 应用程序拥有像 GET
和 PUT
这样的操作 。
到底什么是 OBEX 操作?
OBEX 操作
当客户机和服务器在一个 OBEX 会话内通信时,它们的交互叫做操作。对于每个从客户机发出的操作,服务器给出一个响应,指明操作的状态。要真正了解 OBEX 客户机和服务器的操作和响应的工作方式,请参见图 2:
图 2. OBEX 操作和响应代码
如果从电话向打印机发送文件,就像图 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 是如何放在一起的。我先从服务器代码开始,因为客户机代码更复杂。
|
|
创建 OBEX 服务器应用程序
我的服务器应用程序叫做 FileServer.java
,而且因为 “重要事情优先”,所以我们先来看清单 1 中的 import 语句和类声明。
清单 1. import 语句和类声明
import javax.swing.*; |
如果想创建 OBEX 服务器,就需要扩展 javax.obex.ServerRequestHandler
类。因为这个示例是 J2SE 应用程序,所以我还想实现 ActionListener
接口,这样就能响应按钮点击。当用户点击启动服务器的按钮时,就会调用 actionPerformed()
方法(如清单 2 所示)。
清单 2. FilesServer.actionPerformed()
public void actionPerformed(ActionEvent e) { |
机器上的每台蓝牙设备都需要惟一的标识符,所以我决定将这个服务的 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()
不返回内容,因为不需要。
清单 3. FilesServer.onConnect() 和 FilesServer.onDisconnect()
public int onConnect(HeaderSet request, HeaderSet reply) { |
现在来看允许 FileServer.java
在客户机发送 PUT
请求时从客户机接收文件的代码段。onPut()
的代码如清单 4 所示。
清单 4. FilesServer.onPut()
public int onPut (Operation op) { |
正如我前面说的,清单 4 的代码显示了 OBEX 服务器如何从远程蓝牙设备接收文件。您可能会注意到可以得到传输的文件的名称,在这个示例中,在实例化 File
对象时使用到了文件名称。
|
|
结束语
在这篇文章中,我介绍了开始创建使用 OBEX 协议的 Java 蓝牙应用程序的许多基础知识。您学习了对于数据传输什么时候选择 OBEX 比 RFCOMM 更合适。阅读完这篇文章时,您应当很好地掌握了 OBEX 应用程序需要的语义的知识。
在这个系列的第 2 部分中,我将介绍如何创建能够与这里创建的服务器应用程序正确通信的客户机应用程序。当您很好地理解了客户机代码之后,我将对客户机代码稍做修改,创建一个蓝牙音乐商店。
参考资料
学习
获得产品和技术
关于作者
Bruce Hopkins 是 Bluetooth for Java (Apress)的作者,也是 JB-22 开发包的创建者。他毕业于底特律的 Wayne 州立大学,拥有电子和计算机工程学士学位。他目前是 Gestalt LLC 的技术架构师,专攻分布式计算、Web 服务和无线技术。您可以通过他的电子邮件 [email protected] 与他联系。 |