XMPP Smack客户端指南

2025/03/28

1. 简介

XMPP是一个丰富且复杂的即时通讯协议。

在本教程中,我们不需要从头编写自己的客户端,而是使用Smack,这是一个用Java编写的模块化、可移植的开源XMPP客户端 它为我们完成了大部分繁重的工作。

2. 依赖

Smack被组织为几个模块以提供更大的灵活性,因此我们可以轻松地包含我们需要的功能。

其中包括:

  • XMPP模块
  • 支持XMPP标准基金会定义的许多扩展的模块
  • 旧版扩展支持
  • 用于调试的模块

我们可以在XMPP文档中找到所有支持的模块。

但是,在本教程中,我们仅使用tcp、im、extensions和java7模块:

<dependency>
    <groupId>org.igniterealtime.smack</groupId>
    <artifactId>smack-tcp</artifactId>
</dependency>
<dependency>
    <groupId>org.igniterealtime.smack</groupId>
    <artifactId>smack-im</artifactId>
</dependency>
<dependency>
    <groupId>org.igniterealtime.smack</groupId>
    <artifactId>smack-extensions</artifactId>
</dependency>
<dependency>
    <groupId>org.igniterealtime.smack</groupId>
    <artifactId>smack-java7</artifactId>
</dependency>

最新版本可以在Maven Repository找到。

3. 设置

为了测试客户端,我们需要一个XMPP服务器。为此,我们将在jabber.hot-chilli.net上创建一个帐户,这是一个面向所有人的免费Jabber/XMPP服务。

之后,我们可以使用XMPPTCPConnectionConfiguration类来配置Smack,该类提供了一个构建器来设置连接的参数:

XMPPTCPConnectionConfiguration config = XMPPTCPConnectionConfiguration.builder()
    .setUsernameAndPassword("tuyucheng","tuyucheng")
    .setXmppDomain("jabb3r.org")
    .setHost("jabb3r.org")
    .build();

构建器允许我们设置执行连接所需的基本信息,如果需要,我们还可以设置其他参数,例如端口、SSL协议和超时。

4. 连接

使用XMPPTCPConnection类即可轻松建立连接:

AbstractXMPPConnection connection = new XMPPTCPConnection(config);
connection.connect(); //Establishes a connection to the server
connection.login(); //Logs in

该类包含一个构造函数,用于接收先前构建的配置,它还提供了连接服务器和登录的方法。

一旦建立连接,我们就可以使用Smack的功能,比如聊天,我们将在下一节中描述。

如果连接突然中断,默认情况下,Smack将尝试重新连接。

由于连续重新连接不断失败,ReconnectionManager将尝试立即重新连接到服务器,并增加重试之间的延迟。

5. 聊天

该库的主要功能之一是聊天支持

使用Chat类可以在两个用户之间创建新的消息线程:

ChatManager chatManager = ChatManager.getInstanceFor(connection);
EntityBareJid jid = JidCreate.entityBareFrom("tuyucheng2@jabb3r.org");
Chat chat = chatManager.chatWith(jid);

请注意,为了构建聊天,我们使用了ChatManager,并且显然指定了与谁聊天。我们通过使用EntityBareJid对象实现了后者,该对象包装了一个XMPP地址(也称为JID),由本地部分(tuyucheng2)和域部分(jabb3r.org)组成。

之后,我们可以使用send()方法发送消息:

chat.send("Hello!");

并通过设置监听器接收消息:

chatManager.addIncomingListener(new IncomingChatMessageListener() {
    @Override
    public void newIncomingMessage(EntityBareJid from, Message message, Chat chat) {
        System.out.println("New message from " + from + ": " + message.getBody());
    }
});

5.1 房间

除端到端用户聊天外,Smack还通过使用房间提供对群聊的支持

房间有两种类型,即时房间和预订房间

即时房间可立即进入,并根据某些默认配置自动创建。另一方面,预订房间由房间所有者手动配置,然后才允许任何人进入。

让我们看看如何使用MultiUserChatManager创建即时房间:

MultiUserChatManager manager = MultiUserChatManager.getInstanceFor(connection);
MultiUserChat muc = manager.getMultiUserChat(jid);
Resourcepart room = Resourcepart.from("tuyucheng_room");
muc.create(room).makeInstant();

以类似的方式,我们可以创建一个预订房间:

Set<Jid> owners = JidUtil.jidSetFrom(
  new String[] { "tuyucheng@jabb3r.org", "tuyucheng2@jabb3r.org" });

muc.create(room)
    .getConfigFormManger()
    .setRoomOwners(owners)
    .submitConfigurationForm();

6. 名册

Smack提供的另一个功能是可以跟踪其他用户的存在

通过Roster.getInstanceFor(),我们可以获得一个Roster实例:

Roster roster = Roster.getInstanceFor(connection);

Roster是一个联系人列表,它将用户表示为RosterEntry对象,并允许我们将用户组织成组

我们可以使用getEntries()方法打印Roster中的所有条目:

Collection<RosterEntry> entries = roster.getEntries();
for (RosterEntry entry : entries) {
    System.out.println(entry);
}

此外,它允许我们使用RosterListener监听其条目和存在数据的变化:

roster.addRosterListener(new RosterListener() {
    public void entriesAdded(Collection<String> addresses) { // handle new entries }
    public void entriesDeleted(Collection<String> addresses) { // handle deleted entries }
    public void entriesUpdated(Collection<String> addresses) { // handle updated entries }
    public void presenceChanged(Presence presence) { // handle presence change }
});

它还提供了一种保护用户隐私的方法,即确保只有经过批准的用户才能订阅名册。为此,Smack实现了一种基于权限的模型

有3种方法可以使用Roster.setSubscriptionMode()方法处理状态订阅请求:

  • Roster.SubscriptionMode.accept_all:接受所有订阅请求
  • Roster.SubscriptionMode.reject_all:拒绝所有订阅请求
  • Roster.SubscriptionMode.manual:手动处理存在订阅请求

如果我们选择手动处理订阅请求,我们将需要注册一个StanzaListener(下一节介绍)并处理Presence.Type.subscribe类型的数据包。

7. 节

除了聊天之外,Smack还提供了一个灵活的框架来发送节并监听传入的节。

需要澄清的是,节是XMPP中一个离散的语义单位,它是通过XML流从一个实体发送到另一个实体的结构化信息

我们可以使用send()方法通过Connection传输Stanza:

Stanza presence = new Presence(Presence.Type.subscribe);
connection.sendStanza(presence);

在上面的例子中,我们发送了一个Presence节来订阅名册。

另一方面,为了处理传入的节,该库提供了两种构造:

  • StanzaCollector
  • StanzaListener

特别是,StanzaCollector允许我们同步等待新的节:

StanzaCollector collector = connection.createStanzaCollector(StanzaTypeFilter.MESSAGE);
Stanza stanza = collector.nextResult();

而StanzaListener是一个用于异步通知我们传入节的接口

connection.addAsyncStanzaListener(new StanzaListener() {
    public void processStanza(Stanza stanza) throws SmackException.NotConnectedException,InterruptedException, 
        SmackException.NotLoggedInException {
            // handle stanza
        }
}, StanzaTypeFilter.MESSAGE);

7.1 过滤器

此外,该库还提供了一组内置过滤器来处理传入的节

我们可以使用StanzaTypeFilter按类型过滤节,或者使用StanzaIdFilter按ID过滤节:

StanzaFilter messageFilter = StanzaTypeFilter.MESSAGE;
StanzaFilter idFilter = new StanzaIdFilter("123456");

或者,通过特定地址来辨别:

StanzaFilter fromFilter = FromMatchesFilter.create(JidCreate.from("tuyucheng@jabb3r.org"));
StanzaFilter toFilter = ToMatchesFilter.create(JidCreate.from("tuyucheng2@jabb3r.org"));

我们可以使用逻辑过滤运算符(AndFilter,OrFilter,NotFilter)来创建复杂的过滤器:

StanzaFilter filter = new AndFilter(StanzaTypeFilter.Message, FromMatchesFilter.create("tuyucheng@jabb3r.org"));

8. 总结

在本文中,我们介绍了Smack现成提供的最有用的类。

我们学习了如何配置库以发送和接收XMPP节。

随后,我们学习了如何使用ChatManager和Roster功能处理群聊。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章