Ajax và XML: Ajax với trò chuyện trực tuyến

Sử dụng Ajax và PHP để xây dựng một ứng dụng trò chuyện trực tuyến

Hãy tìm hiểu để xây dựng một hệ thống trò chuyện trực tuyến (chat) trong ứng dụng Web của bạn với JavaScript™ không đồng bộ + XML (Ajax) và PHP. Các khách hàng của bạn có thể trò chuyện với bạn và với nhau về nội dung của trang web mà không cần phải tải về hay cài đặt bất kỳ phần mềm truyền tin nhanh chuyên dùng nào.

Jack Herrington, Tổng biên tập, Code Generation Network

Jack D. Herrington là kỹ sư phần mềm cao cấp với hơn 20 năm kinh nghiệm. Ông là tác giả của ba cuốn sách: Code Generation in Action, Podcasting Hacks và PHP Hacks. Ông cũng đã viết hơn 30 bài báo.



25 05 2011

Các nhà phát triển nói rất nhiều về cộng đồng khi thời hạn Web 2.0 đang đến gần. Và cho dù bạn nghĩ rằng đó là cường điệu hay không, thì ý tưởng lôi kéo các khách hàng hoặc các độc giả của bạn vào một cuộc trò chuyện tức thời về các chủ đề đang bàn hoặc các sản phẩm bạn đang bán là khá hấp dẫn. Nhưng làm thế nào để bạn có được điều đó? Bạn có thể đặt một hộp trò chuyện trực tuyến trên cùng một trang khi liệt kê sản phẩm của bạn để cho các khách hàng của bạn không phải cài đặt phần mềm chuyên dùng hoặc thậm chí Adobe Flash Player? Chắc chắn rồi! Hóa ra, bạn có thể làm tất cả điều đó bằng các công cụ có sẵn, miễn phí như PHP, MySQL, HTML động (DHTML), Ajax và thư viện Prototype.js.

Không dài dòng nữa, hãy nhảy ngay vào triển khai thực hiện thôi.

Đăng nhập

Bước đầu tiên của việc trò chuyện trực tuyến là phải có một mã định danh. Như vậy cần có một trang đăng nhập mở đầu, như được chỉ ra trong Liệt kê 1.

Liệt kê 1. index.html
<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>

Trong Hình 1, bạn có thể thấy một ảnh chụp màn hình của trang này.

Hình 1. Cửa sổ đăng nhập để trò chuyện trực tuyến
Cửa sổ đăng nhập để trò chuyện trực tuyến

Lưu ý: Tôi chỉ cần điều này làm ví dụ, vì tôi muốn xác định được ai đang nói gì. Đối với ứng dụng của mình, có thể bạn đã có một thông tin đăng nhập, vì vậy bạn có thể sử dụng tên người dùng hiện tại của mình.


Hệ thống trò chuyện trực tuyến cơ bản

Một hệ thống trò chuyện trực tuyến thực sự chỉ là một bảng các chuỗi trong đó mỗi chuỗi được gán cho một cá nhân. Phiên bản cơ bản nhất của lược đồ được chỉ ra trong Liệt kê 2.

Liệt kê 2. chat.sql
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 )
);

Kịch bản lệnh này chứa một mã định danh (ID) của thông báo được tăng-tự động, tên người dùng và thông báo đó. Nếu bạn nghĩ rằng điều này quan trọng, thì bạn cũng có thể thêm dấu thời gian cho mỗi thông báo để theo dõi khi nó được gửi đi.

Nếu bạn muốn quản lý nhiều cuộc trò chuyện về các chủ đề khác nhau, bạn phải có bảng khác để theo dõi các chủ đề và bao gồm một topic_id có liên quan trong bảng thông báo. Tôi muốn giữ cho ví dụ này đơn giản, vì thế tôi đã sử dụng lược đồ đơn giản nhất có thể.

Để thiết lập cơ sở dữ liệu và nạp lược đồ này, tôi sử dụng các hướng dẫn sau:

% mysqladmin create chat
% mysql chat < chat.sql

Tùy thuộc vào thiết lập của máy chủ MySQL của bạn và các thiết lập bảo mật và mật khẩu của máy chủ, lệnh của bạn có thể hơi khác một chút.

Giao diện người dùng (UI) cơ bản của cuộc trò chuyện trực tuyến được chỉ ra trong Liệt kê 3.

Liệt kê 3. chat.php
<?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>

Ở đầu kịch bản lệnh, bạn có được tên người dùng từ các đối số được đăng trong trang đăng nhập và lưu nó trong phiên làm việc. Rồi trang này tiếp tục nạp thư viện JavaScript prototype.js vô giá, thư viện này sẽ quản lý tất cả các công việc Ajax cho bạn.

Sau đó, trang này có một chỗ để các thông báo trò chuyện trực tuyến đưa vào đó. Vùng này được điền vào bằng hàm getMessages() nằm ở cuối tệp.

Dưới vùng các thông báo là một biểu mẫu và một vùng văn bản textarea để người sử dụng gõ văn bản thông báo của mình vào trong đó. Bạn cũng có một nút có tên là Add để thêm thông báo đó vào cuộc trò chuyện trực tuyến.

Trang web này trông giống như Hình 2.

Hình 2. Cửa sổ trò chuyện trực tuyến đơn giản
Cửa sổ trò chuyện trực tuyến đơn giản

Việc xem xét cẩn thận hàm getMessages() cho thấy rằng trang này quả thực đang thăm dò máy chủ mỗi 1.000 mili giây (tức 1 giây) một lần để kiểm tra các thông báo mới và đưa kết quả đầu ra của cuộc gọi vào vùng các thông báo ở đầu trang. Tôi nói thêm sau về việc thăm dò trong bài viết này, nhưng vào lúc này, tôi muốn kết thúc việc triển khai thực hiện cơ bản của cuộc trò chuyện trực tuyến bằng cách cho thấy trang messages.php, trả về một tập các thông báo hiện tại. Trang này được chỉ ra trong Liệt kê 4.

Liệt kê 4. messages.php
<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>

Ở đầu của kịch bản lệnh, tôi nối cơ sở dữ liệu với thư viện DB, có sẵn trong PEAR (xem phần Tài nguyên). Nếu bạn chưa cài đặt thư viện này, bạn có thể cài đặt bằng lệnh sau:

% pear install DB

Với PEAR đã cài đặt rồi, kịch bản lệnh có thể truy vấn các thông báo hiện tại, tìm nạp mỗi hàng, và cung cấp tên người dùng và văn bản nhận xét.

Kịch bản lệnh cuối cùng là kịch bản lệnh add.php, được gọi là từ mã Ajax prototype.js trong hàm addmessage() trên trang này. Kịch bản lệnh này nhận văn bản thông báo và tên người dùng từ phiên làm việc và chèn một hàng mới vào bảng các thông báo. Mã này được chỉ ra trong Liệt kê 5.

Liệt kê 5. Kịch bản lệnh add.php
<?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()); }

$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>

Kịch bản lệnh add.php cũng trả về danh sách các thông báo hiện tại, vì mã Ajax trên trang gốc cập nhật các thông báo trò chuyện trực tuyến từ mã HTML được trả về. Hoạt động này phản hồi ngay lập tức cho những người dùng đã thêm bình luận trong cuộc trò chuyện này.

Đây là những vấn đề cơ bản của hệ thống trò chuyện trực tuyến. Phần tiếp theo trình bày cách thực hiện thăm dò hiệu quả hơn một chút.


Tạo một hệ thống trò chuyện trực tuyến tốt hơn

Với hệ thống trò chuyện trực tuyến ban đầu này, trang này yêu cầu tất cả các thông báo trò chuyện trực tuyến theo từng giây cho cuộc trò chuyện. Mặc dù không phải quá tồi với một cuộc trò chuyện ngắn, nhưng với một cuộc trò chuyện nào đó dài hơn, đó sẽ là một vấn đề hiệu năng thực sự. May mắn thay, có một giải pháp khá dễ dàng. Mã định danh thông báo message_id có liên kết với mỗi thông báo và mã này cứ tăng dần lên. Vì vậy, nếu bạn biết rằng bạn có các thông báo đến một mã định danh nhất định, thì bạn chỉ cần yêu cầu các thông báo bất kỳ xuất hiện sau mã định danh đó. Điều đó giữ cho lưu lượng thông báo không tăng lên trong phạm vi lớn. Đối với hầu hết các yêu cầu, có khả năng bạn không nhận được các thông báo mới, do chúng là một gói rất nhỏ.

Việc thay đổi thiết lập này theo thiết kế hiệu quả hơn đòi hỏi có một số thay đổi về trang chat.php, như trong Liệt kê 6.

Liệt kê 6. chat.php (đã sửa lại)
<?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>

Thay vì một thẻ <div> "chat" để chứa tất cả các thông báo, bây giờ bạn đã có một thẻ <table>, bạn thêm các hàng vào thẻ đó một cách động cho mỗi thông báo mới khi nó đến. Bạn có thể thấy sự thay đổi trong hàm getMessages() đã tăng trọng lượng chút ít kể từ phiên bản đầu tiên của nó.

Phiên bản mới này của getMessages() hy vọng các kết quả của trang messages.php là một khối XML với các thông báo mới. Bây giờ trang messages.php cũng có một tham số được gọi là id, là message_id của thông báo cuối mà trang này đã thấy. Thời gian vượt quá đầu tiên, ID này bằng 0, do đó trang messages.php trả về mọi thứ nó có. Sau đó, nó gửi ID của thông báo cuối cùng đã thấy cho đến nay.

Đáp ứng được trình xử lý onSuccess ngắt ra và mỗi phần tử được thêm vào bảng đó bằng cách sử dụng các hàm DOM (document object model – Mô hình đối tượng tài liệu) của DHTML chuẩn như insertRow(), insertCell()appendChild().

Phiên bản nâng cấp của tệp messages.php trả về XML thay vì HTML được chỉ ra trong Liệt kê 7.

Liệt kê 7. messages.php
<?php
// Install the DB module using 'pear install DB'
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>

Hình 3 cho thấy phiên bản nâng cấp, mới.

Hình 3. Cửa sổ trò chuyện trực tuyến tối ưu hóa
Cửa sổ trò chuyện trực tuyến tối ưu hóa

Nó vẫn chưa thay đổi chút nào về dáng vẻ và cảm nhận của nó. Nhưng nó hiệu quả hơn rất nhiều so với ban đầu.


Huyền thoại về "thời gian thực"

Nếu bạn là người mới bắt đầu với Ajax hay thực sự chỉ là một lập trình viên trung bình đã làm việc trong lĩnh vực này đủ lâu để biết rõ hơn, thì ý tưởng của "thăm dò" có thể là cú đánh lên cột sống của bạn. Thật không may, thăm dò là tất cả mọi thứ bạn có. Không có cách nhờ vào nền tảng, vào trình duyệt để tạo ra một đường ống liên tục giữa máy khách và máy chủ mà không cần phần mềm chuyên dùng được cài đặt trên cả hai đầu đường ống. Và thậm chí sau đó, bạn có thể cần một cấu hình tường lửa chuyên dùng để thực hiện tất cả mọi thứ xảy ra với nó. Vì vậy, nếu bạn cần một giải pháp dễ sử dụng cho mọi người, thì Ajax và việc thăm dò là những gì bạn có.

Nhưng thế nào là tiếp thị và sự khẳng định về "thời gian thực"? Việc thăm dò không thể theo thời gian thực. Hoặc nó có thể là thời gian thực không? Tôi nghĩ rằng nó phụ thuộc vào định nghĩa về thuật ngữ thời gian thực với bạn là gì. Khi tôi thường viết mã để thu thập dữ liệu điện sinh lý học, thời gian thực có nghĩa là micro giây. Tôi chắc chắn rằng các nhà địa chất có thể sẵn sàng coi các phút, các ngày hoặc thậm chí nhiều năm trong một số trường hợp là thời gian thực.

Nếu xem trên Wikipedia, tôi thấy rằng thời gian phản ứng trung bình của con người nằm trong khoảng thời gian nào đó giữa 200 và 270 mili giây. Và đó chỉ là thời gian để làm một cái gì đó giống như đá một quả bóng. Thời gian để đọc thông báo và bắt đầu đưa ra một câu trả lời thường kéo dài hơn, ngay cả khi bạn hoàn toàn tham gia vào cuộc trò chuyện này. Vì vậy thực ra, bất kỳ số lượng thời gian nào trong khoảng 200-mili giây (và có lẽ dài hơn một chút nữa) là tốt khi chờ đợi những thông báo trò chuyện này. Tôi đặt nó là một giây và không cảm thấy quá tồi với mình.

Theo như người điều phối diễn đàn Ajax trên developerWorks (xem phần Tài nguyên), vấn đề về thăm dòthời gian thực được đăng lên ít nhất mỗi tháng một lần. Hy vọng rằng, tôi đã tiết lộ vấn đề thăm dò và các vấn đề về thời gian thực khi nó xảy ra với Ajax. Đề nghị của tôi là cố gắng thực hiện thăm dò trước khi bạn cố gắng tạo ra một số giải pháp thời gian thực quá phức tạp. Ít nhất, hãy biết những gì có thể được thực hiện với các công cụ lập sẵn trước khi bạn cố gắng làm thử một giải pháp tùy chỉnh.


Tiến lên từ đây

Hy vọng rằng, những gì tôi đã cung cấp cho bạn ở đây là một điểm khởi đầu tốt với việc thực hiện riêng của bạn trong một hệ thống trò chuyện trực tuyến trong ứng dụng của bạn. Dưới đây là một số ý tưởng về đích đến tiếp theo:

  • Theo dõi những người sử dụng: Đưa ra một danh sách những người tham gia tích cực vào cuộc trò chuyện trong suốt cuộc trò chuyện trực tuyến. Làm như vậy cho phép mọi người biết ai ở bên kia và lúc nào họ đến và đi.
  • Cho phép nhiều cuộc trò chuyện: Cho phép nhiều cuộc trò chuyện về các chủ đề khác nhau xảy ra đồng thời.
  • Cho phép các biểu tượng cảm xúc: Dịch các nhóm ký tự ví dụ như :-) thành hình ảnh một khuôn mặt cười thích hợp.
  • Sử dụng phân tích cú pháp URL: Sử dụng biểu thức chính quy trong mã JavaScrip phía máy khách để tìm các URL và biến chúng thành các siêu liên kết.
  • Quản lý phím Enter: Thay vì có một nút Add, hãy để ý xem người sử dụng nhấn phím Enter hoặc phím Return bằng cách gắn lên trên sự kiện onkeydown trong textarea.
  • Cho thấy khi người dùng đang gõ phím: Báo cho máy chủ khi một người dùng đã bắt đầu gõ phím, để cho những người tham gia khác có thể thấy rằng đang chờ một câu trả lời . Điều này làm giảm thiểu cảm giác rằng cuộc trò chuyện đã mất tiêu rồi khi bạn có những người gõ phím chậm.
  • Hạn chế kích thước của một thông báo được gửi đi: Một cách khác để giữ cho cuộc trò chuyện tiếp diễn là duy trì các thông báo nhỏ. Hạn chế số lượng tối đa các ký tự trong textarea—một lần nữa bằng cách chặn onkeydown—để giúp tăng tốc độ các cuộc trò chuyện.

Đây chỉ là một vài ý tưởng cho các cải tiến mà bạn có thể làm cho mã này. Nếu bạn làm như vậy và bạn muốn chia sẻ những thay đổi của mình với cộng đồng, xin vui lòng cho tôi biết và tôi có thể cung cấp cho họ một phần mã nguồn có sẵn từ phần Tải về.


Kết luận

Tôi thừa nhận rằng mình thực sự không phải là một người hay trò chuyện trực tuyến (chatter). Tôi chưa bao giờ có bạn chat khách của mình. Tôi chỉ sử dụng các thông báo văn bản một lần trong một thời gian dài. Phương tiện trò chuyện của tôi là idratheryouemail (tôi thích thư điện tử của bạn). Không đùa đâu. Nhưng tôi đã thấy rằng trò chuyện theo ngữ cảnh, ví dụ như những gì được thể hiện trong bài viết này, thực sự hấp dẫn. Tại sao? Vì nó được tập trung vào nội dung chủ đề mà trang web giải quyết, giúp bạn giảm thiểu cuộc nói chuyện lạc hướng về những tin tức "TomKat" gần đây nhất.

Hãy thử đoạn mã ví dụ này trong ứng dụng Web của bạn. Hãy xem liệu bạn có thể làm cho các độc giả và các khách hàng của mình tham gia vào một cuộc trò chuyện thời gian thực và hãy cho tôi biết làm thế nào để cuộc trò chuyện như vậy vẫn tiếp diễn trên trang web diễn đàn Ajax của developerWorks. Tôi hy vọng bạn sẽ ngạc nhiên thích thú.


Tải về

Mô tảTênKích thước
Source code for chat applicationx-ajaxxml8-chat.zip38KB

Tài nguyên

Học tập

Lấy sản phẩm và công nghệ

Thảo luận

Bình luận

developerWorks: Đăng nhập

Các trường được đánh dấu hoa thị là bắt buộc (*).


Bạn cần một ID của IBM?
Bạn quên định danh?


Bạn quên mật khẩu?
Đổi mật khẩu

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Ở lần bạn đăng nhập đầu tiên vào trang developerWorks, một hồ sơ cá nhân của bạn được tạo ra. Thông tin trong bản hồ sơ này (tên bạn, nước/vùng lãnh thổ, và tên cơ quan) sẽ được trưng ra cho mọi người và sẽ đi cùng các nội dung mà bạn đăng, trừ khi bạn chọn việc ẩn tên cơ quan của bạn. Bạn có thể cập nhật tài khoản trên trang IBM bất cứ khi nào.

Thông tin gửi đi được đảm bảo an toàn.

Chọn tên hiển thị của bạn



Lần đầu tiên bạn đăng nhập vào trang developerWorks, một bản trích ngang được tạo ra cho bạn, bạn cần phải chọn một tên để hiển thị. Tên hiển thị của bạn sẽ đi kèm theo các nội dung mà bạn đăng tải trên developerWorks.

Tên hiển thị cần có từ 3 đến 30 ký tự. Tên xuất hiện của bạn phải là duy nhất trên trang Cộng đồng developerWorks và vì lí do an ninh nó không phải là địa chỉ email của bạn.

Các trường được đánh dấu hoa thị là bắt buộc (*).

(Tên hiển thị cần có từ 3 đến 30 ký tự)

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Thông tin gửi đi được đảm bảo an toàn.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=70
Zone=Nguồn mở, SOA và dịch vụ Web
ArticleID=660480
ArticleTitle=Ajax và XML: Ajax với trò chuyện trực tuyến
publish-date=05252011