15. 旧文档

这包含有关旧功能的部分,或与Tigase旧版本有关的信息。它被保存在这里用于存档目的。

15.1. Tigase DB架构解释

架构基础知识、外观和对所有行的简要说明可以在 架构文件列表 中找到。然而,这还不足以理解它是如何工作的以及如何访问所有数据。只有3个基本表实际上保存了所有Tigase服务器用户的数据:tig_userstig_nodestig_pairs。因此,起初并不清楚Tigase的数据是如何组织的。

在您了解Tigase XMPP服务器数据库架构、它是如何工作以及如何使用它之前,了解它的开发目标是什么以及为什么它以这种方式工作是必不可少的。让我们从API开始,因为它为您提供了最好的介绍。

可以通过以下方法进行简化访问:

void setData(BareJID user, String key, String value);
String getData(BareJID user, String key);

还有更复杂的版本:

void setData(BareJID user, String subnode, String key, String value);
String getData(BareJID user, String subnode, String key, String def);

尽管API包含更多方法,但其余的或多或少是上面介绍的变体。 UserRepository 接口中的JavaDoc文档中提供了所有访问方法的完整API描述接口。因此,除了主要想法之外,我们不会在这里过多地讨论细节。

Tigase对单个用户数据的 <key, value> 对进行操作。这背后的想法是想让API非常简单,同时也非常灵活,因此添加新插件或组件不需要更改数据库架构、添加新表或将数据库架构转换为新版本。

结果,UserRepository 接口暴露给Tigase的所有代码,主要是组件和插件(我们称它们为模块)。这些模块只需调用 set/get 方法来存储或访问模块特定的数据。

由于插件或组件是独立开发的,开发人员很容易选择相同的键名来存储一些信息。为了避免数据库中的键名冲突,引入了’节点’概念。因此,大多数模块在设置/获取键值时还提供了一个子节点部分,在大多数情况下,它只是XMLNS或其他一些唯一字符串。

‘节点’有点像文件系统中的目录,它可能包含子节点,这使得Tigase数据库表现得像一个层次结构。并且符号也类似于文件系统,您只使用 / 来分隔节点级别。在实践中,您可以像这样组织数据库:

user-name@domain  --> (key, value) pairs
                   |
               roster -->
                       |
                     item1 --> (key1, value1) pairs.
                       |
                     item2 --> (key1, value1) pairs.

因此,要从名册中访问item1的数据,您可以调用如下方法:

getData("user-name@domain", "roster/item1", key1, def1);

这对开发人员来说是极大的方便,因为他可以专注于模块逻辑,而不用担心数据存储实现和组织。特别是在原型阶段,它加快了开发速度,并允许使用不同的解决方案进行快速实验。在实践中,以这种方式访问用户名册的效率非常低,因此名册的存储方式略有不同,但你已经明白了此方法。此外,在某些地方使用了更复杂的API,允许更直接地访问数据库并以针对场景优化的任何格式存储数据。

现在这种层次结构是在SQL数据库之上实现的,但最初Tigase的数据库是作为XML结构实现的,所以它自然而简单。

在SQL数据库中,我们用三个表模拟层次结构:

  1. tig_users - 包含主要用户数据、用户ID (JID)、可选密码、活动标志、创建时间和帐户的一些其他基本属性。所有这些实际上都可以存储在tig_pairs中,但出于性能原因,它们位于一个位置,可以通过一个简单的查询快速访问它们。

  2. tig_nodes - 是实现层次结构的表。当Tigase在XML数据库中存储数据时,层次结构非常复杂。但是,在SQL数据库中,它会导致对数据的访问非常缓慢,并且现在大多数组件都使用更扁平的结构。请注意,每个用户的条目都有一个叫做根节点的东西,它由’root’字符串表示;

  3. tig_pairs - 这是所有用户信息以<key, value>对的形式存储的表。

所以我们现在知道数据是如何组织的。现在我们将学习如何使用SQL查询直接访问数据库中的数据。

假设我们有一个用户’admin@test-d’,我们要为其检索花名册。我们可以简单地执行查询:

select pval
  from tig_users, tig_pairs
  where user_id = 'admin@test-d' and
        tig_users.uid = tig_pairs.uid and
        pkey = 'roster';

但是,如果多个模块将数据存储在单个用户的键’roster’ 下,我们将收到多个结果。要访问正确的’roster’,我们还必须知道这个特定键的节点层次结构。主要用户名册存储在 ‘root’ 节点下,因此查询如下所示:

select pval
  from tig_users, tig_nodes, tig_pairs
  where user_id = 'admin@test-d' and
            tig_users.uid = tig_nodes.uid and
            node = 'root' and
            tig_users.uid = tig_pairs.uid and
           pkey = 'roster';

tig_pairs 表中信息的准确存储方式取决于特定模块。对于名册,它看起来有点像XML内容:

<contact jid="all-xmpp-test@test-d" subs="none" preped="simple" name="all-xmpp-test"/>

15.2. 为什么是最新的JDK?

原因有很多,但主要原因是我们是一个致力于源代码的小团队。所以整个方法是让我们的生活更轻松,让项目更容易维护,开发更高效。

这是列表:

  • 易于维护 - 该项目不使用第三方库,这使得该项目更易于维护。这简化了特定版本库之间的兼容性问题。这也将编码与单个库包统一起来,而不必依赖可能不受支持的特定版本。

  • 易于部署 - 不使用第三方工具的另一个原因是使终端用户更容易安装和使用服务器。

  • 高效开发 - 由于没有使用第三方库,Tigase需要自己实现很多东西,或者尽可能多地使用JDK功能。我们尝试尽可能多地使用JDK提供的现有库,其余的都是自定义编码的。

JDKv5的哪些特性对Tigase开发至关重要?为什么我不能简单地重新实现一些代码以使其与早期的JDK版本兼容?

  • SSL/TLS 的非阻塞 I/O - 这是在JDK-1.4中不能简单地重新实现的功能。由于整个服务器都使用NIO,因此对SSL和TLS使用阻塞I/O是没有意义的。

  • SASL - 这可以对JDK-1.4重新实现而无需付出太多努力。

  • Concurrent package - 这可以为JDK-1.4重新实现,但需要做很多工作。这是服务器的关键部分,因为它使用多线程和并发处理。

  • Security package - 安全包有许多扩展,否则这些扩展无法与早期版本的JDK一起使用。

  • LinkedHashMap - 在JDKv6中是Tigase缓存实现的基础。

  • Light HTTP server - JDKv6提供了实现HTTP绑定 (JEP-0124) 和HTTP用户界面以监控服务器活动并使用服务器配置所需的内置轻型HTTP服务器。

随着JDK的改进,我们的编程也随之改进,因为我们获得了使用新方法,高效率,有时甚至是捷径的能力。

目前Tigase需要 JDKv8,我们建议根据需要经常更新它!

15.3. Tigase服务器中虚拟域管理的API说明

本指南的目的是介绍Tigase服务器中的虚拟主机管理。有关本指南未涵盖的所有具体细节,请参阅JavaDoc文档。所有接口都有很好的文档记录,您可以将现有实现用作示例代码库和参考点。 VHost管理文件位于存储库中,您可以使用 source viewer 浏览它们。

Tigase中的虚拟主机管理可以通过灵活的API以多种方式进行调整。虚拟域管理的核心元素是接口 VHostManager <https://github.com/tigase/tigase-server/blob/master/src/main/java/tigase/vhosts/VHostManager.java>`__类。他们负责向其余的Tigase服务器组件提供虚拟主机信息。特别是 `MessageRouter 类,其控制XMPP数据包如何在服务器内部流动。

您最有可能要重新实现的类是 VHostJDBCRepository 其用作默认虚拟主机存储和实现 VHostRepository 接口。您可能需要拥有自己的实现才能在Tigase自己的数据存储之外存储和访问虚拟主机。如果您要通过Tigase以外的系统修改虚拟域列表,这一点尤其重要。

非常基本的虚拟主机存储由 VHostItem 类提供。这是只读存储,当具有虚拟主机的数据库为空或不可访问时,它会在首次启动时为服务器提供引导虚拟主机数据。因此,建议所有 VHostItem 实现扩展此类。示例代码在 VHostJDBCRepository 文件中提供。

所有可能需要虚拟主机信息或想要与虚拟主机管理子系统交互的组件都应该实现 VHostListener 接口。在某些情况下,实现此接口对于接收数据包进行处理是必要的。

虚拟主机信息在Tigase服务器内部以2种形式执行:

  1. 作为带有域名的 String

  2. 作为一个 VHostItem,它包含了包括域名在内的所有域信息,此域的最大用户数,域是启用还是禁用等等。 JavaDoc文档包含有关所有可用字段和用法的所有详细信息。

以下是所有接口和类的完整列表,以及每个接口和类的简要说明:

  1. VHostManagerIfc - 是用于访问所有其他虚拟主机信息的接口服务器组件。该接口有一个默认实现:VHostManager

  2. VHostListener - 是一个允许组件与 VHostManager 交互的接口。 其交互是双向的。 VHostManager向组件提供虚拟主机信息,组件提供一些控制数据,以便将数据包正确路由到组件。

  3. VHostRepository - 是一个用于存储和加载虚拟域列表的接口数据库或任何其他存储介质。此接口有2个实现:VHostConfigRepository 加载vhosts信息配置文件并提供只读存储- 和 VHostJDBCRepository 类扩展 VHostConfigRepository 并允许读取和保存虚拟域列表。 Tigase服务器将VHostJDBCRepository作为默认存储库加载。

  4. VHostItem - 是一个允许访问所有虚拟域属性的接口。有时域名不足以进行数据处理。该域可能被暂时禁用,可能有有限数量的用户等等。此类的实例保留有关服务器组件可能需要的域的所有信息。

  5. VHostManager - VHostManagerIfc接口的默认实现。它为组件提供虚拟主机信息并管理虚拟主机列表。处理用于重新加载、更新和删除域的临时命令。

  6. VHostConfirRepository - VHostRepository 的一个非常基本的实现,用于从配置中加载域列表文件。

  7. VHostJDBCRepository - Tigase服务器加载的 VHostRepository 的默认实现。它允许在通过UserRepository访问的数据库中读取和存储虚拟域列表。

15.3.1. 扩展虚拟域设置

在某些情况下,需要扩展虚拟域以添加一些额外的设置。从8.1.0版开始,可以使用 VHostItemExtensionVHostItemExtensionProvider

为此,您需要创建一个实现 VHostItemExtension 的类。此类将保存每个虚拟主机的设置值。需要使其可序列化为 Element 并且可从 Element 反序列化。此外,需要使该类的值可以通过临时命令进行修改。

建议提供额外的方法允许您访问此类的值。

此外,您需要将 VHostItemExtensionProvider 接口作为bean实现并返回 VHostItemExtension 实现的类。

用于 SeeOtherHostVHostItemExtension 的示例 VHostItemExtensionProvider 实现

@Bean(name = SeeOtherHostVHostItemExtension.ID, parent = VHostItemExtensionManager.class, active = true)
public static class SeeOtherHostVHostItemExtensionProvider implements VHostItemExtensionProvider<SeeOtherHostVHostItemExtension> {

    @Override
    public String getId() {
        return SeeOtherHostVHostItemExtension.ID;
    }

    @Override
    public Class<SeeOtherHostVHostItemExtension> getExtensionClazz() {
        return SeeOtherHostVHostItemExtension.class;
    }
}

15.4. 节限制

尽管XMPP很强大并且可以处理任意大小的节(以字节为单位),但对于Tigase服务器有一些限制需要牢记。

在使用默认Tigase设置和创建自定义节时请记住这些。

  • 限制单个元素的属性数 = 50个属性

  • 元素数量限制 = 1024个元素

  • 元素名称长度限制 = 1024个字符

  • 属性名称长度限制 = 1024个字符

  • 属性值长度限制 = 10240个字符

  • 单个元素CDATA的内容长度限制 = 1048576b或1Mb

这些值可能会改变。

请注意,这些限制是针对可能在一个节中的元素和属性,但不限制整个节的长度。

15.4.1. 转义字符

如果某些特殊字符包含在节中,则需要对其进行转义以避免冲突。这些规则类似于普通的XML转义。以下是需要转义的字符列表以及使用什么来转义它们:

&    &amp;
<    &lt;
>    &gt;
"    &quot;
'    &apos;

15.5. Tigase Server 5.x中的API更改

此信息适用于旧版本的TIGASE

仅当您开发自己的代码以在Tigase服务器中运行时,API更改才会对您产生影响。这些更改并不广泛,但在某些情况下可能需要在几个文件中进行许多简单的更改。

所有更改都与引入 tigase.xmpp.JID 和 tigase.xmpp.BareJID 类有关。建议将它们用于对用户JID执行的所有操作,而不是更改之前使用的String类。

使用新类有一些优点。首先他们对所有的用户JID进行检查和解析,他们也做stringprep处理。因此,如果您使用由JID或 BareJID实例保存的数据,您可以确定它们是有效且正确的。

然而,这些并不是所有优点。 JID解析代码似乎使用了大量大功率的CPU来执行它的操作。 JID和部分JID用于节处理的许多地方,并且在所有这些地方一遍又一遍地执行解析,浪费了CPU周期、内存和时间。因此,如果JID在解析后在所有进一步的节处理中被重用,则可以从这些新类中获得巨大的性能优势。

这就是 tigase.server.Packet 类派上用场的地方。 Packet类的实例包含XML节并预解析一些节中最常用的元素、节源地址和目标地址。作为一种效果,该类中提供了所有新方法:

JID getStanzaFrom();
JID getStanzaTo();
JID getFrom();
JID getTo();
JID getPacketFrom();
JID getPacketTo();

尽管以下方法仍然可用,但已被弃用:

String getStanzaFrom();
String getStanzaTo();

请参考 Packet 类和方法的JavaDoc文档来学习所有的这些方法的详细信息以及它们之间的区别。

另一个区别是您不能再使用构造函数创建 Packet 实例。相反,有一些工厂方法可用:

static Packet packetInstance(Element elem);
static Packet packetInstance(Element elem,
    JID stanzaFrom, JID stanzaTo);

同样,请参阅JavaDoc文档了解所有详细信息。使用这些方法的要点是它们实际上返回以下类之一的实例,而不是 Packet 类:IqPresenceMessage

还有一些实用方法可以帮助创建Packet实例的副本,以保留尽可能多的预解析数据:

Packet copyElementOnly();
Packet errorResult(...);
Packet okResult(...);
Packet swapFromTo();
Packet swapStanzaFromTo();

我们尽量保持 JavaDoc 文档尽可能完整。如果您发现信息缺失或不正确,请与我们联系。

要点是尽可能地在代码中重用 JIDBareJID 实例。您永远不会知道,您的代码可能会在负载为每秒100k的XMPP数据包的高负载系统中运行。

另一个变化。这个有点冒险,因为很难找到所有可以使用它的地方。有几个实用程序类和方法可以接受节的源地址和目标地址并产生一些东西。它们很混乱,因为其中一些是源地址,而另一些则是目标地址。所有代码都经过重构,以保持所有位置的参数顺序相同。目前的政策是:源地址优先。因此,在所有有以下方法的地方:

Packet method(String to, String from);

它已更改为:

Packet method(JID from, JID to);

据我所知,这些方法中的大部分都是我自己使用的,所以我不希望其他开发人员有太多麻烦。