5.2 实现Ajax聊天
我们将保持应用是简单的、模块化的和可扩展的。为了做到这一点,我们不实现登录模块、聊天室、在线用户表等功能。为了保持它的简单风格,我们试图将精力集中在本章的目标——Ajax聊天上。我们将实现基本的聊天功能:在不引起任何网页重新加载的情况下发送和接收消息。我们也允许用户挑选消息的颜色,这又是一个学习Ajax机制的好机会。
从本章下面要讲的应用程序出发,通过实现前面方案中的其他模块或这里尚未提及的模块,我们可以很容易地扩展这个应用。如果感兴趣的话,可以将这部分作为自己的课后作业。
|
|
要想使这些示例运行起来,需要GD库。附录A的安装介绍中包含如何支持GD库。 |
这个聊天应用可以在线测试:http://ajaxphp.packtpub.com,如图5.2所示。

图5.2 Ajax聊天
本章的新奇之处是会有两个XMLHttpRequest对象。第一个处理更新聊天窗口,第二个处理颜色取色(当点到图片上时,坐标被发送到服务器上,服务器用颜色代码响应)。
Ajax聊天的消息被保存到一个队列中(FIFO结构的),就像在第4章了解到的,即使服务器很慢,也不会丢失消息,而且它们会以发送时的顺序到达服务器。与现在因特网上的其他方式不同,我们保证在一次请求还未结束之前,不向服务器重复发送多个请求。
实现步骤——Ajax聊天
(1)连接Ajax数据库,并用下面的代码创建表chat:
CREATE TABLE chat
(
chat_id int(11) NOT NULL auto_increment,
posted_on datetime NOT NULL,
user_name varchar(255) NOT NULL,
message text NOT NULL,
color char(7) default '#000000',
PRIMARY KEY (chat_id)
);
(2)在Ajax文件夹下,创建新文件夹chat。
(3)从下载代码中将palette.png文件复制到chat文件夹下。
(4)我们将创建一个由服务器功能开始的应用。在chat文件夹下,创建文件config.php,并把数据库配置代码加到此文件里(改变这些变量值来匹配配置)。
<?php
//定义数据库连接数据
define('DB_HOST', 'localhost');
define('DB_USER', 'ajaxuser');
define('DB_PASSWORD', 'practical');
define('DB_DATABASE', 'ajax');
?>
(5)现在加入标准错误处理文件error_handler.php:
<?php
//设置用户错误处理方法为error_handler
set_error_handler('error_handler', E_ALL);
//错误处理函数
function error_handler($errNo, $errStr, $errFile, $errLine)
{
//清除所有已经生成的输出
if(ob_get_length()) ob_clean();
//输出错误消息
$error_message = 'ERRNO: ' . $errNo . chr(10) .
'TEXT: ' . $errStr . chr(10) .
'LOCATION: ' . $errFile .
', line ' . $errLine;
echo $error_message;
//阻止处理任何其他PHP脚本
exit;
}
?>
(6)创建文件chat.php并加入下面代码:
<?php
//参考包含Chat类的文件
require_once("chat.class.php");
//取回执行的操作
$mode = $_POST['mode'];
//默认情况下最后的id是0
$id = 0;
//创建一个新的聊天实例
$chat = new Chat();
//如果操作是 SendAndRetrieve
if($mode == 'SendAndRetrieveNew')
{
//取回用来添加一个新消息的作用参数
$name = $_POST['name'];
$message = $_POST['message'];
$color = $_POST['color'];
$id = $_POST['id'];
//检查是否我们有合法的值
if ($name != '' && $message != '' && $color != '')
{
//把消息传递到数据库
$chat->postMessage($name, $message, $color);
}
}
//如果操作是 DeleteAndRetrieve
elseif($mode == 'DeleteAndRetrieveNew')
{
//删除所有已存在的消息
$chat->deleteMessages();
}
//如果操作是Retrieve
elseif($mode == 'RetrieveNew')
{
//获取客户取回的最后消息的id
$id = $_POST['id'];
}
//清除输出
if(ob_get_length()) ob_clean();
//发送Headers阻止浏览器缓存
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT');
header('Cache-Control: no-cache, must-revalidate');
header('Pragma: no-cache');
header('Content-Type: text/xml');
//从服务器取回新消息
echo $chat->retrieveNewMessages($id);
?>
(7)创建文件chat.class并加入下面代码:
<?php
//加载配置文件
require_once('config.php');
//加载错误处理模块
require_once('error_handler.php');
//包含服务端聊天功能的类
class Chat
{
//数据库处理
private $mMysqli;
//构造函数打开数据库连接
function __construct()
{
//连接到数据库
$this->mMysqli = new mysqli(DB_HOST, DB_USER, DB_PASSWORD,
DB_DATABASE);
}
//析构函数关闭数据库连接
public function __destruct()
{
$this->mMysqli->close();
}
//截去包含消息的表
public function deleteMessages()
{
//建立增加一个新消息到服务器的SQL查询
$query = 'TRUNCATE TABLE chat';
//执行SQL查询
$result = $this->mMysqli->query($query);
}
/*
postMessages方法向数据库中插入消息
- $name代表post消息的用户的名称
- $messsage是post的消息
- $color包含用户选取的颜色
*/
public function postMessage($name, $message, $color)
{
//转换变量数据,以便安全地将它们添加到数据库
$name = $this->mMysqli->real_escape_string($name);
$message = $this->mMysqli->real_escape_string($message);
$color = $this->mMysqli->real_escape_string($color);
//建立增加一个新消息到服务器的SQL查询
$query = 'INSERT INTO chat(posted_on, user_name, message, color) ' .
'VALUES (NOW(), "' . $name . '" , "' . $message .
'","' . $color . '")';
//执行SQL查询
$result = $this->mMysqli->query($query);
}
/*
retrieveNewMessages方法提取已经发送给服务器的新消息
-$id参数由客户端发送,而且它是客户端接收到的最后一个消息的id
通过$id从数据库获取最近的消息,并利用XML格式返回给客户端。
*/
public function retrieveNewMessages($id=0)
{
//转换变量数据
$id = $this->mMysqli->real_escape_string($id);
//组成取回新消息的SQL查询
if($id>0)
{
//取回比$id新的消息
$query =
'SELECT chat_id, user_name, message, color, ' .
' DATE_FORMAT(posted_on, "%Y-%m-%d %H:%i:%s") ' .
' AS posted_on ' .
' FROM chat WHERE chat_id > ' . $id .
' ORDER BY chat_id ASC';
}
else
{
//第一次加载仅仅从服务器取回最后50个消息
$query =
' SELECT chat_id, user_name, message, color, posted_on FROM ' .
' (SELECT chat_id, user_name, message, color, ' .
' DATE_FORMAT(posted_on, "%Y-%m-%d %H:%i:%s") AS posted_on ' .
' FROM chat ' .
' ORDER BY chat_id DESC ' .
' LIMIT 50) AS Last50' .
' ORDER BY chat_id ASC';
}
//执行查询
$result = $this->mMysqli->query($query);
//建立XML响应
$response = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
$response .= '<response>';
//输出清除标志
$response .= $this->isDatabaseCleared($id);
//检查看我们是否有任何结果
if($result->num_rows)
{
//在所有取来的消息间循环来建立结果消息
while ($row = $result->fetch_array(MYSQLI_ASSOC))
{
$id = $row['chat_id'];
$color = $row['color'];
$userName = $row['user_name'];
$time = $row['posted_on'];
$message = $row['message'];
$response .= '<id>' . $id . '</id>' .
'<color>' . $color . '</color>' .
'<time>' . $time . '</time>' .
'<name>' . $userName . '</name>' .
'<message>' . $message . '</message>';
}
//尽快关闭数据库连接
$result->close();
}
//完成XML响应并返回它
$response = $response . '</response>';
return $response;
}
/*
isDatabaseCleared方法查看自从上次访问服务器后数据库是否被清空。
参数包含最后从客户端接收的消息的id。
*/
private function isDatabaseCleared($id)
{
if($id>0)
{
//通过检查其id号比客户的最后id号小的行数,我们检查看是否一个截去操作同时//被执行
$check_clear = 'SELECT count(*) old FROM chat where chat_id<=' . $id;
$result = $this->mMysqli->query($check_clear);
$row = $result->fetch_array(MYSQLI_ASSOC);
//如果一个截去操作发生了,白色书写板需要被重新设置
if($row['old']==0)
return '<clear>true</clear>';
}
return '<clear>false</clear>';
}
}
?>
(8)创建文件get_color.php并加入下面代码:
<?php
//图片文件的名字
$imgfile='palette.png';
//加载图片文件
$img=imagecreatefrompng($imgfile);
//获得用户单击点的坐标
$offsetx=$_GET['offsetx'];
$offsety=$_GET['offsety'];
//获取单击的颜色
$rgb = ImageColorAt($img, $offsetx, $offsety);
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
//返回颜色代码
printf('#%02s%02s%02s', dechex($r), dechex($g), dechex($b));
?>
(9)现在开始处理客户端了。先创建文件chat.css并加入下面代码:
body
{
font-family: Tahoma, Helvetica, sans-serif;
margin: 1px;
font-size: 12px;
text-align: left
}
#content
{
border: DarkGreen 1px solid;
margin-bottom: 10px
}
input
{
border: #999 1px solid;
font-size: 10px
}
#scroll
{
position: relative;
width: 340px;
height: 270px;
overflow: auto
}
.item
{
margin-bottom: 6px
}
#colorpicker
{
text-align:center
}






