XMPP客户端库Smack 4.0.6版开发之一

一、Smack库概述

Smack是一个开源、易用的XMPP/Jabber客户端库,它使用Java语言开发,由Jive Software开发。

Smack的优点是编程简单。

Smack的缺点是其API并非为大量并发用户设计,每个客户都要1个线程,占用资源相对较,因此用Smack做模拟测试时,1台机器只能模拟有限(数千个)客户。

截止2014年11月27日,Smack库已经发展到4.0.6版。

最新的好消息是Smack在4.1.0版后将直接支持Android系统,而无需再使用以前的Smack移植版aSmack库了。

Smack库源码托管于GitHub,主页见: https://github.com/igniterealtime/Smack/



二、Smack 4的改变

Smack库从3.4版发展到4.0.x版后,其API有较大的变化,主要有:

1、把Connection类重命名为XMPPConnection类

XMPPConnection类是XMPPTCPConnection类和XMPPBOSHConnection类的父类。

2、把各种Provider类进行了分包

3、keep-alive(持久连接)机制从smack-core库移到了smack-extensions库

keep-alive机制现在由PingManager类提供。

4、PrivacyList类的toString()方法重命名为getName()

5、当Chat实例的所有引用都撤掉后,应该调用Chat.close()方法

否则Chat对象会有内存泄露的隐患,直到ChatManager对象被垃圾回收器回收后内存泄露隐患才会消失。

6、ServerTrustManager类被移除了

如果要使用带SSL认证的XMPP,你只需提供自己的SSLContext对象给ConnectionConfiguration对象即可。

7、Packet.setProperty()从smack-core库移到了smack-extensions库

其API现在可以在org.jivesoftware.smackx.jiveproperties包中找到。

8、Connection.getAccountManager()方法现在改成了AccountManager.getInstance(XMPPConnection)方法

9、异常API做了改进

10、ToContains过滤器被移除了

三、Smack库的特征
1、极度简单易用,API功能强大
发送一条文本消息给某个用户只需几行代码:

[java] view plaincopyprint?技术分享技术分享

  1. AbstractXMPPConnection connection = new XMPPTCPConnection("mtucker""password""jabber.org");  

  2. connection.connect();  

  3. connection.login();  

  4. Chat chat = ChatManager.getInstanceFor(connection)  

  5.         .createChat("[email protected]"new MessageListener(){  

  6.     public void processMessage(Chat chat, Message message){  

  7.         System.out.println("Received message: " + message);  

  8.     }  

  9. });  

  10. chat.sendMessage("Howdy!");  


2、隔离了底层数据包组装的复杂性,自然有相应的库来完成这些功能。Smack提供了更智能的高层构造,比如Chat类和Roster类,这样开发会更富有效率。
1)无需熟悉XMPP的XML格式,甚至都不需要了解XML
2)提供了简单的M2M通信
Smack让开发者可以对每条消息都设置大量的属性,属性中还可以包含Java对象。
3)基于Apache许可证的开源代码,这意味着你可以把Smack放入你自己的商业软件中。

四、Smack库的组成
Smack库可以内嵌到任意的Java应用程序中。Smack库有数个JAR文件组成,非常具有灵活性。
1、smack-core.jar
提供了核心XMPP功能。都是XMPP RFC规范定义的XMPP特性。
2、smack-extensions.jar
支持许多由XMPP Standards Foundation定义的扩展(XEP)功能。包括群聊、文件传输、用户搜索等等。
以后可查看文档《扩展手册》:
https://github.com/igniterealtime/Smack/blob/master/documentation/extensions/index.html
(目前还是无效的)
3、smack-experimental.jar
支持许多由XMPP Standards Foundation定义的体验性(XEP)功能。其API和功能特性都被认为是不稳定的。
4、smack-legacy.jar
支持许多由XMPP Standards Foundation定义的遗留(XEP)功能。
5、smack-bosh.jar
支持BOSH通信(XEP-0124规范定义的)。此代码被认为处于Beta阶段。
6、smack-jingle.jar
支持Jingle。此代码很老,目前处于无维护的状态。
7、smack-resolver-dnsjava.jar
支持对DNS SRV记录的解析,主要用于那些不支持javax.naming API的平台。
8、smack-debug.jar
用于协议流量的增强型GUI调试器。当调试模式开启后,如果它在类路径下,它会自动被使用。
以后可查看文档《调试模式》:
https://github.com/igniterealtime/Smack/blob/master/documentation/debugging.html
(目前还是无效的)

五、Smack的配置
Smack的初始化过程涉及到2阶段的调用。
1、初始化系统属性
通过SmackConfiguration类初始化所有的系统可访问属性,这些属性都是通过getXXX方法取回属性值的。
2、初始化启动类
任意类如果继承了SmackInitializer接口后,都可以在调用initialize()方法后得到初始化,这意味着得到初始化的类在启动后都是活动的。
如果没有继承SmackInitializer接口,那么要实现初始化,必须要放置一个静态代码块来实现——他在类装载时会自动执行。

初 始化是通过配置文件来完成的。默认情况下,Smack会载入Smack JAR文件中内嵌的配置文件(它位于org.jivesoftware.smack/smack-config.xml)。这个指定的配置文件包含了一系 列需载入初始化的类列表。所有的管理器类型的类都需要被初始化,这些管理器类就包含在上面所说的初始化列表中。

六、建立连接的例子

XMPPConnection类用于创建一个到XMPP服务器的连接,代码例子如下:

[java] view plaincopyprint?技术分享技术分享

  1. // 创建一个到jabber.org服务器的连接  

  2. AbstractXMPPConnection conn1 = new XMPPTCPConnection("username""password""jabber.org");  

  3. conn1.connect();  


[java] view plaincopyprint?技术分享技术分享

  1. // 创建一个到jabber.org服务器指定端口的连接  

  2. XMPPTCPConnectionConfiguration config = XMPPTCPConnectionConfiguration.builder()  

  3.         .setUsernameAndPassword("username""password")  

  4.         .setServiceName("jabber.org")  

  5.         .setHost("earl.jabber.org")  

  6.         .setPort("8222)  

  7.         .build();  

  8. AbstractXMPPConnection conn2 = new XMPPTCPConnection(config);  

  9. conn2.connect();  


注意,在连接到XMPP服务器时,如果采用默认设置,会使用最大程度的安全,包括TLS加密的应用。ConnectionConfiguration类通过了对创建的连接的高级控制,比如可以开启或关闭加密。
以后可查看文档《XMPPConnection Management》:
https://github.com/igniterealtime/Smack/blob/master/documentation/connections.html
(目前还是无效的)

一旦你创建了一个连接后,你应该调用XMPPConnection.login()方法进行服务器登录。一旦登录后,你就可以通过创建Chat对象或GroupChat对象开始与其他用户聊天了。

七、Roster(名单)的用法

Roster用于跟踪其他用户是否在线。用户的联系人可以以分组的方式进行组织,比如“好友”、“同事”。然后就可以查看组中的每个用户是否在线了。
要检索Roster,使用XMPPConnection.getRoster()方法。Roster类允许你查找所有的Roster实体,以及他们属于哪个组,每个实体当前的在线状态。

八、读写Packet(数据包)

从 客户端发送到XMPP服务器的每一条消息都称为一个Packet(数据包)。org.jivesoftware.smack.packet库中包含了 XMPP支持的(消息Message、在线状态Presence、IQ)三种不同的基本数据包类型的封装类。而像Chat或GroupChat这样的类则 提供了更高层的结构来管理数据包的自动创建和发送。但是,开发者还是可以直接创建和发送数据包的。
下面的代码就是修改自己的在线状态,让其他人知道你不在线。

[java] view plaincopyprint?技术分享技术分享

  1. // 创建新在线状态对象,并设为离线状态  

  2. Presence presence = new Presence(Presence.Type.unavailable);  

  3. presence.setStatus("Gone fishing");  

  4. // 发送数据包(假设我们已经有XMPPConnection的连接实例con  

  5. con.sendPacket(presence);  


Smack提供了两种读取到来的数据包的方式:PacketListener(包监听器)和PacketCollector(包收集器)。
两者都使用PacketFilter实例来判断应该处理哪一个数据包。
PacketListener(包监听器)用于事件风格的编程,而PacketCollector(包收集器)有一个数据包的结果队列,你可以做轮询或阻塞等操作。
也就是说,如果你想在数据包到来时执行一些动作,那么包监听器很适合。如果你想等待指定的数据包的到来,那么包收集器很适合。
包收集器和包监听器都使用Connection连接实例创建。

三、XMPPConnection管理

1、创建连接

org.jivesoftware.smack.XMPPConnection类可管理到XMPP服务器的连接,它默认的连接实现类是org.jivesoftware.smack.XMPPTCPConnection。它主要使用两个构造方法,

一个是XMPPTCPConnection(StringserverName)方法,参数为服务器名。连接会使用所有默认的设置,有:

1)执行DNSSRV查询,找到服务器确切的地址和端口(通常是5222)。

2)与服务器协商最大数安全,包括TLS加密。但如果有必要,连接会回落到较低的安全设置。

3)XMPP资源名“Smack”会被用于连接。

第二个是XMPPTCPConnection(ConnectionConfigurationcc)构造器,它会指定高级的连接设置。其中包括:

1)手动指定服务器地址和端口,而不是通过DNSSRV查询。

2)能开启连接压缩。

3) 指定自定义的连接资源名(如Work或Home)。用户到服务器的每一个连接都必须有唯一的资源名。比如对于用 户"[email protected]",完整的带资源的地址应该是"[email protected]/Smack"。通过携带唯一的资源名, 用户可以同时从不同的位置登录到同一个服务器,这适用于多设备的情况。

每一个资源使用的在线优先级值:用于决定由哪一个带资源的指定连接来接收到裸地址"[email protected]"的消息。

 

2、连接和关闭连接

//为新连接创建配置

ConnectionConfigurationconfig = new ConnectionConfiguration(“jabber.org”, 5222);

AbstractXMPPConnectionconn = new XMPPTCPConnection(config);

//连接到服务器

conn.connect();

//登录到服务器

conn.login(“username”,“password”,“SomeResource”);

 

//关闭连接

conn.disconnect();

 

默认情况下,一旦连接断开,Smack会尝试重建连接。

使用ConnectionConfiguration类的setReconnectionAllowed(Boolean)方法可以开启或关闭重连的功能。

重连管理器会立即尝试重连到服务器,并且会增加延时设置,以便提高重连的成功率。

在重连管理器正在等待下一次重连的期间,如果你想强制重连,可以使用AbstractXMPPConnection类的connect()方法,它会尝试建立一个新连接。如果手动尝试也失败了,那么重连管理器会继续重连的工作。

四、使用Chat消息通信

来回收发消息是即时通信的核心功能。尽管单条消息是以包的形式发送和接收的,通常还是把他视为聊天的消息字符串,使用org.jivesoftware.smack.Chat类。

1、Chat类

一个聊天Chat会在两个用户之间创建一个消息线程(通过线程ID)。下面的代码片段演示了怎样创建一个新聊天,然后向用户发送一条文本消息:

//假设已经创建了一个名为"connection"的XMPPConnection

ChatManagerchatmanager = connection.getChatManager();

ChatnewChat = chatmanager.createChat("[email protected]", newMessageListener(){

    public void processMessage(Chat chat,Message message){

        System.out.println(“Receivedmessage: “+ message);

    }

});

try{

    newChat.sendMessage(“Howdy!”);

}catch(XMPPExceptione){

    System.out.println(“Error Deliveringblock”);

}

 

Chat.sendMessage(String) 方法可以方便地创建一个消息Message对象,用字符串参数设置消息正文Body,然后发送消息。在某些情况下你可能希望在发送消息前设置额外的值,使 用Chat.createMessage()方法和Chat.sendMessage(Message)方法,如下面的代码片段所示:

MessagenewMessage = new Message();

newMessage.setBody(“Howdy!”);

message.setProperty(“favoriteColor”,“red”);

newChat.sendMessage(newMessage);

 

前面的例子中,我们可以注意到,在创建聊天Chat时指定了一个消息监听器MessageListener,在任意时刻,当来自其它用户的聊天消息到达后,消息监听器会得到通知。下面的代码片段使用了监听器做鹦鹉学舌,它会回显来自其他用户传递的消息。

//假设在聊天Chat中已经设置了消息监听器MessageListener

publicvoid processMessage(Chat chat, Message message){

    // 把用户发送的消息内容发送给用户

    chat.sendMessage(message.getBody());

}

 

2、来电聊天

当 提示有另一个用户的聊天消息到了后,设置有轻微的不同,因为你是首次接收到聊天消息。取代明确地创建一个Chat来发送消息,当ChatManager创 建了Chat实例后,你需要注册处理新创建的Chat实例。ChatManager会通过线程ID找到匹配的Chat,如果Chat不存在,那么它会创建 一个新Chat对象来匹配。要得到这个新Chat,你必须注册来得到通知。可以注册一个消息监听器来接收所有要到来的消息。

//假定已经创建了名为”connection”的XMPPConnection

ChatManagercm = connection.getChatManager().addChatListener(new ChatManagerListener(){

    @Override

    public void chatCreated(Chat chat, BooleancreatedLocally){

        if(!createdLocally)

            chat.addMessageListener(newMyNewMessageListener());

    }

});

 

除 了基于线程的Chat消息,也有一些客户端不发送线程ID作为Chat的一部分。要处理这种情况,Smack会基于JID尝试匹配接收的消息到最匹配现有 的Chat。它会尝试用完整的JID来查找Chat,如果搜不到,再尝试用基本的JID来查找Chat。如果找不到现有的Chat来匹配,那么会创建一个 新Chat。

五、名单Roster和在线状态Presence

名单可以让你跟踪其他用户是否在线,而且名单可以让你把用户组织到群组,比如朋友群或工作群。而其它的即时通信IM系统则把名单Roster视为好友列表、联系人列表等等。

 

1、名单条目

名单中的每一个用户都由RosterEntry来表示,它包括:

1)一个XMPP地址(比如”[email protected]”)

2)你为用户编写的备注姓名(比如”Joe”)

3)名单中的群列表。如果名单的条目不属于任何群组,那么它被称为"unfiledentry"。

下面的代码片段会打印名单中所有的条目:

Rosterroster = connection.getRoster();

Collection<RosterEntry>entries = roster.getEntries();

for(RosterEntryentry : entries){

    System.out.println(entry);

}

 

还有获取单个条目的方法、获取"unfiledentry"的方法,获取一个群或所有群的方法。

 

2、在线状态

名单中的每个条目都有一个与之相关的在线状态。Roster.getPresence(Stringuser)方法会返回一个表示用户是否在线的Presence对象或者为空。为空是你还没有订阅用户是否在线的返回。

注意:通常情况下,在线状态的订阅总是绑定到名单中的用户,但这并不适应所有的情况。

 

一个用户的在线状态要么是在线,要么是离线。当用户在线时,他们的在线状态还可以包含扩展的信息,比如用户当前正在做什么,用户是否愿意被打扰等等。具体参考Presence类。

 

3、监听名单Roster和在线状态Presence的改变

Roster类的典型应用场景是以树状结构显示用户群和列表,并且用户列表中包含用户是否在线的状态。比如,参考下图所示的一个ExodusXMPP客户端的Roster。

 技术分享

在 线状态的信息可能会经常变化,Roster条目也可能经常修改或删除。要监听Roster和Presence数据的变化,你应该使用 RosterListener。要得到Roster改变的所有提醒,那么必须在登录XMPP服务器之前注册RosterListener。下面的代码片段 注册了一个Roster的RosterListener,它能够在标准输出中打印任何Presence的改变。一个标准的客户端可以使用类似的代码用变化 的信息来更新Roster界面。

Rosterroster = con.getRoster();

roster.addRosterListener(newRosterListener(){

    // 忽略事件public void entriesAdded(Collection<String> addresses){}

    public void entriesDeleted(Collection<String>addresses){}

    public voidentriesUpdated(Collection<String> addresses){}

    public void presenceChanged(Presencepresence){

        System.out.println(“Presencechanged: “+ presence.getFrom() + “ “ + presence);

    }

});

 

4、添加Entries到Roster

Roster 和Presence使用一种基于权限的模型,用户必须得到其他人的许可才能把这些人添加到Roster。这样可以保护用户的隐私,确保了只有获得同意的用 户才能查看到他们的Presence信息。因此,当你想添加某个用户到你的Roster中,必须得到该用户接受你的请求才可以。

如果有用户请求订阅你的在线状态Presence,这个用户必须先把你添加到他的Roster,因此他会发起请求,你必须选择接受或拒绝该请求。Smack通过以下三种方式来处理Presence的预订请求:

1)自动接受所有Presence的预订请求

2)自动拒绝所有Presence的预订请求

3)手动处理每一个Presence预订请求

这 三种方式可以通过Roster.setSubscriptionMode(intsubscriptionMode)方法来设置请求的处理方式。简单的客 户端通常使用第一种自动方式处理预订请求,而功能比较全的客户端应该选择第三种手动处理请求的方式,让终端用户自行决定是接受请求或是拒绝请求。如果使用 手动方式,应该注册一个PacketListener来监听Presence.Type.SUBSCRIBE类型的Presence包。


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。