zoukankan      html  css  js  c++  java
  • php 实现推技术comet(转)

    实现实时通信一般有两种方式:
    socket或comet。socket是比较好的解决方案,问题在于不是所有的浏览器都兼容,服务器端实现起来也稍微有点麻烦。相比之下,comet(基于HTTP长连接的"服务器推")实现起来更加方便,而且兼容所有的浏览器。所以这次就来说说comet的php实现。

    comet也有好几种实现方式,如iframe, http long request,本文主要探讨http long request实现实时通信。

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

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

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

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

    <?php
    // NovComet.php
    class NovComet {
        const COMET_OK = 0;
        const COMET_CHANGED = 1;
    
        private $_tries;
        private $_var;
        private $_sleep;
        private $_ids = array();
        private $_callback = null;
    
        public function  __construct($tries = 20, $sleep = 2)
        {
            $this->_tries = $tries;
            $this->_sleep = $sleep;
        }
    
        public function setVar($key, $value)
        {
            $this->_vars[$key] = $value;
        }
    
        public function setTries($tries)
        {
            $this->_tries = $tries;
        }
    
        public function setSleepTime($sleep)
        {
            $this->_sleep = $sleep;
        }
    
        public function setCallbackCheck($callback)
        {
            $this->_callback = $callback;
        }
    
        const DEFAULT_COMET_PATH = "/dev/shm/%s.comet";
    
        public function run() {
            if (is_null($this->_callback)) {
                $defaultCometPAth = self::DEFAULT_COMET_PATH;
                $callback = function($id) use ($defaultCometPAth) {
                    $cometFile = sprintf($defaultCometPAth, $id);
                    return (is_file($cometFile)) ? filemtime($cometFile) : 0;
                };
            } else {
                $callback = $this->_callback;
            }
    
            for ($i = 0; $i < $this->_tries; $i++) {
                foreach ($this->_vars as $id => $timestamp) {
                    if ((integer) $timestamp == 0) {
                        $timestamp = time();
                    }
                    $fileTimestamp = $callback($id);
                    if ($fileTimestamp > $timestamp) {
                        $out[$id] = $fileTimestamp;
                    }
                    clearstatcache();
                }
                if (count($out) > 0) {
                    return json_encode(array('s' => self::COMET_CHANGED, 'k' => $out));
                }
                sleep($this->_sleep);
            }
            return json_encode(array('s' => self::COMET_OK));
        }
    
        public function publish($id)
        {
            return json_encode(touch(sprintf(self::DEFAULT_COMET_PATH, $id)));
        }
    }
    <?php
    // comet.php
    include('NovComet.php');
    
    $comet = new NovComet();
    $publish = filter_input(INPUT_GET, 'publish', FILTER_SANITIZE_STRING);
    if ($publish != '') {
        echo $comet->publish($publish);
    } else {
        foreach (filter_var_array($_GET['subscribed'], FILTER_SANITIZE_NUMBER_INT) as $key => $value) {
            $comet->setVar($key, $value);
        }
        echo $comet->run();
    }
     
        function send(msg){
            $.ajax({
                data : {'msg' : msg},
                type : 'post',
                url : '{:U('Live/SendMsg')}',
                success : function(response){
                   //alert(response);;
                }
            })
        }
        $(document).ready(function(){
            connect();
            $("#btn").click(function(){
                var msg = $('#msg').val();
                send(msg);
                msg.html('');
              });
        })
        public function SendMsg(){
            
            $filename  = './Uploads/live/'.'data.json';
            if ($_POST['msg']!='') {
                file_put_contents($filename,$_POST['msg']);
                $this->ajaxReturn($_POST,'OK',100);
                die();
            }else{
                $this->ajaxReturn($_POST,'on',0);
                die();
            }
            
        }
    
    
    
     
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <title>Comet demo</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <script type="text/javascript" src="./jquery-1.8.2.min.js"></script>
    <script type="text/javascript" src="./json2.js"></script>
    <script>
        var timestamp = 0;
        var url = 'backend.php';
        var error = false;
        // 通过ajax建立和php端处理函数的连接(通过递归调用建立长时间的连接)
        function connect(){
            $.ajax({
                data : {'timestamp' : timestamp},
                url : url,
                type : 'get',
                timeout : 0,
                success : function(response){
                    var data = JSON.parse(response);
                    error = false;
                    timestamp = data.timestamp;
                    if (data.msg != undefined && data.msg != "")
                    {
                        $("#content").append("<div>" + data.msg + "</div>");
                    }
                },
                error : function(){
                    error = true;
                    setTimeout(function(){ connect();}, 5000);
                },
                complete : function(){
                    if (error)
                        // 请求有错误时,延迟5s再连接
                        setTimeout(function(){connect();}, 5000);
                    else
                        connect();
                }
            })
        }
        // 发送信息
        function send(msg){
            $.ajax({
                data : {'msg' : msg},
                type : 'get',
                url : url
            })
        }
        // 创建长时间的连接
        $(document).ready(function(){
            connect();
        })
    </script>
    </head>
    <body>
        <div id="content"></div>
            <form action="" method="get" 
    onsubmit="send($('#word').val());$('#word').val('');return false;">
                <input type="text" name="word" id="word" value="" />
                <input type="submit" name="submit" value="Send" />
            </form>
        </body>
    </html>
    <?php
    // 设置请求运行时间不限制,解决因为超过服务器运行时间而结束请求
    ini_set("max_execution_time", "0");
    
    $filename  = dirname(__FILE__).'/data.txt';
    $msg = isset($_GET['msg']) ? $_GET['msg'] : '';
    
    // 判断页面提交过来的修改内容是否为空,不为空则将内容写入文件,并中断流程
    if ($msg != '')
    {
        file_put_contents($filename,$msg);
        exit;
    }
    
    /* 获取文件上次修改时间戳 和 当前获取到的最近一次文件修改时间戳
     * 文件上次修改时间戳 初始 默认值为0
     * 最近一次文件修改时间戳 通过 函数 filemtime()获取
     */
    $lastmodif    = isset($_GET['timestamp']) ? $_GET['timestamp'] : 0;
    clearstatcache();  // 清除文件状态缓存
    $currentmodif = filemtime($filename);
    
    /* 如果当前返回的文件修改unix时间戳小于或等于上次的修改时间,
     * 表明文件没有更新不需要推送消息
     * 如果当前返回的文件修改unix时间戳大于上次的修改时间
     * 表明文件有更新需要输出修改的内容作为推送消息
     */
    while ($currentmodif <= $lastmodif)
    {
        usleep(10000);     // 休眠10ms释放cpu的占用
        clearstatcache();  // 清除文件状态缓存
        $currentmodif = filemtime($filename);
    }
    
    // 推送信息处理(需要推送说明文件有更改,推送信息包含本次修改时间、内容)
    $response = array();
    $response['msg'] = file_get_contents($filename);
    $response['timestamp'] = $currentmodif;
    echo json_encode($response);
    flush();
    ?>

    最后,话说,php真不适合干这个,我觉得用nodejs 写是最轻松的,erlang好像也不错

  • 相关阅读:
    servlet中doGet()和doPost()的用法
    mybatis动态SQL中的sql片段
    动态SQL语句
    Mybaits的中的对象映射(包含仅有基本数据类型的属性的和对象类型的属性的)
    <img src = "..."/>的一个图片上面怎么在放上字
    启动tomcat时出现The specified JRE installation does not exist 如何解决?
    Navicat图形更改表结构的时,设置外键时出现1452错误
    数据库主键和外键
    数据库设计的过程中的设置外键的作用
    Hibernate 事务管理
  • 原文地址:https://www.cnblogs.com/jackluo/p/3728933.html
Copyright © 2011-2022 走看看