使用 JSR-82 和 OBEX 进行文件传输-----转

学习如何用 JSR-82 和 OBEX 把文件从客户机传输到服务器
 
    文档选项
   
将此页作为电子邮件发送


拓展 Tomcat 应用
  
下载 IBM 开源 J2EE 应用服务器 WAS CE 新版本 V1.1

 

 

级别: 初级

Bruce Hopkins (bhopkins@gestalt-llc.com), 技术架构师, 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 显示了协议栈的细节。


图 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 或 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.*;
                        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 所示)。


清单 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){
                        }
                        }
                       

 

机器上的每台蓝牙设备都需要惟一的标识符,所以我决定将这个服务的 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) {
                        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 所示。


清单 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;
                        }
                       

 

正如我前面说的,清单 4 的代码显示了 OBEX 服务器如何从远程蓝牙设备接收文件。您可能会注意到可以得到传输的文件的名称,在这个示例中,在实例化 File 对象时使用到了文件名称。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值