请选择 进入手机版 | 继续访问电脑版
设为首页收藏本站

Endige-新锐工作室

 找回密码
 立即注册

用新浪微博连接

一步搞定

QQ登录

只需一步,快速开始

查看: 7803|回复: 0

php实现实时通信 [复制链接]

Rank: 2

威望
0
金钱
43
贡献
0
新豆
0
最后登录
1970-1-1
注册时间
2011-11-23
精华
0
积分
52
阅读权限
20
帖子
9
发表于 2011-11-29 20:45:19 |显示全部楼层
实现实时通信一般有两种方式:
socket或comet。socket是比较好的解决方案,问题在于不是所有的浏览器都兼容,服务器端实现起来也稍微有点麻烦。相比之下,comet(基于HTTP长连接的"服务器推")实现起来更加方便,而且兼容所有的浏览器。所以这次就来说说comet的php实现。

comet也有好几种实现方式,如iframe, http long request,二者的区别可以参考这篇文章。本文主要探讨http long request实现实时通信。

先说说http长链接是怎么回事,通俗点讲就是服务器不是一收到请求就直接吐数据,而是在那憋啊憋,一直憋到憋不住了,才告诉你执行结果。
  1. <?php

  2. $count = 10;

  3. for($i=0; $i<$count; $i++)
  4. {
  5.         // do something ...
  6.         sleep(2);
  7. }

  8. echo '憋死我了';
  9. ?>


至于憋多长时间,就看具体应用了,如果憋太久的话,服务器资源的占用也会是个问题。

现在我们就要通过这种方法来实现实时通信(其实是准实时),先说一下原理:

1. 客户端发起一个ajax长链接查询,然后服务端就开始执行代码,主要是检查某个文件是否被更新,如果没有,睡一会(sleep),醒来接着检查
2. 如果客户端又发起了一个查询链接(正常请求),服务端收到后,处理请求,处理完毕后更新某个特定文件的modify time
3. 这时第一次ajax查询的后台代码还在执行,发现某个文件被更新,说明来了新请求,输出对应的结果
4. 第一次ajax查询的callback被触发,更新页面,然后再发起一个新的ajax长链接
实战

客户端


  1. <html>
  2.     <head>
  3.         <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  4.         <title>Comet Test</title>
  5.     </head>
  6.     <body>
  7.         <p><a class='customAlert' href="#">publish customAlert</a></p>
  8.         <p><a class='customAlert2' href="#">publish customAlert2</a></p>
  9.         <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.5/jquery.min.js" type="text/javascript"></script>
  10.         <script src="NovComet.js" type="text/javascript"></script>
  11.         <script type="text/javascript">
  12.                         NovComet.subscribe('customAlert', function(data){
  13.                                 console.log('customAlert');
  14.                                 //console.log(data);
  15.                         }).subscribe('customAlert2', function(data){
  16.                                 console.log('customAlert2');
  17.                                 //console.log(data);
  18.                         });

  19.                         $(document).ready(function() {
  20.                                 $("a.customAlert").click(function(event) {
  21.                                         NovComet.publish('customAlert');
  22.                                 });

  23.                                 $("a.customAlert2").click(function(event) {
  24.                                         NovComet.publish('customAlert2');
  25.                                 });
  26.                                 NovComet.run();
  27.                         });
  28.         </script>
  29.     </body>
  30. </html>

这段代码说的是,有个NovComet的Object,注册了customAlert和customAlert2事件,当页面载入完成时,对两个按钮又加了监听事件,当点击时NovComet会发布customAlert或customAlert2事件,然后NovComet执行了run方法。

NovComet

  1. //NovComet.js
  2. NovComet = {
  3.     sleepTime: 1000,
  4.     _subscribed: {},
  5.     _timeout: undefined,
  6.     _baseurl: "comet.php",
  7.     _args: '',
  8.     _urlParam: 'subscribed',

  9.     subscribe: function(id, callback) {
  10.         NovComet._subscribed[id] = {
  11.             cbk: callback,
  12.             timestamp: NovComet._getCurrentTimestamp()
  13.         };
  14.         return NovComet;
  15.     },

  16.     _refresh: function() {
  17.         NovComet._timeout = setTimeout(function() {
  18.             NovComet.run()
  19.         }, NovComet.sleepTime);
  20.     },

  21.     init: function(baseurl) {
  22.         if (baseurl!=undefined) {
  23.             NovComet._baseurl = baseurl;
  24.         }
  25.     },

  26.     _getCurrentTimestamp: function() {
  27.         return Math.round(new Date().getTime() / 1000);
  28.     },

  29.     run: function() {
  30.         var cometCheckUrl = NovComet._baseurl + '?' + NovComet._args;
  31.         for (var id in NovComet._subscribed) {
  32.             var currentTimestamp = NovComet._subscribed[id]['timestamp'];

  33.             cometCheckUrl += '&' + NovComet._urlParam+ '[' + id + ']=' +
  34.                currentTimestamp;
  35.         }
  36.         cometCheckUrl += '&' + NovComet._getCurrentTimestamp();
  37.         $.getJSON(cometCheckUrl, function(data){
  38.             switch(data.s) {
  39.                 case 0: // sin cambios
  40.                     NovComet._refresh();
  41.                     break;
  42.                 case 1: // trigger
  43.                     for (var id in data['k']) {
  44.                         NovComet._subscribed[id]['timestamp'] = data['k'][id];
  45.                         NovComet._subscribed[id].cbk(data.k);
  46.                     }
  47.                     NovComet._refresh();
  48.                     break;
  49.             }
  50.         });

  51.     },

  52.     publish: function(id) {
  53.         var cometPublishUrl = NovComet._baseurl + '?' + NovComet._args;
  54.         cometPublishUrl += '&publish=' + id;
  55.         $.getJSON(cometPublishUrl);
  56.     }
  57. };

NovComet的run方法首先把之前注册的几个事件串成一个url,并且很狡猾地使用了"[]",类似:
?subscribed[customAlert]=1300016814&subscribed[customAlert2]=1300016814&1300016825,这样php收到后,就会得到$_GET[subscribed]数组,最后那个时间戳是为了避免请求被缓存。如果收到后台传过来的数据data的s值为0,说明什么也没发生,隔1秒后继续执行;如果data.s的值为1,说明NovComet的publish事件被触发,则调用对应的callback。

publish方法执行后,会构造一个类似: ?publish=customAlert 这样一个url发送到后台。后台检测到pubish参数,则获取该参数的值,并更新对应文件的mtime。

服务端

  1. <?php
  2. // comet.php
  3. include('NovComet.php');

  4. $comet = new NovComet();
  5. $publish = filter_input(INPUT_GET, 'publish', FILTER_SANITIZE_STRING);
  6. if ($publish != '') {
  7.     echo $comet->publish($publish);
  8. } else {
  9.     foreach (filter_var_array($_GET['subscribed'], FILTER_SANITIZE_NUMBER_INT) as $key => $value) {
  10.         $comet->setVar($key, $value);
  11.     }
  12.     echo $comet->run();
  13. }
  14. ?>

如果收到publish参数,直接输出,否则执行run方法,至于run是怎么回事,且看下码。
  1. <?php
  2. // NovComet.php
  3. class NovComet {
  4.     const COMET_OK = 0;
  5.     const COMET_CHANGED = 1;

  6.     private $_tries;
  7.     private $_var;
  8.     private $_sleep;
  9.     private $_ids = array();
  10.     private $_callback = null;

  11.     public function  __construct($tries = 20, $sleep = 2)
  12.     {
  13.         $this->_tries = $tries;
  14.         $this->_sleep = $sleep;
  15.     }

  16.     public function setVar($key, $value)
  17.     {
  18.         $this->_vars[$key] = $value;
  19.     }

  20.     public function setTries($tries)
  21.     {
  22.         $this->_tries = $tries;
  23.     }

  24.     public function setSleepTime($sleep)
  25.     {
  26.         $this->_sleep = $sleep;
  27.     }

  28.     public function setCallbackCheck($callback)
  29.     {
  30.         $this->_callback = $callback;
  31.     }

  32.     const DEFAULT_COMET_PATH = "/dev/shm/%s.comet";

  33.     public function run() {
  34.         if (is_null($this->_callback)) {
  35.             $defaultCometPAth = self:\"\"EFAULT_COMET_PATH;
  36.             $callback = function($id) use ($defaultCometPAth) {
  37.                 $cometFile = sprintf($defaultCometPAth, $id);
  38.                 return (is_file($cometFile)) ? filemtime($cometFile) : 0;
  39.             };
  40.         } else {
  41.             $callback = $this->_callback;
  42.         }

  43.         for ($i = 0; $i < $this->_tries; $i++) {
  44.             foreach ($this->_vars as $id => $timestamp) {
  45.                 if ((integer) $timestamp == 0) {
  46.                     $timestamp = time();
  47.                 }
  48.                 $fileTimestamp = $callback($id);
  49.                 if ($fileTimestamp > $timestamp) {
  50.                     $out[$id] = $fileTimestamp;
  51.                 }
  52.                 clearstatcache();
  53.             }
  54.             if (count($out) > 0) {
  55.                 return json_encode(array('s' => self::COMET_CHANGED, 'k' => $out));
  56.             }
  57.             sleep($this->_sleep);
  58.         }
  59.         return json_encode(array('s' => self::COMET_OK));
  60.     }

  61.     public function publish($id)
  62.     {
  63.         return json_encode(touch(sprintf(self:\"\"EFAULT_COMET_PATH, $id)));
  64.     }
  65. }
  66. ?>

可以看到publish时,创建了一个以$id命名的文件。run时,如果发现该$id文件存在,且时间戳大于之前保存的该$id对应的时间戳(通过setVar设置的),说明$id事件被触发,处理完后把$id放到$out数组中,然后判断一下$out数组是否为空,如果不为空,则输出一段json。

如果一段时间内都没有触发事件(for循环执行完毕),也输出一段json,告诉前端执行完了,但是没有任何新情况。

说明

可以在客户端监听/发布多个事件
监听事件时,可以传一个callback,这样收到数据时就会出发该callback
当监听事件时,会传一个时间戳
当事件被publish时,会向后台发一个请求,并传递一个新的时间戳
服务端不会一直执行,如果指定时间内,没有任何请求被触发,则结束运行
客户端会重复上述过程(setTimeout & NovComet.run())
最后来一张图说明一下

1. 运行一段时间后,没有收到任何publish事件,服务端结束执行
2. 服务端返回一段json
3. 客户端触发了一个事件,服务端收到事件,返回一段新的json
4. callback被触发
5. 客户端进入下一次的ajax长链接查询
附件: 你需要登录才可以下载或查看附件。没有帐号?立即注册
您需要登录后才可以回帖 登录 | 立即注册

馨然玫瑰花茶

Archiver|手机版|Endige Inc   

GMT+8, 2017-11-18 16:27 , Processed in 0.052664 second(s), 14 queries .

Powered by Discuz! X2

© 2001-2011 Comsenz Inc.

回顶部