1、Smack库概述html
Smack是一个开源、易用的XMPP/Jabber客户端库,它使用Java语言开发,由Jive Software开发。java
Smack的优势是编程简单。git
Smack的缺点是其API并不是为大量并发用户设计,每一个客户都要1个线程,占用资源相对较,所以用Smack作模拟测试时,1台机器只能模拟有限(数千个)客户。github
截止2014年11月27日,Smack库已经发展到4.0.6版。编程
最新的好消息是Smack在4.1.0版后将直接支持Android系统,而无需再使用之前的Smack移植版aSmack库了。安全
Smack库源码托管于GitHub,主页见: https://github.com/igniterealtime/Smack/服务器
2、Smack 4的改变并发
Smack库从3.4版发展到4.0.x版后,其API有较大的变化,主要有:ide
一、把Connection类重命名为XMPPConnection类测试
XMPPConnection类是XMPPTCPConnection类和XMPPBOSHConnection类的父类。
二、把各类Provider类进行了分包
三、keep-alive(持久链接)机制从smack-core库移到了smack-extensions库
keep-alive机制如今由PingManager类提供。
四、PrivacyList类的toString()方法重命名为getName()
五、当Chat实例的全部引用都撤掉后,应该调用Chat.close()方法
不然Chat对象会有内存泄露的隐患,直到ChatManager对象被垃圾回收器回收后内存泄露隐患才会消失。
六、ServerTrustManager类被移除了
若是要使用带SSL认证的XMPP,你只需提供本身的SSLContext对象给ConnectionConfiguration对象便可。
七、Packet.setProperty()从smack-core库移到了smack-extensions库
其API如今能够在org.jivesoftware.smackx.jiveproperties包中找到。
八、Connection.getAccountManager()方法如今改为了AccountManager.getInstance(XMPPConnection)方法
九、异常API作了改进
十、ToContains过滤器被移除了
3、Smack库的特征
一、极度简单易用,API功能强大
发送一条文本消息给某个用户只需几行代码:
[java] view plaincopyprint?
AbstractXMPPConnection connection = new XMPPTCPConnection("mtucker", "password", "jabber.org");
connection.connect();
connection.login();
Chat chat = ChatManager.getInstanceFor(connection)
.createChat("jsmith@jivesoftware.com", new MessageListener(){
public void processMessage(Chat chat, Message message){
System.out.println("Received message: " + message);
}
});
chat.sendMessage("Howdy!");
二、隔离了底层数据包组装的复杂性,天然有相应的库来完成这些功能。Smack提供了更智能的高层构造,好比Chat类和Roster类,这样开发会更富有效率。
1)无需熟悉XMPP的XML格式,甚至都不须要了解XML
2)提供了简单的M2M通讯
Smack让开发者能够对每条消息都设置大量的属性,属性中还能够包含Java对象。
3)基于Apache许可证的开源代码,这意味着你能够把Smack放入你本身的商业软件中。
4、Smack库的组成
Smack库能够内嵌到任意的Java应用程序中。Smack库有数个JAR文件组成,很是具备灵活性。
一、smack-core.jar
提供了核心XMPP功能。都是XMPP RFC规范定义的XMPP特性。
二、smack-extensions.jar
支持许多由XMPP Standards Foundation定义的扩展(XEP)功能。包括群聊、文件传输、用户搜索等等。
之后可查看文档《扩展手册》:
https://github.com/igniterealtime/Smack/blob/master/documentation/extensions/index.html
(目前仍是无效的)
三、smack-experimental.jar
支持许多由XMPP Standards Foundation定义的体验性(XEP)功能。其API和功能特性都被认为是不稳定的。
四、smack-legacy.jar
支持许多由XMPP Standards Foundation定义的遗留(XEP)功能。
五、smack-bosh.jar
支持BOSH通讯(XEP-0124规范定义的)。此代码被认为处于Beta阶段。
六、smack-jingle.jar
支持Jingle。此代码很老,目前处于无维护的状态。
七、smack-resolver-dnsjava.jar
支持对DNS SRV记录的解析,主要用于那些不支持javax.naming API的平台。
八、smack-debug.jar
用于协议流量的加强型GUI调试器。当调试模式开启后,若是它在类路径下,它会自动被使用。
之后可查看文档《调试模式》:
https://github.com/igniterealtime/Smack/blob/master/documentation/debugging.html
(目前仍是无效的)
5、Smack的配置
Smack的初始化过程涉及到2阶段的调用。
一、初始化系统属性
经过SmackConfiguration类初始化全部的系统可访问属性,这些属性都是经过getXXX方法取回属性值的。
二、初始化启动类
任意类若是继承了SmackInitializer接口后,均可以在调用initialize()方法后获得初始化,这意味着获得初始化的类在启动后都是活动的。
若是没有继承SmackInitializer接口,那么要实现初始化,必需要放置一个静态代码块来实现——他在类装载时会自动执行。
初 始化是经过配置文件来完成的。默认状况下,Smack会载入Smack JAR文件中内嵌的配置文件(它位于org.jivesoftware.smack/smack-config.xml)。这个指定的配置文件包含了一系 列需载入初始化的类列表。全部的管理器类型的类都须要被初始化,这些管理器类就包含在上面所说的初始化列表中。
6、创建链接的例子
XMPPConnection类用于建立一个到XMPP服务器的链接,代码例子以下:
[java] view plaincopyprint?
// 建立一个到jabber.org服务器的链接
AbstractXMPPConnection conn1 = new XMPPTCPConnection("username", "password", "jabber.org");
conn1.connect();
[java] view plaincopyprint?
// 建立一个到jabber.org服务器指定端口的链接
XMPPTCPConnectionConfiguration config = XMPPTCPConnectionConfiguration.builder()
.setUsernameAndPassword("username", "password")
.setServiceName("jabber.org")
.setHost("earl.jabber.org")
.setPort("8222)
.build();
AbstractXMPPConnection conn2 = new XMPPTCPConnection(config);
conn2.connect();
注意,在链接到XMPP服务器时,若是采用默认设置,会使用最大程度的安全,包括TLS加密的应用。ConnectionConfiguration类经过了对建立的链接的高级控制,好比能够开启或关闭加密。
之后可查看文档《XMPPConnection Management》:
https://github.com/igniterealtime/Smack/blob/master/documentation/connections.html
(目前仍是无效的)
一旦你建立了一个链接后,你应该调用XMPPConnection.login()方法进行服务器登陆。一旦登陆后,你就能够经过建立Chat对象或GroupChat对象开始与其余用户聊天了。
7、Roster(名单)的用法
Roster用于跟踪其余用户是否在线。用户的联系人能够以分组的方式进行组织,好比“好友”、“同事”。而后就能够查看组中的每一个用户是否在线了。
要检索Roster,使用XMPPConnection.getRoster()方法。Roster类容许你查找全部的Roster实体,以及他们属于哪一个组,每一个实体当前的在线状态。
8、读写Packet(数据包)
从 客户端发送到XMPP服务器的每一条消息都称为一个Packet(数据包)。org.jivesoftware.smack.packet库中包含了 XMPP支持的(消息Message、在线状态Presence、IQ)三种不一样的基本数据包类型的封装类。而像Chat或GroupChat这样的类则 提供了更高层的结构来管理数据包的自动建立和发送。可是,开发者仍是能够直接建立和发送数据包的。
下面的代码就是修改本身的在线状态,让其余人知道你不在线。
[java] view plaincopyprint?
// 建立新在线状态对象,并设为离线状态
Presence presence = new Presence(Presence.Type.unavailable);
presence.setStatus("Gone fishing");
// 发送数据包(假设咱们已经有XMPPConnection的链接实例con
con.sendPacket(presence);
Smack提供了两种读取到来的数据包的方式:PacketListener(包监听器)和PacketCollector(包收集器)。
二者都使用PacketFilter实例来判断应该处理哪个数据包。
PacketListener(包监听器)用于事件风格的编程,而PacketCollector(包收集器)有一个数据包的结果队列,你能够作轮询或阻塞等操做。
也就是说,若是你想在数据包到来时执行一些动做,那么包监听器很适合。若是你想等待指定的数据包的到来,那么包收集器很适合。
包收集器和包监听器都使用Connection链接实例建立。
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)。用户到服务器的每个链接都必须有惟一的资源名。好比对于用 户"jsmith@example.com",完整的带资源的地址应该是"jsmith@example.com/Smack"。经过携带惟一的资源名, 用户能够同时从不一样的位置登陆到同一个服务器,这适用于多设备的状况。
每个资源使用的在线优先级值:用于决定由哪个带资源的指定链接来接收到裸地址"jsmith@example.com"的消息。
//为新链接建立配置
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()方法,它会尝试创建一个新链接。若是手动尝试也失败了,那么重连管理器会继续重连的工做。
来回收发消息是即时通讯的核心功能。尽管单条消息是以包的形式发送和接收的,一般仍是把他视为聊天的消息字符串,使用org.jivesoftware.smack.Chat类。
一个聊天Chat会在两个用户之间建立一个消息线程(经过线程ID)。下面的代码片断演示了怎样建立一个新聊天,而后向用户发送一条文本消息:
//假设已经建立了一个名为"connection"的XMPPConnection
ChatManagerchatmanager = connection.getChatManager();
ChatnewChat = chatmanager.createChat("jsmith@jivesoftware.com", 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());
}
当 提示有另外一个用户的聊天消息到了后,设置有轻微的不一样,由于你是首次接收到聊天消息。取代明确地建立一个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。
名单可让你跟踪其余用户是否在线,并且名单可让你把用户组织到群组,好比朋友群或工做群。而其它的即时通讯IM系统则把名单Roster视为好友列表、联系人列表等等。
名单中的每个用户都由RosterEntry来表示,它包括:
1)一个XMPP地址(好比”jsmith@example.com”)
2)你为用户编写的备注姓名(好比”Joe”)
3)名单中的群列表。若是名单的条目不属于任何群组,那么它被称为"unfiledentry"。
下面的代码片断会打印名单中全部的条目:
Rosterroster = connection.getRoster();
Collection<RosterEntry>entries = roster.getEntries();
for(RosterEntryentry : entries){
System.out.println(entry);
}
还有获取单个条目的方法、获取"unfiledentry"的方法,获取一个群或全部群的方法。
名单中的每一个条目都有一个与之相关的在线状态。Roster.getPresence(Stringuser)方法会返回一个表示用户是否在线的Presence对象或者为空。为空是你尚未订阅用户是否在线的返回。
注意:一般状况下,在线状态的订阅老是绑定到名单中的用户,但这并不适应全部的状况。
一个用户的在线状态要么是在线,要么是离线。当用户在线时,他们的在线状态还能够包含扩展的信息,好比用户当前正在作什么,用户是否愿意被打扰等等。具体参考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);
}
});
Roster 和Presence使用一种基于权限的模型,用户必须获得其余人的许可才能把这些人添加到Roster。这样能够保护用户的隐私,确保了只有得到赞成的用 户才能查看到他们的Presence信息。所以,当你想添加某个用户到你的Roster中,必须获得该用户接受你的请求才能够。
若是有用户请求订阅你的在线状态Presence,这个用户必须先把你添加到他的Roster,所以他会发起请求,你必须选择接受或拒绝该请求。Smack经过如下三种方式来处理Presence的预订请求:
1)自动接受全部Presence的预订请求
2)自动拒绝全部Presence的预订请求
3)手动处理每个Presence预订请求
这 三种方式能够经过Roster.setSubscriptionMode(intsubscriptionMode)方法来设置请求的处理方式。简单的客 户端一般使用第一种自动方式处理预订请求,而功能比较全的客户端应该选择第三种手动处理请求的方式,让终端用户自行决定是接受请求或是拒绝请求。若是使用 手动方式,应该注册一个PacketListener来监听Presence.Type.SUBSCRIBE类型的Presence包。