网站首页swoole

websocket心跳包实现

发布时间:2018-09-17 03:36:23编辑:slayer.hover阅读(5226)

    应用场景:在APP中用户登陆后,需要向服务器端发报告说明当前在线。在有业务发生时,服务器会向所有在线的特定用户推送消息。

    APP里打开websocket连接后,在网络异常情况下,可能会异常断开。所以需要用心跳包来保持长时间的连接。


    服务器端使用swoole的swoole_websocket_server实现,用户上线后,将其数据存放于redis

    class WS_Server
    {
        private $serv;
        private static $cache;
        private static $cache_set = [
            'host'=>'127.0.0.1', 
            'port'=>6379, 
            'db'=>5
        ];
        public function __construct() {
            self::$cache = new \Redis();
            self::$cache->connect(self::$cache_set['host'], self::$cache_set['port']);
            self::$cache->select(self::$cache_set['db']);
            $this->serv = new \swoole_websocket_server("0.0.0.0", 9502);
            $this->serv->on('open',[$this,'onOpen']);
            $this->serv->on('message',[$this,'onMessage']);
            $this->serv->on('close',    [$this,'onClose']);
            $this->serv->start();
        }
        
        public function onMessage($serv, $frame){
            if($frame->data=='ping'){
                #处理心跳包
                $serv->push($frame->fd, 'pong'); 
            }else {
                $data = json_decode($frame->data, TRUE);
                if ($data) {
                    switch ($data['action']) {
                        #新用户上线,接收open数据包
                        case 'open':
                            $mem = [
                                'id'   => $data['id'],
                                'type' => $data['type'],
                                'name' => $data['name'],
                                'phone'=> $data['phone'],
                            ];
                            self::$cache->hset('members', $frame->fd, json_encode($mem));
                            #清除意外断掉的用户
                            $allmem = self::$cache->hGetAll('members');
                            if (!empty($allmem)) {
                                foreach ($allmem as $k => $v) {
                                    $row = json_decode($v, true);
                                    if ($k != $frame->fd && $row['id'] == $data['id']) {
                                        self::$cache->hdel('members', $k);
                                    }
                                }
                            }
                            break;                        
                        #其它具体业务处理    
                        case 'otherEvent':
                            ...
                            break;
                    }
                }
            }
          }
          
          public function onClose($serv, $fd){
            self::$cache->hDel('members', $fd);
          }


    websocket客户端实现:

        
        var heartCheck = {
            timeout: 60000, //心跳间隔
            timeoutObj: null,
            serverTimeoutObj: null,
            reset: function(){
                clearTimeout(heartCheck.timeoutObj);
                clearTimeout(heartCheck.serverTimeoutObj);
                return this;
            },
            start: function(){
                heartCheck.timeoutObj && clearTimeout(heartCheck.timeoutObj);
                heartCheck.serverTimeoutObj && clearTimeout(heartCheck.serverTimeoutObj);
                heartCheck.timeoutObj = setTimeout(function(){
                    if(vm.socket && vm.socket.readyState===1) {
                        vm.socket.send("ping");
                        heartCheck.serverTimeoutObj = setTimeout(function () {
                            vm.reconnect();
                        }, heartCheck.timeout);
                    }
                }, heartCheck.timeout)
            }
        };
        
        var vm = new Vue({
            el: '#app',
            data: {            
                userInfo: null,            
                webSocket_url: 'ws://127.0.0.1:9502',
                socket: null,
                lockReconnect: false, //重连标识
                jump: null, //心跳
            },
            methods: {
                wsconnect: function () {
                    try{
                        vm.socket=new WebSocket(vm.webSocket_url);
                    }catch(e){
                        console.log('websocket error');
                        return false;
                    }
                    vm.socket.onmessage= function(msg){
                        if(msg.data!='pong') {
                            //根据msg.data处理具体业务
                            ...
                        }
                        heartCheck.reset().start();
                    }
                    vm.socket.onclose=function(){
                        vm.reconnect();
                    }
                    vm.socket.onerror=function(){
                        vm.reconnect();
                    }
                    vm.socket.onopen=function(){
                        heartCheck.reset().start();
                    }
                },
                reconnect:function(){
                        if(vm.lockReconnect) {
                            return false;
                        };
                        vm.lockReconnect = true;
                        vm.jump && clearTimeout(vm.jump);
                        vm.jump = setTimeout(function () {
                            vm.wsconnect();
                            vm.lockReconnect = false;
                        }, 4000);
                },
                wsopen:function(){
                    !vm.socket && vm.wsconnect();
                    if(vm.socket.readyState===1) {
                        vm.userInfo = localStorage.getItem('userInfo');
                        //推送用户上线消息
                        vm.socket.send(JSON.stringify({
                            action: 'open',
                            id: vm.userInfo.id,
                            type: vm.userInfo.type,                            
                            name: vm.userInfo.name,
                            phone:vm.userInfo.phone,
                        }));
                            
                    }else{
                        setTimeout(function(){
                            vm.wsopen();
                        }, 1000);
                    }
                }
            }
        });
        
        //此页面最好在整个项目中只会执行一次,并且不会关掉
        window.onLoad= function(){
            //判断用户登陆后,打开websocket连接
            if(localStorage.getItem('userInfo')){
                vm.wsopen();
            }
        }


    经测试,只要App进程没被杀死,基本可保持用户长期在线。


评论