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 进行了必要的社会主义改造。
由于该方面的应用大多局限在企业内部,相关的资料不大好找,期间也遇到了不少的坑,好在经过艰辛的爬坑过程,大部分问题都得以解决。因此也希望以博文的形式给正在研究 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,后面也会提到。
然后Finish
完成项目创建。
接着将下载下来的 Spark 源代码拖进项目的目录下,这个时候会看到src
目录下会有很多报错提示,没关系。进入项目的 Build Path 设置里,将src
目录从 Source 栏中移除。然后将 Spark 主体源码src/java
以及 Fastpath 插件源码src/plugins/fastpath/src/java
加入到 Source 栏中。如果自己开发 Spark 插件或者改造其他插件,设置类似。
引入 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
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 的登录界面了。
输入可用的 Openfire 服务器以及用户名密码登录后,即可看到 Spark 的主界面了。Openfire 服务器的搭建及简单使用详情请 Google,这里就不予以说明。然而 Fastpath 插件,这里没有显示出来,原因在后面会提到。
若开启了 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 文件加入其中。
直接运行默认的 target:release。提示 Build Successful,项目目录下新增了一个 target 文件夹。进入target/build/bin
目录,然后运行 bat 或者 sh 文件,即可启动 Spark。
执行 Build 任务时,可能会遇到 Java 版本错误、编译版本错误等问题,导致 Build 失败。留意 Ant Build 文件中 Java Version、Ant Version、Javac Target 要求。
1 2 |
1 2 3 4 5 |
在 MyEclipse 中的 External Tool Configuration 中可以配置指定的 JRE 来运行 Ant 任务。
Build 之后,MyEclipse 中的 SparkDemo 项目出现了错误提示,该错误是 ANT 运行时产生的编译警告,可以在 Problems 窗口中删除该部分警告即可。
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 |
|
而我们的 Spark 版本定义仍然是 2.6.3:src/java/org/jivesoftware/resource/default.properties
1 2 3 |
APPLICATION_NAME = SparkSHORT_NAME = SparkAPPLICATION_VERSION = 2.6.3 |
我们这里可以将 Spark 版本改为 2.7.0。然后再运行程序,就能看见正常加载进 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 pluginsloadPublicPlugins();// 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 versiontry { 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 |
可以看到会对插件中定义的 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 |
@Overridepublic String getElementName() { return "cImg"; }@Overridepublic String getNamespace() { return "xxxx:xmpp:p_w_picpath"; }@Overridepublic String toXML() { StringBuilder sb = new StringBuilder(); sb.append("<"); sb.append(getElementName()); sb.append(" xmlns=\""); sb.append(getNamespace()); sb.append("\">"); sb.append(" |
接着实现一个图片消息解析类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 |
@Overridepublic 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:p_w_picpath", 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 |
@Overridepublic 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:p_w_picpath"); if (ext != null) { // 转换成自定义的图片消息 ImageMessage p_w_picpath = (ImageMessage) ext; // 获取到真正的图片信息 ImageIcon icon = new ImageIcon(Base64.decodeBase64(p_w_picpath.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 p_w_picpath = new ImageMessage(); p_w_picpath.setName(name); p_w_picpath.setType(type); p_w_picpath.setSize(size); p_w_picpath.setEncoded(encode); |
然后加入到消息中:
1 2 3 4 5 6 7 8 9 |
final Message chatMessage = new Message();chatMessage.setType(Message.Type.groupchat);chatMessage.setBody(p_w_picpath.getName() + " | " + p_w_picpath.getSize() + "B | " + p_w_picpath.getType());// 添加扩展 chatMessage.addExtension(p_w_picpath);String room = chat.getRoom();chatMessage.setTo(room);chat.sendMessage(chatMessage); |
Fastpath 中接收到图片如下:
最后我们来看看搭载图片的 XMPP 消息包的具体内容:
在 message body 的后面出现了我们的自定义扩展内容,encoded
元素内存放的则是图片的 Base64 转码。
结语
开发过程中,由于参考资料较少,中间花费了不少时间,遇到几大方面的问题
Spark、Openfire 等一系列环境搭建,调试准备
定位需要进行修改的模块
SWING 开发
Fastpath 处理图片消息
利用 install4j 打包 Spark 安装程序
Fastpath Web Client 的二次开发
本文中提及到的内容只是其中的小部分。面对已有的大量源代码,没有技术文档可读,只能通过 IDE 去不断的溯源查找每一个引用的含义及实现,最终勉强完成功能需求。遗憾的是,读源代码时没有做好笔记,只顾着功能实现,刚刚准备写此文时,就觉部分内容略感生疏。
参考
Smack 解析自定义包结构
LOAD TESTING OPENFIRE FASTPATH
XMPP 实现群聊截图 (spark+openfire)
Openfire 插件开发坏境配置指南
Looking for fastpath_webchat.war source code svn ??
Open Realtime.