• php实现实时通信


    hp实现实时通信

    如果英文ok的话,可以直接看这篇文章

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

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

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

    <?php
    
    $count = 10;
    
    for($i=0; $i<$count; $i++)
    {
    	// do something ...
    	sleep(2);
    }
    
    echo '憋死我了';
    

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

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

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

    实战

    客户端

    <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
            <title>Comet Test</title>
        </head>
        <body>
            <p><a class='customAlert' href="#">publish customAlert</a></p>
            <p><a class='customAlert2' href="#">publish customAlert2</a></p>
            <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.5/jquery.min.js" type="text/javascript"></script>
            <script src="NovComet.js" type="text/javascript"></script>
            <script type="text/javascript">
    			NovComet.subscribe('customAlert', function(data){
    				console.log('customAlert');
    				//console.log(data);
    			}).subscribe('customAlert2', function(data){
    				console.log('customAlert2');
    				//console.log(data);
    			});
    
    			$(document).ready(function() {
    				$("a.customAlert").click(function(event) {
    					NovComet.publish('customAlert');
    				});
    
    				$("a.customAlert2").click(function(event) {
    					NovComet.publish('customAlert2');
    				});
    				NovComet.run();
    			});
            </script>
        </body>
    </html>
    

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

    NovComet

    //NovComet.js
    NovComet = {
        sleepTime: 1000,
        _subscribed: {},
        _timeout: undefined,
        _baseurl: "comet.php",
        _args: '',
        _urlParam: 'subscribed',
    
        subscribe: function(id, callback) {
            NovComet._subscribed[id] = {
                cbk: callback,
                timestamp: NovComet._getCurrentTimestamp()
            };
            return NovComet;
        },
    
        _refresh: function() {
            NovComet._timeout = setTimeout(function() {
                NovComet.run()
            }, NovComet.sleepTime);
        },
    
        init: function(baseurl) {
            if (baseurl!=undefined) {
                NovComet._baseurl = baseurl;
            }
        },
    
        _getCurrentTimestamp: function() {
            return Math.round(new Date().getTime() / 1000);
        },
    
        run: function() {
            var cometCheckUrl = NovComet._baseurl + '?' + NovComet._args;
            for (var id in NovComet._subscribed) {
                var currentTimestamp = NovComet._subscribed[id]['timestamp'];
    
                cometCheckUrl += '&' + NovComet._urlParam+ '[' + id + ']=' +
                   currentTimestamp;
            }
            cometCheckUrl += '&' + NovComet._getCurrentTimestamp();
            $.getJSON(cometCheckUrl, function(data){
                switch(data.s) {
                    case 0: // sin cambios
                        NovComet._refresh();
                        break;
                    case 1: // trigger
                        for (var id in data['k']) {
                            NovComet._subscribed[id]['timestamp'] = data['k'][id];
                            NovComet._subscribed[id].cbk(data.k);
                        }
                        NovComet._refresh();
                        break;
                }
            });
    
        },
    
        publish: function(id) {
            var cometPublishUrl = NovComet._baseurl + '?' + NovComet._args;
            cometPublishUrl += '&publish=' + id;
            $.getJSON(cometPublishUrl);
        }
    };
    

    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。

    服务端

    <?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();
    }
    

    如果收到publish参数,直接输出,否则执行run方法,至于run是怎么回事,且看下码。

    <?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)));
        }
    }
    

    可以看到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长链接查询


    http://blog.leezhong.com/tech/2011/03/21/php-comet.html
  • 相关阅读:
    堆和栈的区别
    熟悉熟悉常用的几个算法用JS的实现
    JS设置CSS样式的几种方式
    javascript的基本语法、数据结构
    DOM的概念及子节点类型
    三列自适应布局
    Javascript中括号“[]”的多义性
    Javascript中大括号“{}”的多义性
    Javascript小括号“()”的多义性
    JavaScript 开发者经常忽略或误用的七个基础知识点
  • 原文地址:https://www.cnblogs.com/datang/p/2493536.html
Copyright © 2020-2023  润新知