如何使用Jquery/PHP实现聊天室?

27

我想使用PHP/Javascript(Jquery)来实现一个包含群聊和私聊功能的聊天室。

问题在于如何以自然的方式连续更新界面,还可能如何在私聊中显示'X正在输入……'消息。

明显的方法似乎是每隔X秒/毫秒,javascript向服务器发出ping请求,并获取上次ping到现在之间的新消息列表。但是,如果聊天室突然被洪水般的五条消息淹没,则界面可能看起来有点不自然。我希望每个消息都能在输入时出现。

是否有一种方法使javascript与服务器保持持续连接,服务器将任何新消息推送到此连接,而javascript将其添加到界面中,使它们几乎在服务器接收到它们后立即出现?

我知道有一些轮询选项需要安装一些Apache模块等,但我很糟糕的系统管理员,因此我更喜欢在共享主机帐户上安装非常容易的解决方案,或者仅使用php/mysql的解决方案。


PHP in CLI=Yes, PHP in HTTP call=No, PHP + Javascript=Possible cause JavaScript can fire multi requestsPHP在CLI中=是PHP在HTTP调用中=否PHP + Javascript=可能会导致JavaScript触发多个请求 - ajreal
@Ajreal - 如果您能够发布一些示例代码以展示如何完成它,我将不胜感激。 - Ali
1
我今天实际上正在看这个网站: http://css-tricks.com/chat2/它不会自动更新,但绝对为PHP/jQuery聊天室提供了一个背景。 祝好运! - switz
9个回答

46

使用PHP/AJAX/JSON进行聊天

我使用了这本书/教程来编写我的聊天应用:

AJAX和PHP:构建响应式Web应用程序:第5章:AJAX聊天和JSON

它展示了如何从头开始编写完整的聊天脚本。


基于Comet的聊天

您也可以使用CometPHP

来自:zeitoun

Comet使Web服务器能够向客户端发送数据,而无需客户端请求。因此,这种技术将比经典的 AJAX 产生更加响应的应用程序。在经典的 AJAX 应用程序中,Web浏览器(客户端)无法实时通知服务器数据模型已更改。用户必须创建请求(例如,通过单击链接)或必须定期发生 AJAX 请求以获取来自服务器的新数据。

我将向您展示两种使用PHP实现Comet的方法。例如:

  1. 基于隐藏的<iframe>使用服务器时间戳
  2. 基于经典 AJAX 非返回请求

第一种方法在客户端上实时显示服务器日期,然后显示迷你聊天。

方法1:iframe + 服务器时间戳

您需要:

  • 一个后端PHP脚本来处理持久的HTTP请求backend.php
  • 一个前端HTML脚本加载JavaScript代码index.html
  • 原型JS库,但您也可以使用jQuery

后端脚本(backend.php)将执行无限循环,并在客户端连接时返回服务器时间。

<?php
header("Cache-Control: no-cache, must-revalidate");
header("Expires: Sun, 5 Mar 2012 05:00:00 GMT");
flush();
?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

<head>
    <title>Comet php backend</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>

<body>
<script type="text/javascript">
// KHTML browser don't share javascripts between iframes
var is_khtml = navigator.appName.match("Konqueror") || navigator.appVersion.match("KHTML");
if (is_khtml)
{
  var prototypejs = document.createElement('script');
  prototypejs.setAttribute('type','text/javascript');
  prototypejs.setAttribute('src','prototype.js');
  var head = document.getElementsByTagName('head');
  head[0].appendChild(prototypejs);
}
// load the comet object
var comet = window.parent.comet;
</script>

<?php
while(1) {
    echo '<script type="text/javascript">';
    echo 'comet.printServerTime('.time().');';
    echo '</script>';
    flush(); // used to send the echoed data to the client
    sleep(1); // a little break to unload the server CPU
}
?>
</body>
</html>

前端脚本(index.html)创建了一个名为“comet”的JavaScript对象,它将把后端脚本连接到时间容器标签。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Comet demo</title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <script type="text/javascript" src="prototype.js"></script>

</head>
<body>
  <div id="content">The server time will be shown here</div>

<script type="text/javascript">
var comet = {
connection   : false,
iframediv    : false,

initialize: function() {
  if (navigator.appVersion.indexOf("MSIE") != -1) {

    // For IE browsers
    comet.connection = new ActiveXObject("htmlfile");
    comet.connection.open();
    comet.connection.write("<html>");
    comet.connection.write("<script>document.domain = '"+document.domain+"'");
    comet.connection.write("</html>");
    comet.connection.close();
    comet.iframediv = comet.connection.createElement("div");
    comet.connection.appendChild(comet.iframediv);
    comet.connection.parentWindow.comet = comet;
    comet.iframediv.innerHTML = "<iframe id='comet_iframe' src='./backend.php'></iframe>";

  } else if (navigator.appVersion.indexOf("KHTML") != -1) {

    // for KHTML browsers
    comet.connection = document.createElement('iframe');
    comet.connection.setAttribute('id',     'comet_iframe');
    comet.connection.setAttribute('src',    './backend.php');
    with (comet.connection.style) {
      position   = "absolute";
      left       = top   = "-100px";
      height     = width = "1px";
      visibility = "hidden";
    }
    document.body.appendChild(comet.connection);

  } else {

    // For other browser (Firefox...)
    comet.connection = document.createElement('iframe');
    comet.connection.setAttribute('id',     'comet_iframe');
    with (comet.connection.style) {
      left       = top   = "-100px";
      height     = width = "1px";
      visibility = "hidden";
      display    = 'none';
    }
    comet.iframediv = document.createElement('iframe');
    comet.iframediv.setAttribute('src', './backend.php');
    comet.connection.appendChild(comet.iframediv);
    document.body.appendChild(comet.connection);

  }
},

// this function will be called from backend.php  
printServerTime: function (time) {
  $('content').innerHTML = time;
},

onUnload: function() {
  if (comet.connection) {
    comet.connection = false; // release the iframe to prevent problems with IE when reloading the page
  }
}
}
Event.observe(window, "load",   comet.initialize);
Event.observe(window, "unload", comet.onUnload);

</script>

</body>
</html>

方法2:AJAX非返回请求

您需要与方法1相同的内容+用于数据交换的文件(data.txt

现在,backend.php将执行两件事:

  1. 当有新消息发送时,写入“data.txt”
  2. 只要“data.txt”文件未更改,就会执行无限循环
<?php
$filename  = dirname(__FILE__).'/data.txt';

// store new message in the file
$msg = isset($_GET['msg']) ? $_GET['msg'] : '';
if ($msg != '')
{
    file_put_contents($filename,$msg);
    die();
}

// infinite loop until the data file is not modified
$lastmodif    = isset($_GET['timestamp']) ? $_GET['timestamp'] : 0;
$currentmodif = filemtime($filename);
while ($currentmodif <= $lastmodif) // check if the data file has been modified
{
    usleep(10000); // sleep 10ms to unload the CPU
    clearstatcache();
    $currentmodif = filemtime($filename);
}

// return a json array
$response = array();
$response['msg']       = file_get_contents($filename);
$response['timestamp'] = $currentmodif;
echo json_encode($response);
flush();
?>
前端脚本(index.html)创建包含来自"data.txt"文件的聊天消息的<div id="content"></div>标签,最后创建一个名为"comet"的javascript对象,该对象将调用后端脚本以监视新的聊天消息。
每当接收到新消息并且每当发布新消息时,"comet"对象将发送AJAX请求。持久连接仅用于监视新消息。使用时间戳url参数来标识上次请求的消息,这样服务器将仅在"data.txt"时间戳更新时返回。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Comet demo</title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <script type="text/javascript" src="prototype.js"></script>
</head>
<body>

<div id="content">
</div>

<p>
<form action="" method="get" onsubmit="comet.doRequest($('word').value);$('word').value='';return false;">
  <input type="text" name="word" id="word" value="" />
  <input type="submit" name="submit" value="Send" />
</form>
</p>

<script type="text/javascript">
var Comet = Class.create();
Comet.prototype = {

timestamp: 0,
url: './backend.php',
noerror: true,

initialize: function() { },

connect: function()
{
  this.ajax = new Ajax.Request(this.url, {
    method: 'get',
    parameters: { 'timestamp' : this.timestamp },
    onSuccess: function(transport) {
      // handle the server response
      var response = transport.responseText.evalJSON();
      this.comet.timestamp = response['timestamp'];
      this.comet.handleResponse(response);
      this.comet.noerror = true;
    },
    onComplete: function(transport) {
      // send a new ajax request when this request is finished
      if (!this.comet.noerror)
        // if a connection problem occurs, try to reconnect each 5 seconds
        setTimeout(function(){ comet.connect() }, 5000); 
      else
        this.comet.connect();
      this.comet.noerror = false;
    }
  });
  this.ajax.comet = this;
},

disconnect: function()
{
},

handleResponse: function(response)
{
  $('content').innerHTML += '<div>' + response['msg'] + '</div>';
},

doRequest: function(request)
{
  new Ajax.Request(this.url, {
    method: 'get',
    parameters: { 'msg' : request 
  });
}
}
var comet = new Comet();
comet.connect();
</script>

</body>
</html>

或者

你还可以看看其他聊天应用程序来了解它们是如何实现的:


谢谢 - 我已经尝试了这段代码,但是使用Iframe方法时,如果我使用flush(),在Google Chrome中我不会立即看到响应,只有当页面加载完成时才会看到。 - Ali
你需要保持连接的活跃。第二种方法使用“长轮询”。还请参阅评论:http://www.zeitoun.net/articles/comet_and_php/start。 - Wouter Dorgelo
我刚发现了Ajax Chat,这是一个使用ajax、sockets、php和ruby(后者是可选的)的开源Web聊天应用程序:https://blueimp.net/ajax/ - Wouter Dorgelo
2
我更喜欢在共享托管帐户上安装非常简单的解决方案,或者是一个仅限于php/mysql的解决方案。我建议您使用推送服务来使聊天环境更好。 (使用推送,您的客户端无需请求服务器获取新数据+它使聊天实时。)一些具有良好文档和库的推送服务:http://beaconpush.com,http://pusherapp.com,http://kwwika.com - Wouter Dorgelo
如果我想要能够进行多个聊天会话,该如何实现data.txt文件? - Gaurav Ojha

5
轮询并不是一个好的解决方案。您需要使用长轮询或Web套接字的解决方案。 http://hookbox.org 可能是您可以使用的最佳工具。
它是一个位于服务器和浏览器之间的盒子,管理称为通道(类似于IRC通道)的抽象。它在github上是开源的:https://github.com/hookbox/hookbox 该盒子用Python编写,但可以轻松地与任何语言编写的服务器一起使用。它还带有一个JavaScript库,构建在jsio上(使用Web套接字、长轮询或浏览器上可用的最佳技术),保证在浏览器中使用最佳技术。在演示中,我看到了一个实时聊天的实现,只需几行代码即可。
Hookbox的目的是方便实时Web应用程序的开发,重点是与现有Web技术的紧密集成。简单地说,Hookbox是一个Web启用的消息队列。浏览器可以直接连接到Hookbox,订阅命名通道,并实时发布和接收这些通道上的消息。外部应用程序(通常是Web应用程序本身)也可以通过Hookbox REST接口向通道发布消息。所有身份验证和授权都通过指定的“Webhook”回调由外部Web应用程序执行。

alt text

任何时候用户连接或在频道上操作(订阅、发布、取消订阅),Hookbox都会向Web应用程序发出HTTP请求以获得操作的授权。一旦订阅了频道,用户的浏览器将接收到实时事件,这些事件要么来自通过JavaScript API从另一个浏览器发起的事件,要么来自通过REST API从Web应用程序发起的事件。
关键的洞见是,使用Hookbox进行应用程序开发只需使用JavaScript或Web应用程序本身的本地语言(例如PHP)。
您需要一个可以运行Python的服务器,但您不必知道Python。
如果您想仅使用Websockets和PHP,则可以使用此链接作为起点:http://blancer.com/tutorials/69066/start-using-html5-websockets-today/

2
你看过PHPDaemon吗?它是使用libevent和pnctl编写的,具有许多功能,甚至有一个简单的聊天演示应用程序。它甚至有一些生产实现。

这非常有趣,看起来它似乎能完成工作。但是它能安装在共享主机上吗?从文档中我不清楚需要上传代码到哪里才能使用SSH运行安装命令(我猜测)。 - Ali
2
PHPDeamon是一种后端服务器解决方案,因此不适合共享托管帐户。 - Wouter Dorgelo

2

2

我建议使用HTML5 WebSockets实现,对于较旧的浏览器,可以使用长轮询或comet作为后备。WebSockets将打开与浏览器的持久连接。 有一个开源的php实现WebSocket服务器


1

我建议你尝试使用Socket.IONodeJS。Socket.IO提供了一个漂亮且非常易于使用的客户端API,适用于大多数现代浏览器,并在可能的情况下使用适当的传输方式(Websocket、长轮询等)。NodeJS是一个服务器端守护程序,可以保持HTTP连接。Socket.IO的官方网站包含有关如何将它们一起使用的信息。希望这能帮到你。


NodeJS需要Python 2.4或更高版本,而不是PHP。 - Wouter Dorgelo

1

我相信你所遇到的问题需要使用Comet Web编程。你可以在维基百科上搜索Comet编程以及Ajaxian上查找更多详细信息(我还是这个网站的新手,无法在回复中发布超过1个链接)。

问题在于,在服务器端使用PHP不能轻松实现此目标。更多细节请参见: 使用PHP进行Comet编程

此外,如果你在谷歌上搜索“php comet”,你会找到一个教程来实现所需的效果。

后期编辑

Ape项目

使用该引擎实现了一个项目。非常棒。

使用PHP进行Comet编程

希望这能帮到你, Gabriel


我一直在研究APE,发现它存在一些严重的问题,尽管它被认为是“稳定”的。请参见:http://www.nulldevice.de/2010/09/ape-ajax-push-engine/。 - Wouter Dorgelo
谢谢你提醒我并分享链接。我也遇到了一些问题。 - Gabriel Croitoru

1
这看起来很有前途!甚至可能非常容易重新样式 :)

http://www.php-development.ru/javascripts/ajax-chat.php

Javascript/PHP中的Ajax聊天脚本

描述

Ajax Chat是一款轻量级可定制的Web聊天软件,采用JavaScript和PHP实现。该脚本不需要Java、Flash或任何其他插件。

特点

  • 公共和私人聊天。
  • 作为注册用户或访客登录。
  • 离开状态、自定义颜色、表情符号、用户性别/状态图标。
  • Ajax Chat可以通过实现用户认证程序将其与第三方会员系统集成。高级集成选项:如果用户已登录网站,则可以自动登录到聊天室。

Ajax lightweight chat script

*请注意,这是从原始网站复制/粘贴的。

0

我以前没有用PHP做过这个,但你最好的选择可能是使用某种套接字连接。这里是PHP手册关于套接字的内容。

我不记得是谁的教程了,但我用Flash作为客户端和Java作为服务器创建了一个像你想要的聊天室。我认为这个链接可能是教程所在的地方,它可能会对你有所帮助。


我认为在这种情况下,套接字不会有太大用处。套接字必须连接到某个东西,也就是说,用户的计算机上必须有某种允许连接的东西。 - AntonioCS
你的意思是什么?当我使用Flash时,它可以嵌入浏览器,并且可以正常工作。 - Aaron Hathaway
2
@Aaron Hathaway:Flash 不是一个网页(文本、HTML),而是一个二进制程序。 - ajreal
@ajreal:嗯,我不记得说过Flash是一个网页了?它是嵌入在网页上的HTML中。 - Aaron Hathaway
也许可以将套接字连接到JavaScript? - Ali
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接