spark client IM

SparkWeb 是由 Jive 软件公司创建的基于Web的XMPP客户端,采用 ActionScript 3 编写,使用 Adobe 的 Flex API 。支持个人头像装扮 Avatars,vcards,多用户聊天以及其他更多的XMPP的特性。


基于开源jabber(XMPP)架设内部即时通讯服务的解决方案


spark client:::http://www.igniterealtime.org/projects/spark/index.jsp


Spark 是一个基于 XMPP 的 Java 即时通讯客户端,非 Apache Spark,有兴趣的可以继续往下瞅。

前言

前两个月的时间里,有幸接触到即时通讯领域的一些内容,认识了基于 XMPP 的开源 RTC Server Openfire以及开源的 XMPP 客户端Spark。由于项目的需求,采用 Openfire + Spark 的方案来完成即时通讯,然后对 Spark 进行了必要的社会主义改造。

Openfire

由于该方面的应用大多局限在企业内部,相关的资料不大好找,期间也遇到了不少的坑,好在经过艰辛的爬坑过程,大部分问题都得以解决。因此也希望以博文的形式给正在研究 Spark 的苦逼娃一点帮助。

这里把重点放在 Spark 方面,主要是对 Spark 官方自带的 Fastpath 插件进行了二次开发、汉化等工作,项目中还涉及到 Fastpath 的 Openfire 插件、以及 Fastpath 的 Web Client,这里就不展开了。

环境搭建

获取源代码

Sarpk 官方正式版最新的是 2.6.3,于 2011 年发布的,官网上也放出了一些开发预览版SPARK NIGHTLY BUILDS。我们可以从 Spark 的官方 SVN http://svn.igniterealtime.org/svn/repos/spark/trunk 获取 Spark 的最新开发代码。代码的下载速度较慢,慢慢等吧。下载完成后,让我们看看具体的内容:

  • build里面是也用来打包 Spark 的,构建工具是 ant。可以将 Spark 打包成各种平台下的客户端。
  • documentation里面是附带的文档,里面有 Spark 客户端插件开发的一些指引文档。
  • src里面则是项目的源代码,包括 Spark 主体以及其他官方插件的源代码等。

新建项目

这里以 MyEclipse IDE 为例,新建一个 Spark 二次开发的项目。

  • MyEclipse 10.5
  • JDK 1.7.0_45
  • Windows 7 SP1

在 MyEclipse 中新建一个 Java 项目 SparkDemo,JRE 指定为 1.7+,下拉栏里没有的,去 Configure JREs 里添加,这里最好选择使用 Java 7,因为在 Build 时,默认要求 JRE 最低版本是 Java 7,后面也会提到。
New Java Project

然后Finish完成项目创建。

接着将下载下来的 Spark 源代码拖进项目的目录下,这个时候会看到src目录下会有很多报错提示,没关系。进入项目的 Build Path 设置里,将src目录从 Source 栏中移除。然后将 Spark 主体源码src/java以及 Fastpath 插件源码src/plugins/fastpath/src/java加入到 Source 栏中。如果自己开发 Spark 插件或者改造其他插件,设置类似。

Source Configuration

引入 lib

然后引入项目所需的库文件也就是 jar 包。
包括 Spark 主体程序需要的库文件:

  • build/lib
  • build/lib/dist
  • build/lib/merge

以及插件需要的库文件:

  • src/plugins/fastpath/build/lib
  • src/plugins/fastpath/build/lib/dist

引入完毕后,SparkDemo 项目的错误提示就会自动去除了。

Build and Run

Run

进入 MyEclipse 的 Run Configurations,新建一个 Java Application 的 launch configuration:

  • Main:Main Class 设置为org.jivesoftware.launcher.Startup
  • JRE:确认 JRE 版本为 1.7+
  • Classpath:将 Spark 的 resources 以及插件的 resources 文件夹加入到 User Entries 中 (选择User Entries, 点击Advanced, 选择Add Folders)

    • src/resources
    • src/plugins/fastpath/src/resources

    User Entries Configuration

  • Arguments:VM arguments 中加入

    • -Djava.library.path=build/lib/dist/windows 引入平台运行环境,根据当前开发的运行环境进行选择,如 win32 win64 Linux。按照自身情况引入相应的 dll 或者 so 等。必须添加。没有的话,windows 平台下会抛出com.lti.civil.CaptureException异常
    • -Dplugin=src/plugins/fastpath/plugin.xml 引入相应的插件配置 xml。
    • -Ddebug.mode=true 开启 Debug 模式,按需添加
    • -Dsmack.debugEnabled=true 开启 Smack Debug 模式,按需添加。添加后,在 Spark 启动后,同时启动 Smack 分析界面,可以用来记录分析 Spark 通信过程的消息包。

      1
      2
      3
      4
      
      -Djava.library.path=build/lib/dist/windows
      -Dplugin=src/plugins/fastpath/plugin.xml
      -Ddebug.mode=true
      -Dsmack.debugEnabled=true
      

设置完毕后,我们就可以按照该 Run config 进行 Run 或者 Debug 了。运行后,就可以看到 Spark 的登录界面了。

Run Spark

输入可用的 Openfire 服务器以及用户名密码登录后,即可看到 Spark 的主界面了。Openfire 服务器的搭建及简单使用详情请 Google,这里就不予以说明。然而 Fastpath 插件,这里没有显示出来,原因在后面会提到。

Spark
若开启了 Smack Debug,还会出现 Smack Debug 窗口。

Smack Debug

运行时,可能会出现bin目录拒绝访问的异常,原因是 Spark 自带的一个插件LanguagePlugin会在试图在运行目录下面寻找 spark.jar,但是调试时bin目录下缺少 spark.jar。该问题在 spark 安装版本时不会出现,调试时可以直接忽略,或者通过下面的build release生成target\build\lib\spark.jar,然后拷贝至 MyEclipse 的项目bin目录下面。

Build

再来看看 Spark 客户端的构建,进入项目的 Build 目录中,查看 build.xml,里面定义了各种 build target。可以打开 MyEclipse 的 Ant 窗口,将该 build 文件加入其中。
Ant Window
直接运行默认的 target:release。提示 Build Successful,项目目录下新增了一个 target 文件夹。进入target/build/bin目录,然后运行 bat 或者 sh 文件,即可启动 Spark。

执行 Build 任务时,可能会遇到 Java 版本错误、编译版本错误等问题,导致 Build 失败。留意 Ant Build 文件中 Java Version、Ant Version、Javac Target 要求。

1
2
<fail if="ant.not.ok" message="Must use Ant 1.6.x or higher to build Spark"/>
<fail if="java.not.ok" message="Must use JDK 1.7.x or higher to build Spark"/>
1
2
3
4
5
<javac destdir="${classes}"
               debug="true"
               source="1.7"
               target="1.7"
            >

在 MyEclipse 中的 External Tool Configuration 中可以配置指定的 JRE 来运行 Ant 任务。

Build 之后,MyEclipse 中的 SparkDemo 项目出现了错误提示,该错误是 ANT 运行时产生的编译警告,可以在 Problems 窗口中删除该部分警告即可。

Delete Compiling Warnings

Build 文件中还定义了其他的 Target,如 clean ,Build 后运行 Clean 执行清理任务 ;  installer.install4j  , 配合 Install4j 生成 Spark 的安装包。可以根据需要进行使用,使用过程中遇到问题可根据 Ant 报错提示来进行调整。

## Fastpath

### 运行 Fastpath

在前面的 运行 过程中,并没有看到 Fastpath 插件的加载,这个是因为 Fastpath 插件中指定了 Spark 最低版本必须是 2.7.0 : src/plugins/fastpath/plugin.xml
1
2
3
4
5
<plugin>
    ...
    <minSparkVersion>2.7.0</minSparkVersion>
    <java>1.7.0</java>
</plugin>

而我们的 Spark 版本定义仍然是 2.6.3: src/java/org/jivesoftware/resource/default.properties
1
2
3
APPLICATION_NAME = Spark
SHORT_NAME = Spark
APPLICATION_VERSION = 2.6.3

我们这里可以将 Spark 版本改为 2.7.0。然后再运行程序,就能看见正常加载进 Fastpath 插件了。
Fastpath

有时运行 Spark 后会碰到 Spark 中出现 2 个相同的插件,此时清空 Spark 工作目录再重新运行即可。Windows 下 Win + R 输入  %appdata%  然后确定,进入 AppData 目录,删除 Spark 目录即可。

在 Spark 代码中, src/java/org/jivesoftware/spark/PluginManager.java  完成加载插件的任务。
loadPlugins 方法中:
1
2
3
4
5
// Load extension plugins
loadPublicPlugins();

// For development purposes, load the plugin specified by -Dplugin=...
String plugin = System.getProperty("plugin");

开发时会以 2 种方式加载插件,就有可能会造成某些插件加载二次。
loadPublicPlugin方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Check for minimum Spark version
try {
    minVersion = plugin1.selectSingleNode("minSparkVersion").getText();

    String buildNumber = JiveInfo.getVersion();
    boolean ok = buildNumber.compareTo(minVersion) >= 0;

    if (!ok) {
        return null;
    }
}
catch (Exception e) {
    Log.error("Unable to load plugin " + name + " due to missing <minSparkVersion>-Tag in plugin.xml.");
    return null;
}

可以看到会对插件中定义的 Spark 最低版本进行检查,另外还有 Java 版本、操作系统类型版本均有相应的匹配验证。

Fastpath 汉化

Spark 的插件机制支持 i18n 国际化,Fastpath 也默认支持了 5 种语言。在目录src/plugins/fastpath/src/resources/i18n下可以看到 Fastpath 的国际化文件,我们只需按照规范加入fastpath_i18n_zh_CN.properties就可以完成汉化操作。

消息扩展

Spark 客户端实现的是 XMPP 的通信协议,构建于Smack API之上。Smack 对于消息 Packet 的灵活扩展也提供了很好的支持,给即时通讯带来的功能性上的扩展。由于项目中要在在 Fastpath 应用中需要加入图片内容处理,但是 Fastpath 应用场景是多人聊天,默认不支持发送图片,因此这里考虑扩展Message,定义自己的图片Packet。本文的实现方式不太优雅:将图片转成 BASE64 编码后,以文本格式传递消息,然后在接收方对消息进行还原,显示图片信息。这种方式只能支持体积较小的图片。

定义 Packet

实现一个图片消息类ImageMessage,实现PacketExtension,主要是定义自己的Packet的 root element name、namespace、属性以及对应 Packet 的 xml 文本。root element name 以及 namespace 名称可以随便取,不要与其他默认消息产生冲突就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Override
public String getElementName() {
    return "cImg";
}

@Override
public String getNamespace() {
    return "xxxx:xmpp:image";
}

@Override
public String toXML() {
    StringBuilder sb = new StringBuilder();
    
    sb.append("<");
    sb.append(getElementName());
    sb.append(" xmlns=\"");
    sb.append(getNamespace());
    sb.append("\">");
    
    sb.append("<name>").append(name).append("</name>");
    sb.append("<type>").append(type).append("</type>");
    sb.append("<size>").append(size).append("</size>");
    sb.append("<encoded>").append(encoded).append("</encoded>");
    
    sb.append("</");
    sb.append(getElementName());
    sb.append(">");
    
    return sb.toString();
}

接着实现一个图片消息解析类ImageMessageExtensionProvider,实现PacketExtensionProvider,定义如何解析上面的自定义的Packet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Override
public PacketExtension parseExtension(XmlPullParser parser)
        throws Exception {
    
    // customized packet message
    ImageMessage message = new ImageMessage();
    
    // parse raw XML stream and populate a message
    boolean done = false;
    while (!done) {
        int eventType = parser.next();
        if (eventType == XmlPullParser.START_TAG) {
            if (parser.getName().equals("name")) {
                message.setName(parser.nextText());
            }else if (parser.getName().equals("type")) {
                message.setType(parser.nextText());
            }else if (parser.getName().equals("size")) {
                message.setSize(Long.valueOf(parser.nextText()));
            }else if (parser.getName().equals("encoded")) {
                message.setEncoded(parser.nextText());
            }
        } else if (eventType == XmlPullParser.END_TAG) {
            if (parser.getName().equals(message.getElementName())) {
                done = true;
            }
        }
    }
    
    return message;
}

注册自定义 Packet

在代码中进行自定义 Packet 的注册,可以选择在加载 Fastpath 插件时完成这项工作。在src/plugins/fastpath/src/java/org/jivesoftware/fastpath/FastpathPlugin.java中的initialize方法中加入

1
2
ProviderManager.getInstance()
            .addExtensionProvider("cImg", "xxxx:xmpp:image", new ImageMessageExtensionProvider());

解析自定义图片 Packet

由于 Fastpath 没有自己处理消息并显示,用的是 Spark 消息处理模块。在src/java/org/jivesoftware/spark/ui/TranscriptWindow.java中的insertMessage方法中,能看到如下

1
2
3
4
5
6
7
8
// Check interceptors.
for (TranscriptWindowInterceptor interceptor : SparkManager.getChatManager().getTranscriptWindowInterceptors()) {
            boolean handled = interceptor.isMessageIntercepted(this, nickname, message);
            if (handled) {
                // Do nothing.
                return;
            }
}

因此可以通过消息拦截器来进行解析自定义的图片消息 Packet。

这时我们实现一个 TranscriptWindowInterceptor 并注册到 ChatManager 中即可,这里直接让 FastpathPlugin.java 实现了该接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Override
public boolean isMessageIntercepted(TranscriptWindow window, String userid,
        Message message) {
    
    String body = message.getBody();
    
    if (ModelUtil.hasLength(body)) {
        
        // 按照 root element 以及 namespace 筛选消息
        PacketExtension ext = message.getExtension("cImg", "xxxx:xmpp:image");
        
        if (ext != null) {
            // 转换成自定义的图片消息
            ImageMessage image = (ImageMessage) ext;
            
            // 获取到真正的图片信息
            ImageIcon icon = new ImageIcon(Base64.decodeBase64(image.getEncoded().getBytes()));
            window.insertIcon(icon);
            try {
                window.insertText("\n");
            } catch (BadLocationException e) {
                
            }
            
        }
        
    }
    // 继续处理其他
    return false;
}

初始化插件时,将该TranscriptWindowInterceptor加载进ChatManager当中。

1
SparkManager.getChatManager().addTranscriptWindowInterceptor(this);

这样当我们的 Spark 加载了 Fastpath 插件后,就可以处理带有 Image 扩展的消息了。

发送图片消息

对于消息发送方,该怎样构建自己的图片扩展消息并发送出去呢?

构建图片扩展:

1
2
3
4
5
6
7
8
9
10
// 将图片文件用 Base64 转码
byte[] bytes = item.get();
String encode = new String(Base64.encodeBase64(bytes));

// 构造消息图片扩展
ImageMessage image = new ImageMessage();
image.setName(name);
image.setType(type);
image.setSize(size);
image.setEncoded(encode);

然后加入到消息中:
1
2
3
4
5
6
7
8
9
final Message chatMessage = new Message();
chatMessage.setType(Message.Type.groupchat);
chatMessage.setBody(image.getName() + " | " + image.getSize() + "B | " + image.getType());
// 添加扩展
chatMessage.addExtension(image);

String room = chat.getRoom();
chatMessage.setTo(room);
chat.sendMessage(chatMessage);

Fastpath 中接收到图片如下:

Image Message

最后我们来看看搭载图片的 XMPP 消息包的具体内容:

Image XMPP

在 message body 的后面出现了我们的自定义扩展内容,encoded元素内存放的则是图片的 Base64 转码。

结语

开发过程中,由于参考资料较少,中间花费了不少时间,遇到几大方面的问题

  • Spark、Openfire 等一系列环境搭建,调试准备
  • 定位需要进行修改的模块
  • SWING 开发
  • Fastpath 处理图片消息
  • 利用 install4j 打包 Spark 安装程序
  • Fastpath Web Client 的二次开发

本文中提及到的内容只是其中的小部分。面对已有的大量源代码,没有技术文档可读,只能通过 IDE 去不断的溯源查找每一个引用的含义及实现,最终勉强完成功能需求。遗憾的是,读源代码时没有做好笔记,只顾着功能实现,刚刚准备写此文时,就觉部分内容略感生疏。

参考

  1. Smack 解析自定义包结构
  2. LOAD TESTING OPENFIRE FASTPATH
  3. XMPP 实现群聊截图 (spark+openfire)
  4. Openfire 插件开发坏境配置指南
  5. Looking for fastpath_webchat.war source code svn ??
  6. Open Realtime.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值