【XMPP】Smack源码之初步认识
Smack 概述
Smack是一个用于和XMPP服务器通信的类库,由此可以实现即时通讯和聊天。
Smack主要优势
- 非常简单易用,并且有十分强大的 API。只需三行代码就可以向用户发关文本消息:
XMPPConnection connection = new XMPPConnection("jabber.org"); connection.login("mtucker", "password"); connection.createChat("jsmith@jivesoftware.com").sendMessage("Howdy!");
- 不像其它类库那样强制您进行包级别的编码。Smack提供智能的更高级的构造,像 Chat和GroupChat类,让您进行高效的编程。
- 不需用您熟悉XMPP XML格式,即使您熟悉 XML.
- 提供简单的设计以进行通讯,Smack允许您在每个消息中设置任意数量的属性,包括java对象。
- Apache许可下的开源类库,这意味着您可以将Smack整合进您的商业或非商业的应用中。
必要条件
Smack的唯一必要条件是JDK 1.2 或更高版本。smack.jar文件已包含一个XML解析器,不需要其它第三方类库。
建立连接
XMPPConnection类用来建立到XMPP服务器的连接。要建立SSL连接,要使用SSLXMPPConnection类。
下面是建立连接的例子:
// 建立一个到jabber.org服务器的连接。 XMPPConnection conn1 = new XMPPConnection("jabber.org"); // 通过一个特殊的端口建立一个到jabber.org服务器的连接。 XMPPConnection conn2 = new XMPPConnection("jabber.org", 5222); // 建立一个到jabber.org服务器的SSL连接。 XMPPConnection connection = new SSLXMPPConnection("jabber.org");
一旦您建立了一个连接,您必须通过方法XMPPConnection.login(String username, String password)使用用户名和密码登陆
如果登陆成功,您可以通过创建新的Chat或GroupChat对象和其它用户聊天。
操作Roster
Roster能够让您跟踪其它用户的有效性(存在)
您可以通过使用像“朋友”和“同事”这样的组来组织用户,这样您可以发现每个用户是否在线。
使用XMPPConnection.getRoster()这个方法得到Roster。
通过Roster类您可以找到所有Roster登陆、他们所属的组以及每个登陆当前的存在状态。
读写Packet
从客户端以XML格式发送到XMPP服务器的每个消息被称为一个“packet”。
org.jivesoftware.smack.packet包中包含了一些类,这些类封装了XMPP所允许的三个不同的基本packet类型(message, presence, 和 IQ)。
像Chat和GroupChat这样的类提供了更高类别的构造能够自动地创建和发送packet,但是您也可以直接创建和发送packet。
下面是一个通过改变您的presence来让别人知道您已无效,已经"out fishing"了:
// 创建一个新的presence. 传入false以指示我们已经无效了 Presence presence = new Presence(Presence.Type.UNAVAILABLE); presence.setStatus("Gone fishing"); // 发送packet (假设已经有了一个名为"con"的XMPPConnection实例). con.sendPacket(presence);
Smack提供两种方法读取收到的packet:
- PacketListener[packet监听器]和PacketCollector[packet收集器]。
- 二者都是使用PacketFilter实例来决定哪个packet应该被处理。
- packet监听器用于事件样式的编程,而packet收集器有一个可以做轮询和阻塞操作的packet的结果队列。
- 所以,当您想对一个有可能随时到来的packet采取一些操作时,使用packet监听器;
- 而当您想等待一个特别的packet到来时,使用packet收集器。
- 可以使用XMPPConnection实例创建packet收集器和监听器。
使用Chat 和 GroupChat发送消息
往复地发送消息处于即时通讯的核心地位。
两个类辅助发送和接收消息:
-
- org.jivesoftware.smack.Chat --用于在两个人之间发送消息。
- org.jivesoftware.smack.GroupChat --用于加入聊天室在多个人之间发送消息。
Chat和 GroupCha类都是使用 org.jivesoftware.smack.packet.Message packet类来发送消息。
在某种特定情况下,您可能不愿意使用高级的Chat和GroupChat类而直接发送和监听消息。
Chat
一个chat在两个用户之间创建一个消息线程(通过线程ID)。
下面这段代码演示了怎样和用户创建一个新的Chat并向他们发送一条文本消息:
// 假设我们已经创建了一个名为"connection"的XMPPConnection。 Chat newChat = connection.createChat("jsmith@jivesoftware.com"); newChat.sendMessage("Howdy!");
Chat.sendMessage(String)方法可以方便地创建一个Message对象,用字符串参数设置消息正文,然后发送消息。
在一定情况下您可能希望在发送消息前设置额外的值,使用Chat.createMessage()和 Chat.sendMessage(Message)方法,如下面代码片段所示:
// 假设我们已经创建了一个名为"connection"的XMPPConnection。 Chat newChat = connection.createChat("jsmith@jivesoftware.com"); Message newMessage = newChat.createMessage(); newMessage.setBody("Howdy!"); message.setProperty("favoriteColor", "red"); newChat.sendMessage(newMessage);
Chat对象能够让您很容易监听其它聊天参与者的回复。
下面这段代码演示的功能类似鹦鹉学舌--它将回复对方输入的一切消息。
// 假设我们已经创建了一个名为"connection"的XMPPConnection。 Chat newChat = connection.createChat("jsmith@jivesoftware.com"); newMessage.setBody("Hi, I'm an annoying parrot-bot! Type something back to me."); while (true) { // 等待用户发送给我们的下一条消息。 Message message = newChat.nextMessage(); // 将对方发送过来的消息原样发送给他。 newChat.sendMessage(message.getBody()); }
以上这段代码使用了这个Chat.nextMessage() 方法得到下一条消息,它将等待不确定何时到来的另一条消息。
当然也有其它的方法用于等待特定时间段到来的新消息,或者您可以添加一个监听器,它将在每次有消息到来时通知您。
GroupChat
通过GroupChat连接到服务器上的聊天室,您可以在一群人中发送和接收消息。但在您发送或接收消息之前,您必须用一个昵称加入聊天室。
下面这段代码演示了连接到一个聊天室并发送一条消息:
// 假设我们已经创建了一个名为"connection"的XMPPConnection。 GroupChat newGroupChat = connection.createGroupChat("test@jivesoftware.com"); // 用昵称"jsmith"加入这处群。 newGroupChat.join("jsmith"); // 向聊天室中的其它人发送一条消息。 newGroupChat.sendMessage("Howdy!");
通常,在群中发送和接收消息和在Chat类中非常相似。
同时还提供了用于得到聊天室中其它人的列表的方法。
处理收到的Packet
Smack提供灵活的框架来通过两种构造处理收到的 packet:
-
- org.jivesoftware.smack.PacketCollector —— 一个让您同步等待新packet的类。
- org.jivesoftware.smack.PacketListener —— 一个异步通知您引入的packet的接口。
packet监听器用于事件样式的编程,而packet收集器有一个可以做轮询和阻塞操作的packet的结果队列。
所以,当您想对一个有可能随时到来的packet采取一些操作时,使用packet监听器;而当您想等待一个特别的packet到来时,使用packet收集器。
您可以使用XMPPConnection实例创建packet收集器和监听器。
org.jivesoftware.smack.filter.PacketFilter 接口决定哪个特别的将会被传递到PacketCollector或PacketListener。
org.jivesoftware.smack.filter package包中有许多预定义的过滤器。
下面的代码片段演示注册了一个packet收集器和一个packet 监听器:
// 创建一个packet过滤器来监听来自一个特定用户的新的消息 //我们可以使用一个AndFilter来结合其它两个过滤器。 PacketFilter filter = new AndFilter(new PacketTypeFilter(Message.class), new FromContainsFilter("mary@jivesoftware.com")); // 假设我们已经创建了一个名为"connection"的XMPPConnection。 // 首先,用我们创建的过滤器注册一个packet收集器。 PacketCollector myCollector = connection.createPacketCollector(filter); // 通常,您应该用收集器来些什么,像等待新的packet。 // 接下来,创建一个packet监听器。我们可以简便地使用匿名内部类。 PacketListener myListener = new PacketListener() { public void processPacket(Packet packet) { // 在这里用收到的packet做些什么。 } }; // 注册这个监听器。 connection.addPacketListener(myListener, filter);
标准Packet过滤器
Smack包括丰富的packet 过滤器集,当然您可以通过实现PacketFilter接口创建自己的过滤器。
默认的过滤器集包括:
-
- PacketTypeFilter ——特定类的packet过滤器。
- PacketIDFilter ——含有特定packet ID的packet过滤器。
- ThreadFilter ——含有特定线程ID的消息packet过滤器。
- ToContainsFilter ——发送到特定地址的packet过滤器。
- FromContainsFilter ——来自特定地址的packet过滤器。
- PacketExtensionFilter ——含有特定packet扩充的packet过滤器 filters for s that have a particular extension.
- AndFilter ——实现两个过滤器的逻辑“与”操作。
- OrFilter —— 实现两个过滤器的逻辑“或”操作。
- NotFilter ——实现一个过滤器的逻辑“非”操作。
Packet属性
Smack提供一个有效的机制,可以向packet附加任意属性。
每个属性有一个String名字,这是一个java简单类型值(int, long, float, double, boolean)and a value that is a Java primitive () 或者任何序列化对象(java对象可序列化当它实现了Serializable接口)。
使用API
所有主要对象支持属性,如Message对象。
下面的代码显示如何设置属性:
Message message = chat.createMessage(); // 添加一个Color对象作为属性。 message.setProperty("favoriteColor", new Color(0, 0, 255)); // 添加一个int作为属性。 message.setProperty("favoriteNumber", 4); chat.sendMessage(message);
使用如下代码获得这些属性:
Message message = chat.nextMessage(); // 获得一个Color对象属性。 Color favoriteColor = (Color)message.getProperty("favoriteColor"); // 获得一个intg属性,注意属性作为对象返回,我们必须把值转换为Integer,然后转换为int。 int favoriteNumber = ((Integer)message.getProperty("favoriteNumber")).intValue();
对象作为属性
使用对象作为属性值是一个非常强大和容易的交换数据的方式。
然而,应该牢记如下:
-
- Packet extension有更多标准方法添加额外数据到XMPP节。在某些情况下使用属性可能更方便,由于Smack自身会做处理XML的工作。
- 当你将Java对象作为属性发送时,只有运行着Java的客户端能够解释数据。所以,应该考虑将数据转换为一系列简单类型的值来代替Java对象。
- 作为属性值发送的对象必须实现Serialiable。另外,发送端和接收端都必须有同种的类,否则当系列化对象时会发生序列化异常。
- 序列化的对象可能会很大,这将使用更多的带宽和服务器资源。
XML格式
当前用于发送属性数据XML格式还不规范,所以很可能难以被不使用Smack的客户端识别。
XML犹如下面所示(附清晰的注释):
<!--某块中的所有属性。 --> <properties xmlns="http://www.jivesoftware.com/xmlns/xmpp/properties"> <!-- 首选,一个名为"prop1"的integer型值。--> <property> <name>prop1</name> <value type="integer">123</value> <property> <!-- 其次,一个序列化的Java对象,然后从二进制数据转换到base-64编码的文本。 --> <property> <name>blah2</name> <value type="java-object">adf612fna9nab</value> <property> </properties>
前支持的类型有:integer, long, float, double, boolean, string, 和java对象。
roster和presence
roster能让您跟踪其它用户的有效性(存在)。您可以通过使用像“朋友”和“同事”这样的组来组织用户。
其它IM系统如朋友列表,联系列表引用roster。
一个roster实例通过XMPPConnection.getRoster()方法获得,但仅当成功登陆服务器之后对可用。
名薄登陆
在roster中每个用户用一个RosterEntry表示,它包括:
-
- 一个XMPP地址(例如 jsmith@example.com)。
- 一个您分配给用户的名字(例如 "Joe")。
- 登陆所属的roster组列表。如果roster登陆不属于任何组,它将被称为“unfiled entry”。
下面的代码片段打印roster中的所有登陆:
Roster roster = con.getRoster(); for (Iterator i=roster.getEntries(); i.hasNext(); ) { System.out.println(i.next()); }
也可能用方法获得单个登陆,未定义登陆列表,或者获得一个或所有roster组。
presence
roster中的每个登陆有presence与之关联。
Roster.getPresence(String user)方法可以返回一个用户Presence的对象,如果用户不在线或您没有预订用户的presence将会返回null。
注意:一般而言,presence预订一般受用户是否在roster中的约束,但这并不适应所有情况。
一个用户可以有在线或离线两种presence。
当用户在线时,他们的可能包含外延信息,如他们正在做什么,他们是否愿意被打扰等等。参考Presence类以获得更多细节信息。
监听roster和presence的变化
roster类的典型应用就是显示组的树型视图和含有当前presence值的登陆。
presence信息很可能经常变化,roster登陆也可能经常改变或被删除。
为了监听roster和presence数据的变化,应该使用RosterListener。
下面的代码片段注册了一个roster的RosterListener,它能够在标准输出中打印任何presence的变化。
一个标准的客户端可以使用类似的代码用变化的信息来更新roster用户界面。
final Roster roster = con.getRoster(); roster.addRosterListener(new RosterListener() { public void rosterModified() { // 这个例子中忽略这个事件。 } public void presenceChanged(String user) { // 如果presence无效,将会打印"null", // 这对本例来说很不错。 System.out.println("Presence changed: " + roster.getPresence(user)); } });
向roster中添加登陆
roster和presence使用一种基于许可的模式,用户只有在被许可的情况下才能被添加到别人的roster中。
这样可以保护用户的隐私因为只有经核准的其它用户才能查看他们的 presence信息。
因此,只有当其它用户接受您的请求时您才能添加新的roster登陆。
如果一个用户请求presence预订,因此他们可以把您添加到他们的roster中,您必须接受或拒绝该请求。
Smack通过以下三种方式中的一种处理presence预订请求:
-
- 自动接受所有presence预订请求。
- 自动拒绝所有presence预订请求。
- 手动处理presence预订请求。
通过Roster.setSubscriptionMode(int subscriptionMode)方法设置对请求的处理方式。
简易客户端通常使用一种自动方式处理预订请求,而复杂客户端应该手动处理方式,请最终用户接受或拒绝请求。
如果使用手动方式,应该注册一个PacketListener以监听Presence.Type.SUBSCRIBE类型的Presence packet。
Privacy
Privacy是什么
Privacy是用户阻挡其它个别用户的通信的方法。在XMPP中它通过操作隐私列表完成。
通过下面的用例服务器端隐私列表能够成功完成:
- 重新获得某人的隐私列表。
- 添加,删除,并编辑某人的隐私列表。
- 设置,更改,或放弃活动列表。
- 设置,更改,或放弃默认列表(也就是默认活动的列表)。
- 基于JID,组,或签名类型(或全部)允许或阻挡消息。
- 基于JID,组,或签名类型(或全部)允许或阻挡向内的出席通知。
- 基于JID,组,或签名类型(或全部)允许或阻挡向外的出席通知。
- 基于JID,组,或签名类型(或全部)允许或阻挡IQ节。
- 基于JID,组,或签名类型(或全部)允许或阻所有通信。
怎么使用它
API实现有三个主公共类:
- PrivacyListManager: 这是重新获得并处理服务器隐私列表的主API类。
- PrivacyList: 代表一个隐私列表,有一个名字,一组隐私项目。例如,可见或不可见列表。
- PrivacyItem:阻挡或允许隐私的某个方面。例如,允许我的的朋友看到我们的出席状态。
1.正确从头开始,客户端可以获得他的/她的存储在服务器上的隐私列表:
// 为当前连接创建一个隐私管理器。 PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection); // 重新获得服务器隐私列表。 PrivacyList[] lists = privacyManager.getPrivacyLists();
正在客户端能够显示服务器的每个PrivacyItem和每个列表是否是活动的,默认或没有。客户端是一个隐私变化的监听器。
2.要向服务器添加一个新列表,客户端可以像这样执行:
// 设置列表的名称 String listName = "newList"; // 创建PrivacyItem的列表,PrivacyItem将会允许或拒绝某些隐私方面。 String user = "tybalt@example.com"; String groupName = "enemies"; ArrayList privacyItems = new ArrayList(); PrivacyItem item = new PrivacyItem(PrivacyRule.JID, true, 1); item.setValue(user); privacyItems.add(item); item = new PrivacyItem(PrivacyRule.SUBSCRIPTION, true, 2); item.setValue(PrivacyRule.SUBSCRIPTION_BOTH); privacyItems.add(item); item = new PrivacyItem(PrivacyRule.GROUP, false, 3); item.setValue(groupName); item.setFilterMessage(true); privacyItems.add(item); // 获得当前连接的隐私管理器。 PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection); // 创建新列表。 privacyManager.createPrivacyList(listName, Arrays.asList(privacyItems));
3.修改一个已存的列表,客户端代码可能像这样:
// 设置列表名称 String listName = "existingList"; //获得当前连接的隐私管理器。 PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection); // 向服务器发送新列表。 privacyManager.updatePrivacyList(listName, items);
注意items在例2中定义并且必须包含列表中的所有元素(not the "delta")。
4.删除一个已存在的列表,客户端可以像这样执行:
// 设置列表名称 String listName = "existingList"; // 获得当前连接的隐私管理器。 PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection); // 删除列表。 privacyManager.deletePrivacyList(listName);
5.放弃活动列表的使用,客户端可以像这样执行:
// 获得当前连接的隐私管理器。 PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection); // 放弃活动列表的使用。 privacyManager.declineActiveList();
6.放弃默认列表的使用,客户端可以像这样执行:
// 获得当前连接的隐私管理器。 PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection); // 放弃默认列表的使用。 privacyManager.declineDefaultList();
监听Privacy变化
为了处理隐私变化,客户端应该监听管理器的更新。
当一个列表更改时管理器通知每个已添加的监听器。
监听必须实现PrivacyListListener接口。当隐私列表被修改时客户端可能需要作出反应。
PrivacyListManager让您添加监听器,它将在列表被改变时得知通知。监听器应该实现PrivacyListListener接口
最重要的通知是updatedPrivacyList,它当隐私列表改变它的隐私项目时被执行。
当执行如下代码监听器能得到通知:
// 获得当前连接的隐私管理器。 PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection); // 为了得到通知添加监听器(this) privacyManager.addListener(this);