聊天是一对多的通信,而每个人都可以发送消息,也可以接收其他人的消息。
这两个动作(发送、接收)连续发生。所以这看起来像是一个无限循环,而用户可以进入(加入聊天)和退出(离开聊天)。
- 输入
- 发送消息
- 接收消息
- 退出
所以循环在客户端看起来像这样(伪代码):
while (userInChat)
{
if (userEnteredMessages)
{
userSendMessages(userEnteredMessages)
}
if (chatNewMessages)
{
displayMessages(chatNewMessages)
}
}
正如您在问题中已经指出的那样,问题在于为网站实施这种聊天。
要为网站实现这样的“循环”,您首先面临的情况是您不希望在这里有一个实际的循环。只要用户在聊天,它就会运行,运行,运行。所以你想随着时间的推移分配循环的执行。
为此,您可以将其转换为事件函数的集合:
ChatClient
{
function onEnter()
{
}
function onUserInput(messages)
{
sendMessages = send(messages)
display(sendMessages)
}
function onReceive(messages)
{
display(messages)
}
function onExit()
{
}
}
现在可以触发事件而不是循环。剩下的就是随着时间的推移触发这些事件的实现,但目前这并不是很有趣,因为它取决于聊天数据交换的实际实现方式。
总是有一个远程点,聊天客户端(以某种方式)连接到该点以发送它自己的消息并从中接收新消息。
这是某种聊天消息流。同样,这看起来像一个循环,但实际上它是一个流。就像在聊天客户端循环中一样,它会在某个时间点连接到流上,并从该流中发送输入(写入)和接收输出(读取)。
这在上面的 ChatClient 伪代码中已经可见,当用户输入一条或多条消息时会发生一个事件,然后将发送(写入)。并且在 onReceive 事件函数中可以读取消息。
由于流是按顺序排列的数据,因此需要有顺序。由于这都是基于事件的并且有多个客户端可用,因此需要一些专门的处理。由于顺序是相对的,它只会在它的上下文中起作用。上下文可能是时间(一条消息在另一条消息之前出现),但如果聊天客户端有另一个时钟作为服务器或另一个客户端,我们不能使用现有时钟作为消息顺序的时间源,因为它通常在 WAN 中的计算机之间有所不同。
相反,您可以创建自己的时间来排列所有消息。通过所有客户端和服务器的共享时间,可以实现有序流。这可以通过在中心位置对消息进行编号来轻松完成。幸运的是,您的聊天有一个中心位置,即服务器。
消息流从第一条消息开始,到最后一条消息结束。因此,您只需将第一条消息编号为 1,然后每条新消息将获得下一个更大的编号。我们称之为消息 ID。
因此,无论您将使用哪种服务器技术,聊天都知道消息的类型:带有 ID 的消息和不带 ID 的消息。这也表示消息的状态:不是流的一部分或一部分。
非流相关消息是用户已经输入但尚未发送到服务器的消息。当服务器接收到“免费”消息时,它可以通过分配 ID 将它们放入流中:
function onUserInput(messages)
{
sendMessages = send(messages)
display(sendMessages)
}
正如这个伪代码示例所示,这就是这里发生的事情。 onUserInput 事件获取尚不属于流的消息。 sendMessages 例程将返回其流式表示,然后显示。
然后,显示例程能够按流顺序显示消息。
因此,不管客户端/服务器通信是如何实现的,使用这样的结构,您实际上可以粗略地处理基于消息的聊天系统并将其与底层技术解耦。
服务器唯一需要做的就是接收消息,给每条消息一个 ID 并返回这些 ID。 ID 的分配通常在服务器将消息存储到它的数据库时完成。一个好的数据库会负责正确地对消息进行编号,所以没有什么可做的。
另一个交互是从服务器读取新消息。为了通过网络有效地做到这一点,客户端告诉服务器它喜欢从哪个消息中读取。然后,服务器会将自该时间 (ID) 以来的消息传递给客户端。
如图所示,从一开始的“无休止”循环,它现在变成了一个基于事件的远程调用系统。由于远程调用很昂贵,最好让它们能够通过一个连接传输大量数据。其中一部分已经在伪代码中,因为可以向服务器发送一条或多条消息,并一次从服务器接收零条或多条消息。
理想的实现是与服务器建立一个连接,允许以全双工方式向其读取和写入消息。然而,javascript 中还没有这样的技术。这些东西正在使用 Websockets 和 Webstream API 等进行开发,但现在让我们把事情简单化,看看我们拥有什么:无状态 HTTP 请求、服务器上的一些 PHP 和 MySQL 数据库。
可以在数据库表中表示消息流,该表具有用于存储消息的 ID 和其他字段的自动递增唯一键。
写入事务脚本只会连接到数据库、插入消息并返回 ID。这是一个非常常见的操作,应该很快(mysql 有一种 memcache 桥,它应该使存储操作更加快速和方便)。
读取事务脚本同样简单,它只会读取所有 ID 高于传递给它的消息并将其返回给客户端。
使这些脚本尽可能简单并优化存储的读/写时间,因此它们可以快速执行,即使通过普通 HTTP 聊天也可以完成。
您的网络服务器和整体互联网连接可能仍然不够快(尽管有keep-alive)。
但是,目前 HTTP 应该足以测试您的聊天系统是否真的在没有任何循环、客户端或服务器端的情况下工作。
让服务器保持简单也很好,因为每个客户端都依赖它们,所以他们应该只做自己的工作,仅此而已。
您可以随时更改可以与您的聊天客户端交互的服务器(或提供不同类型的服务器),方法是为聊天客户端提供不同的发送和接收功能实现。例如。我在您的问题中看到您正在使用彗星,这应该也可以,直接为彗星实现服务器可能很容易。
如果将来 websocket 更易于访问(出于安全考虑,可能永远不会出现这种情况),您也可以为 websocket 提供另一种类型的服务器。只要流的数据结构完好无损,这将适用于彼此相邻的不同类型的服务器。数据库将负责一致性。
希望这有帮助。
作为附加说明:HTML5 提供了一个名为 Stream Updates with Server-Sent Events 的东西,带有 online demo 和 PHP/JS sources。 HTML 5 功能已经在 javascript 中提供了一个事件对象,可用于创建示例性聊天客户端传输实现。