norgul / xmpp-php
PHP 的 XMPP 库。
Requires
- php: >=7.1
Requires (Dev)
- friendsofphp/php-cs-fixer: 3.0.x-dev
- phpmd/phpmd: 2.6.0
- phpunit/phpunit: 6.*
- squizlabs/php_codesniffer: 3.4.2
README
这是由于网络上缺乏此类库(至少是我能找到的具有良好文档的库),因此为使 PHP 能够与 XMPP 通信而实现的低级别套接字实现。
XMPP 核心文档可以在 这里 找到。
安装要求和示例
项目要求在 composer.json
中给出( Composer 网站)
您可以通过运行以下命令在项目中使用此库
composer require norgul/xmpp-php
您可以通过更改凭据指向您的 XMPP 服务器,在项目根目录下运行 php Example.php
来在 Example.php
文件中查看使用示例。
库使用
初始化
为了开始使用库,您首先需要实例化一个新的 Options
类。主机、用户名和密码是必填字段,而端口号如果省略,则默认为 5222
,这是 XMPP 的默认端口号。
用户名可以是裸 JID
或 JID/resource
形式。如果您使用的是裸 JID
,则资源将被自动添加。您可以通过使用 $client->iq->setResource()
显式设置资源来覆盖此操作。在第二种情况下,用户名将自动解析为 username
和 resource
变量。在 JID/resource/xyz
格式中,第二个斜杠之后的所有内容都将被忽略。如果同时存在 JID/resource
以及使用 $client->iq->setResource()
方法,则最后定义的将具有优先权。
$options = new Options();
$options
->setHost($host) // required
->setPort($port) // not required, defaults to 5222
->setUsername($username) // required
->setPassword($password); // required
Options
对象是建立连接和每个后续请求所必需的,因此一旦设置,就不应该更改。
设置此对象后,您可以实例化一个新的 XmppClient
对象,并将 Options
对象传递进去。
XMPP 客户端类说明
由于 XMPP 主要涉及 3 个主要协议(IQ、消息和 Presence),因此我创建了依赖于套接字实现的独立类,以便您可以通过调用协议方法直接发送 XML。
这意味着从 XmppClient
类构造函数中提供了 3 个协议,可以像客户端具体类上的链式方法一样使用。
当前的逻辑是 $client->STANZA->METHOD()
。例如
$client->iq->getRoster();
$client->message->send();
$client->presence->subscribe();
连接到服务器
除了作为协议包装器之外,XmppClient
类还提供了一些公共方法。
$client->connect()
方法执行以下操作
- 连接到在
XmppClient
构造函数中初始化的套接字 - 打开与 XMPP 服务器交换的 XML 流
- 根据提供的凭据尝试与服务器进行身份验证
- 开始与服务器进行初始通信,这是最基本的,以帮助您开始
当前版本支持 PLAIN
和 DIGEST-MD5
身份验证方法。
默认支持 TLS。如果服务器支持 TLS,则库将自动尝试使用 TLS 连接,并使连接安全。
如果您想显式禁用此功能,可以在Options
实例上使用setUseTls(false)
函数来禁用TLS通信。请注意,这将在支持TLS但不是必需的环境中使用。如果TLS是必需的,程序将独立于您设置的选项连接到它。
发送原始数据
send()
消息在XmppClient
类中公开为公共的,其目的是将原始XML数据发送到服务器。为了正确工作,您发送的XML必须是有效的XML。
获取原始响应
可以使用$client->getResponse()
获取服务器响应(或更确切地说,是服务器端持续的XML会话)。如果您想看到来自服务器的所有内容的连续流,您可以在无限循环或某些WebSocket解决方案(如Ratchet)中使用它。
如果您想将接收到的响应的输出显示在控制台中,可以调用$client->prettyPrint($response)
方法。
接收消息和其他响应
如果您对来自服务器的完整响应不感兴趣,您也可以使用$client->message->receive()
(已删除$client->getMessages()
,因为它只是此方法的简写)来匹配消息标签与正则表达式,并返回匹配的消息数组。如果您想在终端中查看响应,可以这样做
do {
$response = $client->message->receive();
if($response)
echo print_r($response);
} while (true);
断开连接
断开连接方法向服务器发送关闭XML以结束当前打开的会话并关闭打开的套接字。
Stanza方法分解
请记住从这里 -> $client->STANZA->METHOD()
消息
send()
- 向某人发送消息。它接受3个参数,其中最后一个参数是可选的。第一个参数是要发送的实际消息(正文),第二个参数是消息的接收者,第三个参数是要发送的消息类型。默认为chat
。
您可以在此RFC文档中找到可能类型。
receive()
- 在本节中介绍
IQ
getRoster()
- 不接受任何参数并获取当前已验证用户的联系人列表。
setGroup()
- 将指定的用户放入您提供的组中。方法接受两个参数:第一个参数是您将附加到指定用户的组名,另一个参数是该用户的JID。
Presence
setPriority()
- 为指定的资源设置优先级。第一个参数是一个整数-128 <> 127
。如果没有第二个参数,则优先级将设置为您当前使用的资源。其他资源可以作为第二个参数提供,而优先级将设置为此特定资源。
subscribe()
- 接受JID作为参数并请求该用户的状态。
acceptSubscription()
- 接受来自该用户的请求。
declineSubscription()
- 拒绝来自该用户的请求。
会话
会话目前仅用于区分多个连接时生成的日志。
XmppClient
类接受第二个可选参数$sessionId
,您可以将其从您的系统转发,或者它将自动分配。
您可以通过Options
对象($options->setSessionManager(false)
)禁用会话,因为它们可能在框架或类似情况下与已建立的会话发生冲突。不用说,如果禁用,则将第二个参数转发到XmppClient
不会建立新的会话。
更多选项(不是必需的)
Options
对象可以接受更多选项,这些选项可以是链式的,但不是必需的。这些在Options
类中的代码中直接进行了解释和注释。
$options
->setProtocol($protocol) // defaults to TCP
->setResource($resource) // defaults to 'norgul_machine_' string + timestamp
->setLogger($logger) // logger instance (logging explained below)
->setAuthType($authType) // Takes on classes which implement Authenticable
套接字选项
大多数套接字选项默认设置,因此无需修改此类,然而您可以在执行socket_read()
时更改套接字存活期间的超时时间,这可以通过$socket->setTimeout()
来实现。
日志记录
在建立新的会话后,库会在logs/
文件夹中创建一个xmpp.log
日志文件。
您可以通过在实例化Options
时使用setLogger($logger)
来手动设置记录器。该方法接受实现Loggable
接口的任何对象,因此您可以创建自己的实现。
有趣的事实:这曾经是一个PSR-3
日志接口,但我认为在开发的这个阶段这是过度设计的。
其他
Example.php
中有一个sendRawXML()
方法,这在调试时非常有用。该方法的工作方式是您可以提供手写的XML并将其发送到服务器。另一方面,您也可以通过提供方法名称而不是XML来触发方法。
Enter XML: <xml>foo</xml> <-- will send XML
Enter XML: getRoster <-- will run getRoster() method
Enter XML: requestPresence x@x.com <-- will run with argument requestPresence(x@x.com)
一些有效的XMPP XML可能会被拒绝(如发送<presence/>
),因为simplexml_load_string()
无法将其解析为有效的XML。在您需要进行此类自定义操作且确信它是有效的XMPP XML的情况下,您可以删除解析行,并让send()
方法自行处理。
请注意!请务必注意!向服务器发送无效的XML可能会使当前打开的XML会话无效,并且您可能需要重新启动脚本。这是一个高度实验性的功能,实际上并未得到维护。这是一个被遗弃的方法,其父母已经离开并且匆忙。它可能有一天会成为哈利·波特,但是嘿...我们都清楚地怀疑这一点。你可能是个特别的人,但没有人喜欢你。向前,孤儿之家!方法(只是开玩笑,我不会删除它)!
谁说readme很无聊。
开发文档
对于愿意贡献的人来说,这是一个关于结构的简要概述
Options.php
- 库中所有可变的内容Socket.php
- 套接字相关实现(连接、读取、写入等)XmppClient.php
- 与库交互的用户友好方法以及启用用户通过实例化类调用stanza方法的包装器。这应该包含尽可能少的逻辑,但事实证明这并不容易 :)
-
AuthTypes
- 包含用于验证自己到XMPP服务器的各种方法。除了具体实现外,还有一个包含少量逻辑的抽象类以避免重复,以及一个包含所有必要方法的接口,以便在需要新的认证类型时使用。 -
Buffers
- 缓冲区实现(或者说是一个简单的数组),当套接字调用receive()
方法时会填充,并在任何读取时刷新,例如在调用getResponse()
方法时。简要的历史背景:我在遇到不可恢复的错误时遇到了问题。在这种情况下,我必须做两件事:尝试重新连接,向用户显示错误。问题是getResponse()
返回字符串,而在重新连接的情况下,程序执行将继续返回空或返回错误字符串,因为服务器已经连接了第二次,从而误导用户之前发生的错误。缓冲区就是这样诞生的。 -
Exceptions
- 这基本上是一个标准。我只是覆盖了构造函数,以便我可以获得我的消息。 -
Loggers
- 包含将日志存储到logs/xmpp.log
文件的逻辑。想法是将几种日志类型放在里面(完整、简单、无记录器),但我发现一个就足够了。
Xml
Xml.php
- 一个由过多正则表达式匹配组成的特质。这部分应该重新格式化。Stanzas
- 与服务器进行所有stanza通信的主要逻辑。这曾经只是普通的XML,但我决定在内部转发socket依赖,这样当你调用方法时,你实际上也将其发送到服务器。这曾经类似于$this->socket->send($this->iq->getRoster())
,这在编程角度上是正确的,但为了简单起见,我更喜欢$client->iq->getRoster()
。我对其他建议持开放态度。
CI
通过 Travis CI 进行持续集成,每次推送都会经过一个目前非常简单的流程:
- 检查单元测试(天知道我有很多)
- 检查语法错误
- 运行
phpcs
(配置在phpcs.xml
中) - 运行
phpmd
(配置在phpmd.xml
中)
TODO
- 单元测试 - 很遗憾,我已经推迟了很长时间,也许有喜欢编写测试的好心人。
- 限速 - 当发生不可恢复的错误时(目前我正在捕获导致流中断的
<stream:error>
错误),程序将自动尝试重新连接。如果这种情况反复发生,程序将无限期地尝试连接,这在除日志之外的所有方面都是可以接受的,因为日志可能会变得拥挤。我希望限速连接,使其在每次失败时增加连接时间。问题在于我只能捕获响应时的错误,而响应可能在第一次XML交换时成功(例如,当你发送打开流请求时),而在第二次请求时中断。考虑到这一点,我唯一想到的方法是使用时间戳或其他方法来实现限速。 - 会话 - 我假设这部分工作正常,但应该从框架中进行测试
- 多个连接 - 我认为这部分工作得很好,但我担心在同时获取消息时触发
getRoster()
可能会删除一个服务器响应。如果你在一次批量操作中同时获取罗盘和消息,它将被添加到缓冲区。回调响应将得到罗盘或消息,而不是两者。然后缓冲区将被刷新。这是一个需要思考的问题。 - XmppClient 的结构 - 为了启用
$client->stanza->method
,我需要在类中实例化所有stanza。我觉得这可以简化。