聊天是一种一对多的通讯方式,每个人都可以发送消息并从其他所有人接收消息。
这两个动作(发送、接收)会持续发生。因此看起来像一个无限循环,用户可以进入(加入聊天)并退出(离开聊天)。
- 进入
- 发送消息
- 接收消息
- 退出
因此,客户端的循环看起来像这样(伪代码):
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有一种类似于内存缓存桥的东西,应该使存储操作更加快速和方便)。
读取事务脚本同样简单,它将只读取所有ID高于传递给它的消息,并将其返回给客户端。
保持这些脚本尽可能简单,并优化读写存储时间,以便它们可以快速执行,即使在使用纯HTTP聊天时也能完成。
尽管有
keep-alive,但您的Web服务器和整体互联网连接可能仍然不够快。
然而,暂时使用HTTP应该足以测试您的聊天系统是否正常工作,而没有任何循环,无论是客户端还是服务器端。
保持服务器简单也很重要,因为每个客户端都依赖于它们,所以它们只需要完成自己的工作就行了。
您可以随时更改服务器(或提供不同类型的服务器),通过给聊天客户端不同的发送和接收函数实现与聊天客户端交互。例如,我在您的问题中看到您正在使用comet,这也应该可以工作,直接实现comet服务器可能很容易。
如果将来Websockets更易于访问(由于安全考虑可能永远不会发生),您也可以为Websockets提供另一种类型的服务器。 只要流的数据结构完好无损,这将与其他类型的服务器并存。 数据库将负责协调。
希望这有所帮助。
额外提醒:HTML5 提供了一项名为 服务器推送事件(Server-Sent Events) 的功能,同时提供了一个 在线示例 和 PHP/JS 源代码。该 HTML 5 功能已经在 JavaScript 中提供了一个事件对象,可用于创建示例聊天客户端传输实现。