zoukankan      html  css  js  c++  java
  • WebIM技术---编写前端WebSocket组件

    过去我们想要实现一个实时Web应用通常会考虑采用ajax轮循或者是long polling技术,但是因为频繁的建立http连接会带来多余的请求以及消息精准性的问题,让我们在实现实时Web应用时头疼不已。现在,Html5提出了WebSocket协议来规范解决了这个问题。

    ajax轮询,long polling技术实现原理

    ajax轮询

    ajax轮询非常简单了,就是在客户端设置一个定时器,频繁的去请求接口,看有没有数据返回,但是这样很明显会有很多的多余请求,导致服务器压力巨大。。

    long polling

    long polling技术,其实是在ajax轮询的基础上再做了一些优化,客户端请求服务端看有没有返回,如果暂时没有,服务端会把请求短暂的挂起,当有数据的时候再把数据返回给客户端,这时候客户端再另起一个请求,询问服务器。
    为了保证连接的通道不断,我们通常会设置一个timeout,当过了这个timeout时还没有数据,服务端会把挂起的服务关闭,返回空的数据,然后客户端再进行请求。
    这样做虽然比ajax轮询好很多,但是当消息量大的时候,请求数还是很多。而且轮询还极有可能在传输的过程中遇到消息丢失的情况,这时候需要服务端做消息缓存等处理。

    WebSocket协议

    WebSocket协议本质上是一个基于TCP的协议,它由通信协议和编程API组成,WebSocket能够在浏览器和服务器之间建立双向连接,以基于事件的方式,赋予浏览器实时通信能力。既然是双向通信,就意味着服务器端和客户端可以同时发送并响应请求,而不再像HTTP的请求和响应。
    简单来说,WebSocket就是一个长连接通道,在这个通道里客户端和服务端可以自由的发送消息给对方,而且不用管对方是否有返回,双方都有关闭这个通道的权利。

    WebSocket通信场景

    客户端:啦啦啦,我要建立Websocket协议,Websocket协议版本:17(HTTP Request)
    服务端:ok,确认,已建立Websocket(HTTP Protocols Switched)
    客户端:有消息的时候记得推给我哦。
    服务端:ok,有的时候会告诉你的。
    服务端:balabalabalabala
    服务端:balabalabalabala
    服务端:哈哈哈哈哈啊哈哈哈哈
    客户端:哈哈哈哈哈哈哈,你可以不用返回
    ...

    使用WebSocket有什么好处

    • WebSocket 能节约带宽、CPU 资源并减少延迟。
    • WebSocket 基于事件交流,通信简单。
    • WebSocket 可以跨域。

    WebSocket API介绍

    建立WebSocket连接

    var ws = new WebSocket('ws://www.websocket.org')
    

    为WebSocket 对象添加 4 个不同的事件:

    • open
    • message
    • error
    • close

    代码示例:

    // 当websocket连接建立成功时
    ws.onopen = function() {
        console.log('websocket 打开成功');    
    };
    
    // 当收到服务端的消息时
    ws.onmessage = function(e) {
        // e.data 是服务端发来的数据
        console.log(e.data);
    };
    
    // 当websocket关闭时
    ws.onclose = function() {
        console.log("websocket 连接关闭");
    };
    
    // 当出现错误时
    ws.onerror = function() {
        console.log("出现错误");
    };
    

    WebSocket对象方法

    • send
    • close

    代码示例:

    // 发送消息 
    ws.send('blablabla')
    
    // 关闭socket
    ws.close()
    

    定义一个前端Socket组件

    为什么需要定义一个Socket组件

    HTML5提供的SocketAPI太过于简陋,并不能满足复杂环境的socket交互需要,API的调用也不太方便。

    定义一个什么样的Socket组件

    • 简单好用的客户端和服务端的双向通信API
    • 支持断线重连功能
    • 支持自定义事件
    • 能够自由感知socket状态信息

    Socket组件示例

    首先要和服务端约定互相可以识别的通信协议,假设我们约定的通信协议是

    // 客户端发送给服务端
    {
        method: 'xxx',
        request: {} 
    }
    
    // 服务端返回给客户端
    {
        data: {},
        success: true,
        errorCode: 0,
        request: {} // 如果是服务端主动推消息给客户端,request会带有method参数
                    // 如果是服务端返回客户端请求,request就是客户端之前请求的数据
    }
    

    然后我们上代码:

    /*
     * @example
     *  var ws = new Socket('ws://www.websocket.org')
     *  ws.on('ready',function() {
     *      console.log('服务器连接成功');
     *      ws.on('message', function(json) {
     *          console.log('一条新消息:'+json.session);
     *      });
     *      ws.emit("send", {
     *          session: "一条新消息"
     *      })
     *  })
     *  ws.on("error",function(){
     *      console.log("连接报错")
     *  })
     *  ws.on("close",function(){
     *      console.log("连接关闭");
     *  })
     */
    
    function Socket(url) {
        this.init(url)
    }
    
    Socket.prototype = {
        init: function(url) {
            this.initListeners()
            this.initSocket(url)
            this.bindSocketEvent()
        },
    
        initSocket: function(url) {
            this.url = url
            this.socket = new WebSocket(url)
            return this
        },
    
        initListeners: function() {
            this.listeners = {}
            return this
        },
    
        bindSocketEvent: function() {
            var me = this
    
            me.socket.onopen = function() {
                me.stopHeartBeat()
                me.startHeartBeat()
                me.clearAll()
                me.trigger('ready')
            }
    
            me.socket.onerror = function(e) {
                me.trigger('close', e)
                me.close()
            }
    
            me.socket.onmessage = function(e) {
                me.refreshServerTimer();
                var json = JSON.parse(e.data);
                me.trigger(json.request.method, json);
            }
    
            return this
        },
    
        reConnect: function() {
            this.initSocket(this.url).bindSocketEvent()
            this.trigger('reconnect')
        },
    
        isOffline: function() {
            return this.socket.readyState != WebSocket.OPEN
        },
    
        on: function(evt, fn) {
            var me = this
    
            if(me.listeners[evt] && me.listeners[evt].length) {
                if(me.listeners[evt].indexOf(fn) == -1){
                    me.listeners[evt].push(fn)
                }
            }else {
                me.listeners[evt] = [fn]
            }
    
            return this
        },
    
        off: function(evt, fn) {
            var me = this
    
            if(me.listeners[evt] && me.listeners[evt].length){
                var index = me.listeners[evt].indexOf(fn)
    
                if(index != -1){
                    me.listeners[evt].splice(index,1)
                }
            }
    
            return this
        },
    
        emit: function(method, info) {
            var me = this
    
            me.socket.send(JSON.stringify({
                method: method,
                request: info || ''
            }))
    
            return this
        },
    
        trigger: function(evt) {
            var me = this
    
            if(me.listeners[evt]) {
                for(var i=0; i<me.listeners[evt].length; i++) {
                    me.listeners[evt][i].apply(me, [].slice.call(arguments,1))
                }
            }
    
            return this
        },
    
        startHeartBeat: function() {
            var me = this
    
            me.heartBeatTimer = setInterval(function() {
                me.emit("heartBeat")
            }, 5000)
        },
    
        stopHeartBeat: function() {
            clearInterval(this.heartBeatTimer)
        },
    
        //重新开始断线计时,20秒内没有收到任何正常消息或心跳就超时掉线
        refreshServerTimer: function() {
            var me = this
    
            clearTimeout(me.serverHeartBeatTimer)
            me.serverHeartBeatTimer = setTimeout(function() {
                me.trigger("disconnect")
                me.close()
                me.reConnect()
            }, 20000)
        },
    
        clearAll: function() {
            var tmp = this.listeners['ready']
    
            this.listeners = {}
            this.listeners['ready'] = tmp
    
            return this
        },
    
        close: function(options) {
            var me = this;
    
            clearTimeout(me.serverHeartBeatTimer);
            me.stopHeartBeat();
            me.socket.close();
    
            return this
        }
    }
    

    介绍一下组件里的心跳包机制:
    因为一些原因,有这么一种情况,socket还在客户端连着,但是服务端和客户端之间却没有办法互相发送消息,我们称这种现象叫做WebSocket失活。

    组件里采用的解决办法是,客户端每5秒钟向服务端发送心跳包,讲道理服务端会返回一个心跳包以保活。但是如果客户端检查1分钟内没有收到服务端的返回,客户端会自动重连WebSocket。

    这里有个坑,请躲好。。

    WebSocket在建立连接之前,会先发一个http协议询问服务端要不要建立WebSocket,因为http请求是会带上cookie的,这时候如果域名下的cookie太多,有可能会导致WebSocket建立连接失败。。

    我这里的解决方案是,更换接口的域名地址,利用WebSocket可以跨域的特性绕过当前域的cookie建立连接。

    原文:

    https://github.com/matthew-sun/blog/issues/21?utm_source=tuicool&utm_medium=referral

  • 相关阅读:
    WPF后台生成datatemplate(TreeViewItem例子)
    后台根据数据模版内的子控件获取使用该模版的控件
    逻辑代码实现拼音首字母检索
    自定义LISTBOX内子项为checkbox或者radio时,关于IsChecked绑定
    siliverlight某些事件无法响应
    页面内容不能铺满浏览器窗口的解决方法
    linux sort命令学习
    linux find命令学习
    linux tr命令学习
    linux cat命令学习
  • 原文地址:https://www.cnblogs.com/chunguang/p/5706376.html
Copyright © 2011-2022 走看看