简单的PHP长轮询聊天脚本,太简单了吗?

8

我正在开发一个简单的聊天应用,每个房间可能会有10到20个用户。

查询数据库获取新消息的脚本看起来对于所有请求都太简单了。

以下是循环获取新消息的代码块,其余部分只是获取变量、构建查询和JSON响应对象:

$sleepTime = 1; //Seconds
$data = "";
$timeout = 0;

//Query database for data
while(!$data and $timeout < 10){
    $data = getQuery($sql);
    if(!$data){
        //No new messages on the chat
        flush();
        //Wait for new Messages
        sleep($sleepTime);          
        $timeout += 1;
    }else{
        break;
    }
}

上面的代码块将每秒查询数据库10秒钟以获取新消息,如果10秒后没有新消息,它将通知浏览器。浏览器等待5秒钟,然后发送另一个请求以获取新消息。
但是,如果脚本发现新消息,则浏览器会立即请求更多新消息,一旦它从服务器收到包含新消息的响应。
这个过程不断重复...
那么我该如何进一步优化这个过程? 这已经是最好的了吗? 在我的本地服务器上运行良好,但我担心只有几个用户就可能用所有请求和循环超载实时服务器(共享主机)。
这里是一个实时演示,您可以使用Firebug检查:http://pixbush.com/chat/chat.php

3
作为程序员,我们追求的是简洁明了。 - Jacob Relkin
4个回答

3

根据您的描述,似乎您存在5秒的静默间隔,这将削弱长轮询的优势。当浏览器从服务器接收到调用(长或短)时,立即启动另一个请求。作为备选方案,每次与服务器通信时,让浏览器启动一个超时时间略长于服务器端超时时间的计时器,但在请求返回时取消它。如果服务器请求失败且浏览器计时器结束,则启动新的请求。


2

这个场景需要使用AJAX。

请看我今天在StackOverflow上发布的帖子,介绍如何将JavaScript响应发送到PHP。你的脚本没有必要循环执行。


编辑:我之前提到的AJAX可能不太合适。当我编写IRC聊天机器人PHP-Egg时,我遇到了100次类似的问题。那时候是PHP 4时代,我的解决方法是使用pcntl_fork()来分叉PHP,并在每次有消息时简单地返回。它的好处是不会完全阻塞CPU,不像sleep(),而且比10秒或任何你设置的任意限制要快得多。


我再次修改我的答案(抱歉!):

使用某种异步进程将文本转储到文件中。

然后你可以这样做:

if (filemtime('chat.log') > time() - 5) { echo json_encode(file_get_contents('chat.log')); }

好处:SQL使用量有限;无需循环。


3
根据问题描述,我很确定OP正在使用AJAX和长轮询技术。参见:http://en.wikipedia.org/wiki/Comet_(programming)#Ajax_with_long_polling - deceze
@Pablo:以下几个教程可能会对您有所帮助:http://css-tricks.com/chat2/,http://net.tutsplus.com/tutorials/javascript-ajax/how-to-create-a-simple-web-based-chat-application/,http://anantgarg.com/2009/05/13/gmail-facebook-style-jquery-chat/。AJAX 绝对是最佳选择。 - Luke Stevenson
1
我正在使用AJAX,另外我还在服务器上进行循环以最小化AJAX请求。 - Pablo
@Lucanos:sleep()实际上是一种CPU阻塞操作(至少在我最后尝试的PHP 4日子里是这样的),这意味着如果您有100个用户,那么仅循环就可能占用100%的CPU。 - Theodore R. Smith
@hopeseekr:我有点想到这种情况,因此提出了后续问题,询问Pablo使用这种长循环功能的目标。 - Luke Stevenson
显示剩余2条评论

1

我一直在制作Web聊天,发现了保持实时更新的相同解决方案。所以,我想知道你是否已经想出了解决方法:使用sleep()函数循环服务器端是一个好方法吗?还是使用更多的ajax查询更好?而且,当多个用户正在轮询时,sleep()函数真的是一个好主意吗?它不会使服务器停止运行吗?

我看到Meebo使用长轮询(查询之间的时间也取决于窗口焦点),而SO聊天应用程序似乎只使用了ajax查询。这让我感到疑惑。


1
使用sleep()函数进行长轮询在理论上听起来不错,而且在本地测试机器上运行得更好。但是在实时服务器(共享主机)上并不是这样,它会给服务器带来太大的压力。我最终决定只保留ajax请求,没有长轮询。我还创建了一些逻辑,根据活动水平和情况增加和减少ajax请求率。 - Pablo
谢谢回复,我会看看我的长轮询如何进行,然后决定是保留还是拒绝。 - dr3w

0
你可以尝试使用按conversationId标记的文件代替数据库,并检查文件是否被“触摸”。此外,使用usleep和set_time_limit(对于Windows服务器)以毫秒为单位设置间隔并增加睡眠时间。 usleep实际上会延迟CPU使用,但如果文件已更改,则仍会立即触发。

这是我的聊天脚本的一部分。=)

define('SUCCESS', '__SUCCESS__');
define('FAILED', '__FAILED__');

$tmpLib = $TMPFOLDER;
$msgPath = $MSGFILE;

$timeout = $POLLSPEEDSEC;

$acct = new Account($tmpLib, $_GET['key']);

if (false === $acct) {
    return false;
}

$msg = new Message($msgPath, $acct);

$lastMod = !empty($_GET['ts']) ? $_GET['ts']: 0;
$lastMod = substr($lastMod, 0, 10);
$lastMod = (int)$lastMod;

$result = array();

$start = gettimeofday();
$prevMsg = $acct->getTemp('cache');

do{
    usleep(10000);

    if ($acct->getFileTime() >= $lastMod) {
        $result['account'] = $acct->getAllOnline();
    }

    if($msg->getFileTime() >= $lastMod) {
        $result['message'] = $msg->fetch();
    }

    if (!empty($result)) {
        $theMsg = json_encode($result);
        if ($theMsg != $prevMsg) {
            $acct->setTemp('cache', $theMsg);
            echo $theMsg;
            flush();
            exit;
        }
        $result = array();
        $lastMod = time();
    }

    $end = gettimeofday();
} while($timeout > ($end['sec'] - $start['sec']));

echo FAILED;

和我的答案一样,所以我同意。 - Theodore R. Smith
你如何确保文件不会在同时被访问和可能同时被写入时损坏? - Pablo
为了同时访问,FS会锁定文件而不是破坏它。如果发生这种情况,则不会进行任何更改。这种情况可能有50分之1的几率发生。机会太低,不能成为阻碍项目进展的原因。此外,如果使用数据库,也会出现相同的问题。从理论上讲,文件更好,因为它可以“释放”锁并比数据库更快地响应。 - gianebao

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