nmhnmh的笔记

为中华崛起而读书

基于JSON的Ajax简易网络聊天程序(PHP)

译者注:第一次翻译技术类文章,请多批评指正,谢谢。
原文连接:http://www.dynamicajax.com/fr/JSON_AJAX_Web_Chat-.html
这个教程将指导你一步一步完成一个基于JSON的Ajax站点。这个教程与我之前写于2005年11月的Ajax网络聊天教程非常相似,但与那时使用XML传输消息数据不同,这里我们使用JSON取而代之。

Download The JSON AJAX Chat Source Code下载本例源代码
View AJAX Web Chat in Action浏览例程页面

在阅读关于JSON的各种资料几个月后,我终于在最近发现了它的趣味所在。正如 SecretGeek 所说的那样:
  1. 两个月前你可能从未听说过JSON
  2. 一个月前你可能听说过他,但并没有在意它
  3. 一周前你几次听到它被提及,然后,你开始想:**,又有新东西要学了。
  4. 今天你被脑海中这样一个念头惊醒:JSON到底是什么鬼东西,为什么突然间到处都是!
那么究竟是么是JSON呢?JSON 指的是 Javascript Object Notation,简而言之就是一种轻量级的描述层次化数据的方法。正因为他是如此的轻巧,使得它成为了Ajax应用中一个理想的候选技术。那么JSON究竟是什么样子的呢?在本例程中,返回的JSON代码格式大致是这样的:
{"messages": 
	{"message":[ 
		{"id":  "17",
			"user": "Ryan Smith",
			"text": "This is an example of JSON",
			"time": "04:41"
		},{"id":  "18",
			"user": "Ryan Smith",
			"text": "Here is another Element",
			"time": "04:41"
		} ]
	}
}

如你所看到的,他看上去很像一种格式化的数据,事实正是如此。相同的数据内容使用XML表示如下:
<?xml version="1.0" ?>
<root>
	<message id="17">
		<user>Ryan Smith</user>
		<text>This is an example of JSON</text>
		<time>04:41</time>
	</message>
	<message id="18">
		<user>Ryan Smith</user>
		<text>Here is another Element</text>
		<time>04:41</time>
	</message>
</root>


你还可以用JSON做一些其它很酷的事情,比如嵌入Javascript调用(译者注:应该是指JSONP技术,JSON with Padding),但他们已经超出了本教程的范围。

创建聊天程序数据库表
  现在让我们开始吧,首先要做的是建立数据库表。实际上我们只需要一个数据库表来存储所有消息,但我总是想到将来有一天我可能会将它扩展位一个真正的实用的聊天应用程序,所以,我们还是创建两张表吧。
--Chat Table
DROP TABLE IF EXISTS `chat`;
CREATE TABLE `chat` (
  `chat_id` INT(11) NOT NULL AUTO_INCREMENT,
  `chat_name` VARCHAR(64) DEFAULT NULL,
  `start_time` DATETIME DEFAULT NULL,
  PRIMARY KEY  (`chat_id`)
) ENGINE=INNODB DEFAULT CHARSET=latin1;
  
--Message Table
DROP TABLE IF EXISTS `message`;
CREATE TABLE `message` (
  `message_id` INT(11) NOT NULL AUTO_INCREMENT,
  `chat_id` INT(11) NOT NULL DEFAULT '0',
  `user_id` INT(11) NOT NULL DEFAULT '0',
  `user_name` VARCHAR(64) DEFAULT NULL,
  `message` TEXT,
  `post_time` DATETIME DEFAULT NULL,
  PRIMARY KEY  (`message_id`)
) ENGINE=INNODB DEFAULT CHARSET=latin1;

第一张表“chat”在本例中并不需要。第二章表“message”将用来存储所有从我们的JSON Ajax 页面上发送的消息。它主要由以下几部分组成:谁发送了消息,什么时候发送的消息,消息内容是什么。当你希望有多个聊天会话同时进行时,可以使用chat_id域来区分。

HTML框架

由于我们已经创建了数据库表,现在我们需要穿件HTML页面的框架了。这是一个非常简单的布局,可以很轻易的通过CSS来进行美化,但我们现在并不关心它的视觉效果,我们只是希望它能实现基本功能。

出于演示的目的,我们将一些必要的CSS样式信息直接内置于HTML页面内的Style标记内,在一个真实的工作环境下,你应当将你的CSS样式集中放在一个外部对立的CSS文件中,能够实现缓存,尤其是当你有很多的CSS样式时。

同样,我们将Javascript代码也直接内置于HTML页面上的一个Script标记内。我们将在稍后提到它。

我们的基本HTML页面是这样的:
<html>
	<head>
		<title>JSON AJAX Driven Web Chat</title>
                <style type="text/css" media="screen"></style>
		<script language="JavaScript" type="text/javascript"></script>
	</head>
	<body>
		<h2>AJAX Driven Web Chat.</h2>
		<p id="p_status">Status: Normal</p>
		Current Chitter-Chatter:
		<div id="div_chat" class="chat_main">
			
		</div>
		<form id="frmmain" name="frmmain" onsubmit="">
			<input type="button" name="btn_get_chat" id="btn_get_chat" value="Refresh Chat" />
			<input type="button" name="btn_reset_chat" id="btn_reset_chat" value="Reset Chat"  /><br />
			<input type="text" id="txt_message" name="txt_message" style="width: 447px;" />
			<input type="button" name="btn_send_chat" id="btn_send_chat" value="Send" />
		</form>
	</body>
	
</html>


如你所看到的,我们有一个简单的页面头部,其中只有一个标题。一个用于在出现错误时显示状态信息的段落,一个用于显示主聊天区域的DIV。

我们还有一个HTML表单,其中有四个HTML控件。我们加入了一个刷新按钮用于在出错时重置Javascript定时器。这个按钮仅仅用于测试用途。我们加入一个重置按钮用于清除屏幕上的消息。最后还有一个文本框和一个发送按钮用于想服务器发送消息。

在HTML中,我们有一个行内CSS类别,用于扩展文本框的宽度位447px,然后在主消息区上还有一个CSS类别,尚未定义。

主消息区的CSS应当定义如下:
overflow: auto; 
height: 300px; 
width: 500px; 
background-color: #CCCCCC; 
border: 1px solid #555555;


所有的这些CSS样式大多很直白,除了overflow一项。overflow:auto;这一规则使得消息区像一个iframe一样,当内容高度超出时,可以通过滚动条来显示超出的内容,而不是扩展消息区的尺寸。我尽可能尝试不去使用iframe,因为搜索引擎很难为他们建立索引。而且,我开始WEB编程的时候,并不是所有的浏览器都支持iframe,所以总之你不能使用它们。

Javascript代码

现在轮到ajax了,让我们来写一段使得ajax变得更直白简单的代码,以下是我最喜欢的一段ajax实现代码:
//Gets the browser specific XmlHttpRequest Object
function getXmlHttpRequestObject() {
	if (window.XMLHttpRequest) {
		return new XMLHttpRequest();
	} else if(window.ActiveXObject) {
		return new ActiveXObject("Microsoft.XMLHTTP");
	} else {
		document.getElementById('p_status').innerHTML = 
		'Status: Cound not create XmlHttpRequest Object.' +
		'Consider upgrading your browser.';
	}
}

这段代码返回了一个针对于特定浏览器的XMLHttpRequest对象,而这是Ajax的基础。XMLHttpRequest使得我们可以向服务器发送异步请求而不需要刷新整个页面。

旧版本的IE使用的是ActiveX对象而火狐以及大多数其它浏览器都使用浏览器原生的XMLHttpRequest对象。IE7同时支持两种对象,这使得我们可以去掉这一段代码,但是人们可能在接下来的许多年里继续使用IE6,所以我们最好还是习惯写这段代码。

使用这段代码我们可以在页面中的任何地方创建一个XMLHttpRequest对象。

现在我们在页面中加入四个全局变量:
var sendReq = getXmlHttpRequestObject();
var receiveReq = getXmlHttpRequestObject();
var lastMessage = 0;
var mTimer;
我们需要两个XMLHttpRequest对象,一个用于接收消息,一个用于发送消息。我们同时需要一个变量来存储最后一条消息,以免我们每次都要发送整个消息列表,并且我们还需要一个定时器来周期性的向服务器查询新消息。我们把自动刷新的定时器设置为全局变量使得我们可以在页面中任何地方重置它,以免出现同时有多个定时器运行。

现在我们加入接收服务器上最新消息的函数:
//Gets the current messages from the server
function getChatText() {
	if (receiveReq.readyState == 4 || receiveReq.readyState == 0) {
		receiveReq.open("GET", 'getChat.php?chat=1&last=' + lastMessage, true);
		receiveReq.onreadystatechange = handleReceiveChat; 
		receiveReq.send(null);
	}			
} 
代码的第一行用于检查我们的XMLHttpRequest对象是否正处于另一个不同的请求中。0是初始状态,4代表结束。之间其他的状态代表正处于请求中。

下一行建立到服务器的连接:
receiveReq.open("GET", 'getChat.php?chat=1&last=' + lastMessage, true);


由于我们并没有发送大量信息,一个标准的HTTP GET方法就足够了。当我们后面发送消息是,你会看到如何去创建一个使用 HTTP POST方法创建一个ajax请求。

open函数的第二个参数是我们的ajax请求的URL。注意,我们将我们最后收到的消息以及我们当前所属聊天会话ID两个参数通过URL的querystring进行传递。当前所属聊天会话ID直接设为常数1,但如果我们希望有更多的聊天室,我们就可以根据我们所在的聊天会话不同传递一个动态的值。

最后一个参数,“true”,是一个用来标记请求是否是异步的标志。实际上我们可以省略,因为默认就是异步的(true),但我们还是加上了它。

下一行设置了每次XMLHttpRequest异步对象状态发生改变时调用的回调函数。
receiveReq.onreadystatechange = handleReceiveChat;


我们将它设置成一个我们马上就要编写的函数。

最后一行向服务器发出请求,如果你是在使用POST方法发送异步请求, 那么可以将null 改为其他参数,以便传递更多信息。

客户端JSON 处理
现在我们将要创建用于处理服务器响应的函数。这个函数也是我们看到JSON实际应用的地方。
function handleReceiveChat() {
	if (receiveReq.readyState == 4) {
		//Get a reference to our chat container div for easy access
		var chat_div = document.getElementById('div_chat');
		//Get the AJAX response and run the JavaScript evaluation function
		//on it to turn it into a usable object.  Notice since we are passing
		//in the JSON value as a string we need to wrap it in parentheses
		var response = eval("(" + receiveReq.responseText + ")");
		for(i=0;i < response.messages.message.length; i++) {
			chat_div.innerHTML += response.messages.message[i].user;
			chat_div.innerHTML += '  <font class="chat_time">' +  response.messages.message[i].time + '</font><br />';
			chat_div.innerHTML += response.messages.message[i].text + '<br />';
			chat_div.scrollTop = chat_div.scrollHeight;
			lastMessage = response.messages.message[i].id;
		}
		mTimer = setTimeout('getChatText();',2000); //Refresh our chat in 2 seconds
	}
}


该函数每次当XMLHttpRequest对象的状态发生改变时都会被调用,所以我们需要检查readyState==4以便确定请求已经结束。

第一行仅仅是获取一个到主消息区Div的引用,然后将其保存备用,我们这样做的目的是避免每次都要调用 document.getElementById() 函数。

下一行则是真正很酷的地方,也是我为什么以后将在ajax中使用JSON而不是XML的原因。使用JSON,我们可以使用javascript内置的eval()函数通过简单的调用来创建一个可用对象。你可以去看看在Ajax网络聊天教程 使用XML是如何做到这点的。
var response = eval("(" + receiveReq.responseText + ")");
这里实际上是JSON格式的服务器端响应通过eval()函数被解析成了一个带有数组成员的javascript对象,receiveReq.responseText从Ajax异步请求中获取响应数据即JSON格式的数据。异步请求的响应也可应通过responseXML函数来获取,它将返回一个XML DOM 对象。

注意:由于我们是将responseText 作为一个字符串传递给eval函数的,所以我们需要将它包裹在一对小括号中。否则会抛出一个错误,我已经多次遇到过这种情况了。

由于我们已经将Ajax响应存储到了一个易用的javascript对象中,现在我就可以遍历每条消息然后更新我们的主消息区了。

让我们在来看一下我们的JSON响应的格式:
{"messages": 
	{"message":[ 
		{"id":  "17",
			"user": "Ryan Smith",
			"text": "This is an example of JSON",
			"time": "04:41"
		},{"id":  "18",
			"user": "Ryan Smith",
			"text": "Here is another Element",
			"time": "04:41"
		} ]
	}
}

如你所见,我们有一个"根"消息对象messages,内部有一个message成员(译者注:也是一个javascript对象),其内部又有多个消息值。JSON的键值对使用冒号:来表示,并且需要使用双引号括住。我认为你只要将那些包含空格或者特殊字符的用双引号括住就行了,但是作为一种好的编程习惯,我们将所有的东西都用双引号括住了。

由于我们将数据使用Javascript对象来表示,我们可以通过javascript的方式来获取所有的数据值包括它们的长度。
for(i=0;i < response.messages.message.length; i++) { 
上面一行遍历了每个消息。下面的三行则将消息值加入到主消息区DIV中。
chat_div.innerHTML += response.messages.message[i].user;
chat_div.innerHTML += '  <font class="chat_time">' +  response.messages.message[i].time + '</font><br />';
chat_div.innerHTML += response.messages.message[i].text + '<br />';


注意此处我们如何仅仅通过一些.user .time .message等简单方式来获取数据值而不是像在XML中使用一些复杂的getElementByTag 方法调用。

下面的一行是一个很不错的易用性增强,它可以自动滚动DIV,使得最新的消息可以始终被显示出来,感谢Eric
chat_div.scrollTop = chat_div.scrollHeight;


最后我们将最后收到的消息的ID使用像之前一样的方法获取到然后保存到lastMessage变量中:
lastMessage = response.messages.message[i].id;


一旦我们完成了消息的遍历,我们就可以重置定时器在下一个2秒之后检查新的消息。根据你的服务器的负载情况,你可以进一步的延长这个时间,尽管传输的数据量非常小,很难超过10K。
mTimer = setTimeout('getChatText();',2000); //Refresh our chat in 2 seconds 




发丝消息

下面,我们需要编写发送消息到服务器的代码。我们将通过HTTP POST方法而不是 GET方法来发送,因为我们将发送大量数据。在一些旧的浏览器中URL的querystring一般只适合传输几千个字符的数据,而关于URL的RFC甚至要求你不要传输超过255个字符。为了在传输长消息时不出现意外,我们使用POST。

创建一个Ajax POST方法的请求与GET方法并没有什么太大的不同:
//Add a message to the chat server.
function sendChatText() {
	if (sendReq.readyState == 4 || sendReq.readyState == 0) {
		sendReq.open("POST", 'getChat.php?chat=1&last=' + lastMessage, true);
		sendReq.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
		sendReq.onreadystatechange = handleSendChat; 
		var param = 'message=' + document.getElementById('txt_message').value;
		param += '&name=Ryan Smith';
		param += '&chat=1';
		sendReq.send(param);
		document.getElementById('txt_message').value = '';
	}							
} 


如你所见,基本上是相同的代码,般与之前在URL中传递参数不同,这里我们在调用XmlHttpRequest.send函数发送请求时传送参数,并且在初始化XmlHttpRequest异步对象时,我们将其标为POST。
		var param = 'message=' + document.getElementById('txt_message').value;
		param += '&name=Ryan Smith';
		param += '&chat=1';
		sendReq.send(param);

参数之间通过&符号隔开,就像URL的query string一样。我们向服务器传递三个参数:消息正文,姓名,当前的聊天会话ID,在本教程中最后一个值设为常数1。

我们的回调函数也很简单:
//When our message has been sent, update our page.
function handleSendChat() {
	//Clear out the existing timer so we don't have 
	//multiple timer instances running.
	clearInterval(mTimer);
	getChatText();
}


这里我们将旧的定时器重置,然后立即向服务器请求新的消息。

重置聊天
我们现在有了HTML页面中需要的一切以及用来收发消息的javascript代码。剩下的是增加一个重置聊天程序的功能。在实际的实现中,你可能希望让服务器不时地去删除消息而不时让用户去清除。
//This cleans out the database so we can start a new chat session.
function resetChat() {
	if (sendReq.readyState == 4 || sendReq.readyState == 0) {
		sendReq.open("POST", 'getChat.php?chat=1&last=' + lastMessage, true);
		sendReq.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
		sendReq.onreadystatechange = handleResetChat; 
		var param = 'action=reset';
		sendReq.send(param);
		document.getElementById('txt_message').value = '';
	}							
}


这个函数与发送消息的函数是基本相同的,但我们这里不是发送消息数据,而是发送一个动作参数来重置连天会话。

回调函数与handleSendChat 基本相同,增加了一个清除主聊天消息区的功能。

The last thing we need to do to our HTML page it to add onclick handlers to our HTML buttons.剩下要做的就是为页面内的按钮增加onclick事件处理程序。
<input type="button" name="btn_get_chat" id="btn_get_chat" value="Refresh Chat" onclick="javascript:getChatText();" />
<input type="button" name="btn_reset_chat" id="btn_reset_chat" value="Reset Chat" onclick="javascript:resetChat();" /><br />
<input type="text" id="txt_message" name="txt_message" style="width: 447px;" />
<input type="button" name="btn_send_chat" id="btn_send_chat" value="Send" onclick="javascript:sendChatText();" /> 
现在每个按钮都指向了对应的javascript函数。

后台代码
本教程将使用PHP与Mysql的组合作为后台,但这可以很方便的修改为使用其他服务器端语言 (ASP, ASP.NET, JSP, CFM, RoR, 等等)。服务器端响应是简单的文本形式的,所以关于语言的选择可以遵照个人的喜好。

We only need the one page since all of our AJAX requests go to the same page.  They are then sorted out based on the parameters sent in the response.我们只需要一个页面,因为所有的请求都是向同一个页面的。然后所有的请求根据其参数的不同进行分类。
要做的第一件事是创建一些HTTP头部信息,以免用户的浏览器缓存响应信息。
//Send some headers to keep the user's browser from caching the response.
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; charset=utf-8");


很可能最重要的头部信息是”expire“。你可以看到我们将它设为了一个已经过去的时间点,如果没有这个头部信息的话,不论其它头部信息是什么样的,IE 浏览器将会缓存响应信息。

要设置的另一个头部信息是Content-Type。我们并不真正需要它,因为我们发送的是纯文本。如果像我们之前的Ajax网络聊天教程 一样发送XML之类的数据,那么这个头部信息将会变得非常重要。

下一行将一个包含了我们的数据库函数的文件包含到页面中来。我喜欢将数据库连接函数放到同一个独立文件中,以防我什么时候需要改变数据库类型(如MSSQL,ORACLE)。
require('database.php');


这个文件可以在经过简单修改后与相当多的数据库进行连接。

我们先要检查在POST参数中有没有一个新的消息被传送上来:
//Check to see if a message was sent.
if(isset($_POST['message']) && $_POST['message'] != '') {
	$sql = "INSERT INTO message(chat_id, user_id, user_name, message, post_time) VALUES (" . 
			db_input($_GET['chat']) . ", 1, '" . db_input($_POST['name']) . 
			"', '" . db_input($_POST['message']) . "', NOW())";
	db_query($sql);
} 


如果有新消息被发送,然后我们创建一个SQL INSERT 声明语句将消息内容存储到数据库。我们使用了db_input 函数来将用户输入的引号以及各种SQL语句关键字等危险信息进行转义,这个函数包含在database.php文件中。

在执行SQL语句前,你一定要先检查用户输入。实际上应该使用存储过程或者r prepared statements语句,但那已经超出了本教程的范围。

最后我们执行SQL语句将用户输入存入数据库。

然后我们检查是否有一个重置参数在POST参数中被传递上来,如果是的话,我们将删除对应的chat_id下的所有消息。
//Check to see if a reset request was sent.
if(isset($_POST['action']) && $_POST['action'] == 'reset') {
	$sql = "DELETE FROM message WHERE chat_id = " . db_input($_GET['chat']);
	db_query($sql);
}


很明显,如果这是一个实际运行的系统,那么你很可能会希望有一些安全措施来防止用户有意或者无疑地删除他人会话中的消息。但这只是一个概念性的验证,所以我们不会去担心它。

创建响应
一旦我们完成了INSERT或者Delete,就到了我们创建JSON响应的时候了。

首先,我们创建JSON 根对象messages,因为它总是要被发回客户端。
//Create the JSON response.
$json = '{"messages": {';

接下来我们要确认在请求中我们接收到了一个合法的会话ID,即用户确实处在聊天会话中。
//Check to ensure the user is in a chat room.
if(!isset($_GET['chat'])) {
	$json .= '"message":[ {';
	$json .= '"id":  "0",
				"user": "Admin",
				"text": "You are not currently in a chat session.  <a href="">Enter a chat session here</a>",
				"time": "' . date('h:i') . '"
			}]';


如果用户当前不在会话中,那么我们将会通知他们进入一个会话,以便接受消息。当然,如果是在实际的系统中,那么你还是要进行安全检查,以便确认用户有足够的权限进入会话中。

如果我们在用户的请求中找到了一个合法的会话ID,那么我们就进行数据库查询,找出自上次请求后有没有新的消息。
} else {
	$last = (isset($_GET['last']) && $_GET['last'] != '') ? $_GET['last'] : 0;
	$sql = "SELECT message_id, user_name, message, date_format(post_time, '%h:%i') as post_time" . 
		" FROM message WHERE chat_id = " . db_input($_GET['chat']) . " AND message_id > " . $last;
	$message_query = db_query($sql);


如果我们没有收到一个last参数,我们认为用户没有收到过任何消息,并将last变量设为0,这样用户可以接受所有消息。

如果用户有未接收的新消息,那么我们将遍历这些消息然后依次将它们加入到JSON响应中去。
//Loop through each message and create an XML message node for each.
if(db_num_rows($message_query) > 0) {
	$json .= '"message":[ ';	
	while($message_array = db_fetch_array($message_query)) {
		$json .= '{';
		$json .= '"id":  "' . $message_array['message_id'] . '",
					"user": "' . htmlspecialchars($message_array['user_name']) . '",
					"text": "' . htmlspecialchars($message_array['message']) . '",
					"time": "' . $message_array['post_time'] . '"
				},';
	}
	$json .= ']';


如你所见,我们只要创建一个消息对象然后把数据库中的值取存入其中即可。

如果没有新的消息,那么我们需要创建一个空的消息对象作为JSON响应,以免在我们客户端javascript测试消息长度时出现错误。
	} else {
		//Send an empty message to avoid a Javascript error when we check for message lenght in the loop.
		$json .= '"message":[]';
	}
}

我们最后做的是完成JSON数据结构的封装,然后发回客户端。
//Close our response
$json .= '}}';
echo $json;


现在我们可以开始测试了。
不过先要确认你已经创建了数据库表,并且在database.php中设置好了连接参数等。

阅读更多
个人分类: WEB编程
想对作者说点什么? 我来说一句

基于socket聊天程序编写实验报告

2009年06月19日 668KB 下载

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭