第一次做 push

筆 者 老 是 覺 得 世 界 上 是 沒 有 push 這 種 東 西 的 ,頂 多 就 是 你 pull 得 很 利 害 很 有 技 巧 幾 乎 沒 有 人 察 覺 而 已 。

所 謂 的 push 和 pull 是 什 麼 呢 ?在 一 個 client / server 的 環 境 ,特 別 是 互 聯 網 的 環 境 ,一 般 都 是 client 向 server 發 出 一 個 request,然 後 server 就 傳 回 client 要 求 的 資 料 ,這 就 是 所 謂 的 pull。

而 push 呢 ?理 論 上 就 是 server 可 以 向 client 直 接 發 送 資 料 ,而 不 需 要 client 先 發 request。

最 初 ,所 謂 的 push 就 是 在 client 方 面 不 停 的 向 server 發 出 request。例 如 用 javascript 的 setInterval()去 發 出 一 個 ajax 的 查 詢 ,再 把 傳 回 的 資 料 顯 示 給 使 用 者 。這 就 是 所 謂 的 polling 了 。

較 舊 的 polling 的 interval 是 在 client 那 裡 設 定 的 ,如 果 設 定 得 太 頻 密 ,那 server 端 就 會 很 忙 碌 。試 想 像 用 一 秒 一 次 這 個 頻 率 吧 ,如 果 網 站 上 有 一 百 位 用 家 ,那 server 每 秒 就 會 收 到 一 百 個 request。但 這 個 頻 率 如 果 設 定 得 太 低 的 話 ,應 用 程 式 又 會 不 夠 實 時 (real time)。比 如 說 每 三 秒 更 新 一 次 ,如 果 應 用 程 式 是 下 棋 的 話 ,玩 家 每 完 成 一 步 ,對 手 都 要 等 三 秒 才 開 始 ,就 實 在 是 太 lag (慢 )了 。

在 上 面 的 polling,每 一 次 查 詢 都 是 完 整 的 ,完 成 之 後 都 會 斷 開 連 線 。然 後 又 有 人 想 到 ,如 果 不 斷 開 連 線 ,由 server 端 不 停 的 傳 回 最 新 的 資 料 ,那 就 可 以 減 少 request 的 次 數 了 。這 個 方 法 就 像 是 在 server 端 做 polling。這 樣 做 令 到 request 減 少 了 ,但 同 時 的 應 用 程 式 又 會 比 較 實 時 。

但 上 面 的 兩 個 polling 方 法 ,都 無 可 避 免 地 重 覆 傳 送 相 同 的 數 據 ,特 別 數 據 變 更 得 不 太 頻 密 的 時 候 。這 實 在 是 白 白 浪 費 了 頻 寬 ,費 時 失 事 。

針 對 這 個 問 題 ,又 有 聰 明 的 人 士 想 到 了 long polling 的 解 決 辦 法 。long polling 的 做 法 是 先 由 client 向 server 發 出 一 個 查 詢 ,然 後 server 只 在 有 須 要 時 傳 回 資 料 ,如 果 沒 有 須 要 就 等 待 ,等 到 有 須 要 時 再 傳 回 資 料 。

在 client 端 來 看 ,我 發 出 一 個 查 詢 ,可 能 馬 上 有 資 料 傳 回 來 ,也 可 能 三 分 鐘 後 才 有 資 料 傳 回 來 ,但 傳 回 來 的 資 料 都 是 實 時 ,又 有 用 的 ,而 不 是 重 覆 又 重 覆 的 無 用 資 料 。在 server 端 來 看 ,這 個 作 法 既 減 少 了 request,又 實 保 了 實 時 ,也 減 省 了 頻 寬 。

讓 我 舉 一 個 超 簡 化 了 的 回 合 制 遊 戲 例 子 ,來 說 明 一 下 long polling 是 如 何 達 成 的 。long polling 並 不 是 火 箭 科 學 ,基 本 上 任 一 個 web server 加 上 任 何 一 個 web browser 都 可 以 實 作 成 功 。

<html>
 <head>
 </head>
 <body>
  <div id="active"></div>
  <input type="button" value="Next Player" id="nextplayer" >
 </body>
</html>

上 例 中 ,我 們 用 一 個 div 來 顯 示 自 己 是 不 是 active player。然 後 有 一 個 button 用 來 完 成 自 的 回 合 ,把 主 動 權 交 到 下 一 個 玩 家 。

$(document).ready(function(){

 var userid = 0;
 var active = 0;

 checkuser();

 $('#nextplayer').click(function(){

  if (active==1) {

   $.ajax({
    url:"nextplayer.php?userid=" + userid,
    async: true,
    dataType: "json",
    timeout: 10000,
    complete: function(){
     active=0;
    }
   });

  }

 });

 poll();

 function poll(){

  $.ajax({
   url:"data_json.php?userid=" + userid + "&active=" + active,
   async: true,
   dataType: "json",
   timeout: 1000000,
   success: function(data){
    // update data
    if (data.active=='1') {
     $('#active').text("Active");
     active = 1;
    } else {
     $('#active').text("Inactive");
     active = 0;
    }

   },
   complete: function() {
    setTimeout(
     poll, /* Request next message */
     1000 /* ..after 1 seconds */
    );
   }
  });

 }

 checkuser(){

  $.ajax({
   url:"checkuser.php"
   async: true,
   dataType: "json",
   timeout: 10000,
   success: function(data){
     $userid = data.newuserid;
   }
  });

 }

});

然 後 是 簡 化 了 的 javascript 的 部 分 ,我 用 了 jquery 的 ajax 來 達 成 。在 上 例 中 ,首 先 我 們 一 開 始 就 checkuser() 一 次 ,是 用 來 決 定 自 己 的 userid 的 ,這 部 分 簡 化 了 之 後 如 果 是 ,如 果 你 是 第 一 個 打 開 網 頁 的 ,你 的 userid 就 是 1,第 二 個 ,就 是 2,如 此 類 推 。

然 後 如 果 你 是 active player(就 是 active==1),那 你 就 可 以 按 一 下 Next Player,把 主 動 權 交 給 下 一 個 玩 家 。

因 為 要 在 server 端 才 能 確 定 你 的 userid 和 次 序 ,所 以 兩 者 都 必 須 要 在 server 端 j做 才 成 。完 成 工 作 的 就 是 checkuser.php 和 nextuser.php。這 部 分 應 該 沒 有 難 度 吧 ?我 們 就 直 接 跳 過 了 ,把 精 神 集 中 在 long polling 的 實 作 。

在 上 面 的 jquery 中 ,poll() 函 數 就 是 實 現 long polling 的 client 端 部 分 。我 們 向 data_json.php 提 出 查 詢 ,然 後 按 傳 回 的 結 果 設 定 自 己 究 竟 是 active 還 是 inactive。如 果 是 active 的 ,我 們 就 把 active 寫 在 div 裡 面 。如 果 不 是 ,就 寫 上 inactive。完 成 的 話 就 等 一 秒 ,再 poll 另 一 次 (setTimeout(poll,1000))。

比 較 要 注 意 的 ,就 是 我 在 poll 函 數 裡 面 ,除 了 傳 遞 了 自 己 的 userid,還 傳 遞 了 自 己 的 active status。這 兩 者 都 是 必 須 要 的 。

然 而 我 們 看 看 server 端 的 部 分 ,我 這 裡 是 用 php 實 作 的 。

<?php

if (isset($_GET['userid']) and isset($_GET['active'])){

 $userid = $_GET['userid'];
 $client_active = $_GET['active'];

 openConnection();

 for ($i = 0, $timeout = 180; $i < $timeout; $i++ ) {

  $SQL = "SELECT active FROM table WHERE user='$userid';";
  $result = mysql_query($SQL) or die("Couldn t execute query.".mysql_error());
  $row = mysql_fetch_array($result);
  $active = $row[active];

  if ($active<>$client_active) {
   $responce->active = $active;
   echo json_encode($responce);
   flush();
   exit(0);
  }

  usleep(1000000);

 }

 $responce->active = $active;
 echo json_encode($responce);
 flush();

}

?>

在 這 個 超 簡 化 了 的 php 中 ,我 們 先 檢 查 有 沒 有 傳 來 userid 和 active,如 果 有 的 話 ,我 們 就 在 資 料 庫 檢 查 user 現 在 的 active 值 。有 了 現 在 的 active 值 ,再 對 比 client 傳 來 的 值 ,如 果 是 一 樣 的 話 ,就 是 沒 有 變 化 ,於 是 就 不 用 傳 回 資 料 ,先 等 一 秒 ,再 作 下 一 次 的 檢 查 。

直 到 active 的 值 不 一 樣 ($active<>$client_active),我 們 就 馬 上 傳 回 最 新 的 資 料 。在 實 際 的 應 用 上 ,整 個 網 路 的 各 個 部 分 都 會 有 自 己 的 timeout 的 ,所 以 一 個 request 不 可 能 無 限 地 延 續 下 去 。在 上 例 中 ,我 設 定 為 最 多 查 詢 180 次 ,按 每 一 秒 鐘 查 詢 一 次 算 ,一 共 就 是 3 分 鐘 。就 是 說 ,即 使 三 分 鐘 都 還 沒 有 變 化 ,我 還 是 會 傳 回 一 次 結 果 的 。然 後 再 由 client 端 發 送 另 一 次 request。

筆 者 舉 的 這 個 例 子 ,在 最 一 般 的 browser 和 最 一 般 的 web server 就 可 以 成 功 執 行 ,完 全 是 使 用 現 有 最 根 本 的 技 術 。你 完 全 不 需 要 在 client 和 server 端 安 裝 任 何 額 外 的 東 西 。據 了 解 ,html 5 已 經 內 建 了 push 的 技 術 ,但 因 為 安 全 問 題 ,大 部 分 的 browser 還 沒 有 支 援 html 5 的 push。看 來 ,真 真 正 正 的 push 還 是 得 多 等 一 會 ,哈 哈 。

 

ctleung張 先 生 ,男 性 ,肖 龍 。
職 業 :I.T. Consultant
簡 介 :不 好 好 讀 書 ;七 尺 差 五 寸 ,手 長 過 膝 ,雙 耳 垂 肩 ;性 寬 和 ,寡 言 語 ,喜 怒 不 形 於 色 。據 說 少 時 曾 斬 白 蛇 於 鳳 凰 山 下 ……
This entry was posted in Computer & Network and tagged , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>