了解如何使用 Asynchronous JavaScript? + XML (Ajax) 和 PHP 在 Web 應用程序中建立聊天系統。您的客戶不需要下載或安裝任何專門的即時消息通訊軟件,就能和您及其他客戶討論網站的內容。<!--START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
![]() |
|
Web 2.0 一詞出現以來,開發人員都在說社區。不論您是否認為這有點夸大其辭,但讓用戶或讀者能夠方便地實時討論頁面主題或者銷售的產品,這一想法還是很吸引人的。但是怎么辦呢?能否在推銷產品的頁面中加入聊天,而不必讓客戶安裝任何特殊的軟件包括 Adobe Flash Player 呢?當然!實踐證明,用免費的現成工具如 PHP、MySQL、動態 HTML (DHTML)、Ajax 和 Prototype.js 庫就能完全做到。
不再羅嗦了,讓我們立即開始吧。
聊天首先要有一個身份標識。這就需要一個簡單的登錄頁,如 清單 1 所示。
<html> <head><title>Chat Login</title></head> <body> <form action="chat.php" method="post"> Username: <input type="text" name="username"> <input type="submit" value="Login"> </form> </body> </html> |
該頁的顯示結果如 圖 1 所示。
注意: 該例中需要登錄窗口是因為我希望知道誰在說話。對于您的應用程序,可能已經存在一個登錄頁面,使用自己已有的用戶名即可。
![]() ![]() |
![]()
|
聊天系統實質上就是一個字符串表格,每個字符串屬于一個發言者。最簡單的模式如 清單 2 所示。
DROP TABLE IF EXISTS messages; CREATE TABLE messages ( message_id INTEGER NOT NULL AUTO_INCREMENT, username VARCHAR(255) NOT NULL, message TEXT, PRIMARY KEY ( message_id ) ); |
腳本中包含自動增加的消息 ID、用戶名和消息本身。如果需要,還可以向每條消息增加時間戳以記錄發送的時間。
如果需要管理不同話題的多個會話,還需要建立一個表記錄不同的話題,并在消息表中增加相關的
topic_id
。為了盡量簡化例子,我采用了最簡單的模式。
建立數據庫和加載模式使用了下列命令:
% mysqladmin create chat % mysql chat < chat.sql |
根據 MySQL 服務器的設置及其安全設定和口令,命令可能略有不同。
最基本的聊天用戶界面(UI)如 清單 3 所示。
<?php if ( array_key_exists( 'username', $_POST ) ) { $_SESSION['user'] = $_POST['username']; } $user = $_SESSION['user']; ?> <html> <head><title><?php echo( $user ) ?> - Chatting</title> <script src="prototype.js"></script> </head> <body> <div id="chat" style="height:400px;overflow:auto;"> </div> <script> function addmessage() { new Ajax.Updater( 'chat', 'add.php', { method: 'post', parameters: $('chatmessage').serialize(), onSuccess: function() { $('messagetext').value = ''; } } ); } </script> <form id="chatmessage"> <textarea name="message" id="messagetext"> </textarea> </form> <button onclick="addmessage()">Add</button> <script> function getMessages() { new Ajax.Updater( 'chat', 'messages.php', { onSuccess: function() { window.setTimeout( getMessages, 1000 ); } } ); } getMessages(); </script> </body> </html> |
在腳本的開始部分中,您可從登錄頁面提交的參數中獲取用戶名并存儲在會話中。然后加載 Prototype.js JavaScript 庫,它可以完成所有 Ajax 處理。
然后頁面提供了存放消息的位置。該區域由文件后面的
getMessages()
JavaScript 函數填寫。
消息區域的下面是一個表單和用戶輸入消息文本的
textarea
。還有一個按鈕
Add
添加聊天消息。
頁面如 圖 2 所示。
請注意
getMessages()
函數,頁面實際上每 1000 毫秒(1 秒)輪詢一次服務器,檢查是否有新消息,并把結果輸出到頁面上方的消息區域。本文
后面
還要詳細介紹輪詢,我想首先完成基本的實現,messages.php 頁面返回當前的消息列表。該頁如
清單 4
所示。
<table> <?php // Install the DB module using 'pear install DB' require_once 'DB.php'; $db =& DB::Connect( 'mysql://root@localhost/chat', array() ); if (PEAR::isError($db)) { die($db->getMessage()); } $res = $db->query('SELECT * FROM messages' ); while( $res->fetchInto( $row ) ) { ?> <tr><td><?php echo($row[1]) ?></td> <td><?php echo($row[2]) ?></td></tr> <?php } ?> </table> |
腳本的一開始用 DB 庫連接到數據庫,這個庫可從 PEAR 下載(請參閱 參考資料 )。如果還沒有安裝這個庫,可通過下面的命令完成:
% pear install DB |
PEAR 安裝后,腳本可以查詢當前的消息,檢索每一行,輸出用戶名和消息文本。
最后還有 add.php 腳本,從頁面上
addmessage()
函數的 Prototype.js Ajax 代碼中調用。該腳本從會話中取得消息文本和用戶名,然后在消息表中插入新的一行。代碼如
清單 5
所示。
<?php require_once("DB.php"); $db =& DB::Connect( 'mysql://root@localhost/chat', array() ); if (PEAR::isError($db)) { die($db->getMessage()); } $sth = $db->prepare( 'INSERT INTO messages VALUES ( null, ?, ? )' ); $db->execute( $sth, array( $_SESSION['user'], $_POST['message'] ) ); ?> <table> <?php $res = $db->query('SELECT * FROM messages' ); while( $res->fetchInto( $row ) ) { ?> <tr><td><?php echo($row[1]) ?></td> <td><?php echo($row[2]) ?></td></tr> <?php } ?> </table> |
add.php 腳本還返回當前的消息列表,因為原頁面中的 Ajax 代碼要從返回的 HTML 代碼更新聊天記錄。這樣用戶就能馬上看到添加到會話中的文本。
聊天系統的基本結構就是這些。下一節說明如何改進輪詢的效率。
![]() ![]() |
![]()
|
這個原始的聊天系統中,頁面每秒請求一次對話的所有聊天記錄。雖然對于較短的對話影響不大,但是如果對話很長,性能問題就顯現出來了。所幸的是解決起來很簡單。每條消息都有
message_id
,這個數字自動遞增。因此,如果知道已經有了屬于某個 ID 的消息,只需要請求出現在此 ID 之后的消息就可以。這樣可以大大降低消息傳遞的數量。多數請求很可能沒有新的消息,傳遞的包就會變小。
采用效率更高的設計需要稍微修改 chat.php 頁面,如 清單 6 所示。
<?php if ( array_key_exists( 'username', $_POST ) ) { $_SESSION['user'] = $_POST['username']; } $user = $_SESSION['user']; ?> <html> <head><title><?php echo( $user ) ?> - Chatting</title> <script src="prototype.js"></script> </head> <body> <div style="height:400px;overflow:auto;"> <table id="chat"> </table> </div> <script> function addmessage() { new Ajax.Request( 'add.php', { method: 'post', parameters: $('chatmessage').serialize(), onSuccess: function( transport ) { $('messagetext').value = ''; } } ); } </script> <form id="chatmessage"> <textarea name="message" id="messagetext"> </textarea> </form> <button onclick="addmessage()">Add</button> <script> var lastid = 0; function getMessages() { new Ajax.Request( 'messages.php?id='+lastid, { onSuccess: function( transport ) { var messages = transport.responseXML.getElementsByTagName( 'message' ); for( var i = 0; i < messages.length; i++ ) { var message = messages[i].firstChild.nodeValue; var user = messages[i].getAttribute('user'); var id = parseInt( messages[i].getAttribute('id') ); if ( id > lastid ) { var elTR = $('chat').insertRow( -1 ); var elTD1 = elTR.insertCell( -1 ); elTD1.appendChild( document.createTextNode( user ) ); var elTD2 = elTR.insertCell( -1 ); elTD2.appendChild( document.createTextNode( message ) ); lastid = id; } } window.setTimeout( getMessages, 1000 ); } } ); } getMessages(); </script> </body> </html> |
不再用 “chat”
<div>
標記包含所有的消息,現在改為
<table>
標記,收到新消息的時候動態地追加一行。可以看到
getMessages()
函數中的相應變化,和第一個版本相比長了一些。
新版本的
getMessages()
預期 messages.php 頁面的結果是包含新消息的 XML 塊。messages.php 增加了一個參數
id
,即頁面顯示的最后一條消息的
message_id
。一開始 ID 為 0,因而 messages.php 頁面返回所有的消息。此后則發送到目前為止顯示過的最后一條消息的 ID。
XML 響應用
onSuccess
處理程序分解成元素,每個元素使用標準 DHTML 文檔對象模型(DOM)函數添加到表格中,如
insertRow()
、
insertCell()
和
appendChild()
。
修改后的 messages.php 文件返回 XML 而不是 HTML,如 清單 7 所示。
<?php require_once("DB.php"); header( 'Content-type: text/xml' ); $id = 0; if ( array_key_exists( 'id', $_GET ) ) { $id = $_GET['id']; } $db =& DB::Connect( 'mysql://root@localhost/chat', array() ); if (PEAR::isError($db)) { die($db->getMessage()); } ?> <messages> <?php $res = $db->query( 'SELECT * FROM messages WHERE message_id > ?', $id ); while( $res->fetchInto( $row ) ) { ?> <message id="<?php echo($row[0]) ?>" user="<?php echo($row[1]) ?>"> <?php echo($row[2]) ?> </message> <?php } ?> </messages> |
圖 3 顯示了新的改進后的版本。
從外觀上來說沒有什么改變。但是和原來的相比效率要高得多。
![]() ![]() |
![]()
|
如果剛接觸 Ajax 或者僅對該領域有所了解,“輪詢” 的概念可能讓您感到害怕。不幸的是,輪詢是惟一的辦法。要在客戶機和服務器之間建立連續管道,同時又不需要在兩端安裝特定軟件,尚不存在可實現此目的的跨平臺、跨瀏覽器方法。即便這樣,可能還需要對防火墻進行專門配置才行得通。因此,如果需要人人能用的一種簡便辦法,Ajax 和輪詢是惟一的可能。
但是不斷宣傳和鼓吹的 “實時” 在哪兒呢?輪詢不可能是實時的。真的如此嗎?我認為這取決于您對 實時 的定義。我過去編寫電生理學數據檢索代碼時, 實時 意味著毫秒。我相信地質學家在某些情況下把分、日甚至年看作是 實時 。
如果查閱 Wikipedia,即會發現人類的平均反應時間大約在 200 到 270 毫秒之間。也就是擊一次球的時間。閱讀一條消息并形成答復的時間要長得多,即使您非常投入。因此,等待聊天消息時,200 毫秒左右(可能再長一點)的時間應該足夠了。我設置為 1 秒,而且沒有感覺到不舒服。
作為 developerWorks Ajax 論壇(請參閱 參考資料 )的主持人, 輪詢 和 實時 的問題每月至少遇到一次。我希望對于 Ajax 來說已經揭穿了輪詢和所謂 實時 的面具。建議在考慮某種極其復雜的實時解決方案之前嘗試一下輪詢。這樣至少可以知道嘗試自定義的解決方案之前使用現成的工具能夠做什么。
![]() ![]() |
![]()
|
希望本文為您提供了一個不錯的起點,以此為基礎在您的應用程序中實現自己的聊天系統。下面是一些建議:
- 記錄用戶: 在聊天窗口的旁邊列出目前參加會談的人員。這樣可以告訴人們誰參加了談話,什么時候來的,什么時候退出的。
- 允許多個會談: 允許多個關于不同話題的談話同時進行。
-
支持表情字符:
將
:-)
這樣的字符組合翻譯成適當的笑臉圖像。 - 使用 URL 解析: 在客戶端 JavaScript 代碼中使用正則表達式發現 URL 并轉化成超鏈接。
-
處理 Enter 鍵:
取消
Add
按鈕,通過檢查
textarea
的onkeydown
事件看看用戶是否按下了 Enter 或 Return 鍵。 - 顯示用戶輸入時間: 用戶開始輸入的時候通知服務器,會談的其他人可以看到有人在回復。這樣如果有人打字慢可以將談話結束的感覺減到最低。
-
限制消息的大小:
保持談話順暢的另一個辦法是避免消息過長。限制
textarea
中的最大字符數 — 同樣通過捕獲onkeydown
— 有助于提高交談的速度。
這僅僅是修改上述代碼進行改進的部分想法。如果您這樣做了并且希望在社區中分享您的成果,請告訴我,我可以將其放到 下載 的源代碼中。
![]() ![]() |
![]()
|
我承認我不大喜歡聊天。我從未打開我的聊天客戶機。很長時間內僅使用過一次文本消息。我的聊天標識符是 idratheryouemail 。夠嚴肅的。不過我發現結合當前環境的聊天,比如本文所述的這種情況很吸引人。為什么?因為它主要集中在網站有關的主題上,可以最大限度的避免關于最近 “TomKat” 新聞這類的東拉西扯。
在您的 Web 應用程序中嘗試這段代碼。看看能否讓您的讀者和客戶進行實時交談,并通過 developerWorks Ajax 論壇告訴我效果如何。希望能給您以驚喜。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
