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 进行了必要的社会主义改造。
http://blog2mih.qiniudn.com/img/of.png
由于该方面的应用大多局限在企业内部,相关的资料不大好找,期间也遇到了不少的坑,好在经过艰辛的爬坑过程,大部分问题都得以解决。因此也希望以博文的形式给正在研究 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>
[*] 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,后面也会提到。
http://blog2mih.qiniudn.com/img/project-jre.png
然后Finish完成项目创建。
接着将下载下来的 Spark 源代码拖进项目的目录下,这个时候会看到src目录下会有很多报错提示,没关系。进入项目的 Build Path 设置里,将src目录从 Source 栏中移除。然后将 Spark 主体源码src/java以及 Fastpath 插件源码src/plugins/fastpath/src/java加入到 Source 栏中。如果自己开发 Spark 插件或者改造其他插件,设置类似。
http://blog2mih.qiniudn.com/img/source-config.png
引入 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>org.jivesoftware.launcher.Startup
[*] JRE:确认 JRE 版本为 1.7+
[*] Classpath:将 Spark 的 resources 以及插件的 resources 文件夹加入到 User Entries 中 (选择User Entries, 点击Advanced, 选择Add Folders)
http://blog2mih.qiniudn.com/img/UserEntries.png
[*] 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 的登录界面了。
http://blog2mih.qiniudn.com/img/RunSpark.png
输入可用的 Openfire 服务器以及用户名密码登录后,即可看到 Spark 的主界面了。Openfire 服务器的搭建及简单使用详情请 Google,这里就不予以说明。然而 Fastpath 插件,这里没有显示出来,原因在后面会提到。
http://blog2mih.qiniudn.com/img/Spark.png
若开启了 Smack Debug,还会出现 Smack Debug 窗口。
http://blog2mih.qiniudn.com/img/SmarckDebug.png
运行时,可能会出现bin目录拒绝访问的异常,原因是 Spark 自带的一个插件LanguagePlugin会在试图在运行目录下面寻找 spark.jar,但是调试时bin目录下缺少 spark.jar。该问题在 spark 安装版本时不会出现,调试时可以直接忽略,或者通过下面的build>生成target\build\lib\spark.jar,然后拷贝至 MyEclipse 的项目bin目录下面。
Build
再来看看 Spark 客户端的构建,进入项目的 Build 目录中,查看 build.xml,里面定义了各种 build target。可以打开 MyEclipse 的 Ant 窗口,将该 build 文件加入其中。
http://blog2mih.qiniudn.com/img/AntWin.png
直接运行默认的 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 窗口中删除该部分警告即可。
http://blog2mih.qiniudn.com/img/DeleteComplierWarnings.png
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
... 2.7.0
1.7.0
而我们的 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 插件了。
http://blog2mih.qiniudn.com/img/Fastpath.png
有时运行 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 -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
@Overridepublic String getElementName() { return "cImg";
}@Overridepublic String getNamespace() { return "xxxx:xmpp:image";
}@Overridepublic String toXML() {
StringBuilder sb = new StringBuilder();
sb.append("");
sb.append("").append(name).append("");
sb.append("").append(type).append("");
sb.append("").append(size).append("");
sb.append("").append(encoded).append("");
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
@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: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
@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: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当中。
1SparkManager.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 中接收到图片如下:
http://blog2mih.qiniudn.com/img/image-chat.png
最后我们来看看搭载图片的 XMPP 消息包的具体内容:
http://blog2mih.qiniudn.com/img/ImagePacket.png
在 message body 的后面出现了我们的自定义扩展内容,encoded元素内存放的则是图片的 Base64 转码。
结语
开发过程中,由于参考资料较少,中间花费了不少时间,遇到几大方面的问题
[*] Spark、Openfire 等一系列环境搭建,调试准备
[*] 定位需要进行修改的模块
[*] SWING 开发
[*] Fastpath 处理图片消息
[*] 利用 install4j 打包 Spark 安装程序
[*] Fastpath Web Client 的二次开发
本文中提及到的内容只是其中的小部分。面对已有的大量源代码,没有技术文档可读,只能通过>参考
[*] Smack 解析自定义包结构
[*] LOAD TESTING OPENFIRE FASTPATH
[*] XMPP 实现群聊截图 (spark+openfire)
[*] Openfire 插件开发坏境配置指南
[*] Looking for fastpath_webchat.war source code svn ??
[*] Open Realtime.
页:
[1]