摘要:该文章记录了我使用websocket的两次实践经历,在第一次实践过程中,踩了很多坑。第二次实践,可谓得心应手,但是很多理论性还很欠缺。通过该文章,从理论到实践,一举全部拿下。
目录
一、Websocket理论
(1)Websocket是什么?
(2)Websocket出现的背景?
(3)采用该协议的优势?
(4)握手协议
(5)Websocket和Socket的区别?
(6)关于socket.io
二、Vue实现在线聊天(实践与踩坑)
三、Angular项目首页实时推送图表数据
一、Websocket理论
(1)Websocket是什么?
Websocket是一种网络通信协议,是一种在单个TCP连接上的全双工通信协议。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
全双工通信:又称为双向同时通信,即通信的双方可以同时发送和接收信息的信息交互方式。
关于通信协议的分类,该文章图文并茂,介绍很详细:blog.csdn.net/Dingjiawang…
(2)Websocket出现的背景?
很多网站为了实现推送技术,所用的技术都是轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
而比较新的技术去做轮询的效果是Comet。这种技术虽然可以双向通信,但依然需要反复发出请求。而且在Comet中,普遍采用的长链接,也会消耗服务器资源。
在这种情况下,HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
(3)采用该协议的优势?
- 较少的控制开销。在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小。在不包含扩展的情况下,对于服务器到客户端的内容,此头部大小只有2至10字节(和数据包长度有关);对于客户端到服务器的内容,此头部还需要加上额外的4字节的掩码。相对于HTTP请求每次都要携带完整的头部,此项开销显著减少了。
 - 更强的实时性。由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于HTTP请求需要等待客户端发起请求服务端才能响应,延迟明显更少;即使是和Comet等类似的长轮询比较,其也能在短时间内更多次地传递数据。
 - 保持连接状态。与HTTP不同的是,Websocket需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息。而HTTP请求可能需要在每个请求都携带状态信息(如身份认证等)。
 - 更好的二进制支持。Websocket定义了二进制帧,相对HTTP,可以更轻松地处理二进制内容。
 - 可以支持扩展。Websocket定义了扩展,用户可以扩展协议、实现部分自定义的子协议。如部分浏览器支持压缩等。
 - 更好的压缩效果。相对于HTTP压缩,Websocket在适当的扩展支持下,可以沿用之前内容的上下文,在传递类似的数据时,可以显著地提高压缩率。
 
(4)握手协议
WebSocket同HTTP一样也是应用层的协议,但是它是一种双向通信协议,是建立在TCP之上的。
Websocket 通过HTTP/1.1 协议的101状态码进行握手。
为了创建Websocket连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为“握手”(handshaking)。
握手过程:
1. 浏览器、服务器建立TCP连接,三次握手。这是通信的基础,传输控制层,若失败后续都不执行。
2. TCP连接成功后,浏览器通过HTTP协议向服务器传送WebSocket支持的版本号等信息。(开始前的HTTP握手)
3. 服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据。
4. 当收到了连接成功的消息后,通过TCP通道进行传输通信。
(5)Websocket和Socket的区别?
Socket是TCP、IP网络的API,是为了方便使用TCP或UDP而抽象出来的一层,位于应用层和传输层之间的一组接口,而Websocket是一个典型的应用层协议。
(6)关于socket.io
Socket.IO是node.js的一个模块,它是通过WebSocket进行通信的一种简单方式。WebSocket协议很复杂,从头开始写一个支持WebSocket的应用程序将需要花费很多时间。Socket.IO提供服务器和客户端双方的组件,所以只需一个模块就可以给应用程序加入对WebSocket的支持。Socket.IO也解决了各浏览器的支持问题(不是所有浏览器都支持WebSocket)并让实时通信可以跨几乎所有常用的浏览器实现。Socket.IO的设计非常好,将实时通信带入应用程序的过程便得非常简单。如果想做任何涉及在web服务器和浏览器之间通信的事情,那么nodejs和Socket.IO是极好的选择哦!
# npm安装socket.op
$ npm install --save socket.io
二、Vue实现在线聊天(实践与踩坑)
需求:在后台管理系统中加入售后服务人员与客户的在线聊天功能。
框架:Vue
实践第一步:连接websocket及携带token
连接websocket 的方式我所接触过的包括原生方式,代码如下:
`initWebSocket () {
    // 初始化websocket
    const wsuri = 'wss://XXXXXXXXXXXXXX/ws/adminOnlineService'
    this.websock = new WebSocket(wsuri)
},`
踩坑一:对于我的项目而言,这样连接报错500,原因是没有携带token,该方式携带token的方式我所了解的如下:
(1)send发送参数,这种方式的劣势为每次发送消息,都会重新连接一次websocket;
(2)请求地址中带参数,如:var  wss = new WebSocket("wss://" + url?token + "/webSocketServer");
(3)基于协议头:this.websock = new WebSocket(wsuri, ['Bearer' + store.state.token]);
经历了一番尝试之后,我认为无论采用哪种方式,都需要跟服务端协商,即前端传什么样的类型,后端应采用相应的处理方式,不要轻易否定任何一种方式,这是一个尝试的过程。之所以这样说,是因为前端尝试了很多种方式,最终决定放在请求地址中,此时服务端做相应的处理。
连接websocket的另一种方式是使用socket.io-client
`onConnect: () => {
      console.log('connect')
      this.message = 'connect'
    },
    onDisconnect: () => {
      console.log('disconnect')
      this.message = 'disconnect'
    },
    connect: () => {
      let socket = io('wss://XXXXXXXXXXX', {
        path: '/welfare/ws/adminOnlineService',
        query: {
          'Authorization': 'Bearer  abdadfssadfasdf'
        }
      })
      socket.connect()
}`
这样在请求的时候会带上token,但是同样token会拼在url中,需要服务端进一步做处理。该方式连接成功,但是遇到的问题是在连接成功之后,会断连,之后会立马连接,如此循环,直到刷新页面为止。该问题当时没有解决。但是原生方式没有存在该问题,连接一次即可。
实践第二步:在点击登录按钮时就进行websocket的连接
点击登录按钮即连接websocket,要求定义全局方法,当时采用的是vuex。虽然时间过去很久了,并且现在看来并不需要采用这种方案,但是再来回顾下当时的实现思路吧。
在vuex的modules下新建了websocket.js文件,其中的代码如下所示:
import store from './user'
const state = {
  websock: null
}
const mutations = {
  STAFF_UPDATEWEBSOCKET (state, websock) {
    state.websock = websock
  }
  // STAFF_SEND (state, text) {
  //   state.websock.send(text)
  // }
}
// 实现websocket的连接,需要携带参数token
const actions = {
  // 用到 ES2015 的参数解构来简化代码(特别是我们需要调用 commit 很多次的时候)
  STAFF_WEBSOCKET ({ commit }) {
    let token = encodeURI('Bearer ' + store.state.token)
    const wsuri = 'wss://XXXXXXXXX/?Authorization=' + token + '&EIO=3&transport=websocket'
    commit('STAFF_UPDATEWEBSOCKET', new WebSocket(wsuri))
    // 只有定义了onopen方法,才能继续实现接收消息,即在使用的地方调用onmessage方法。
    state.websock.onopen = function () {
    }
    // 心跳包,30s左右无数据浏览器会断开连接Heartbeat
    setInterval(function () {
      state.websock.send(JSON.stringify({
        'heart': true
      }))
    }, 30000)
  }
}
// 该部分为了获取websocket的相关方法。会发现此处跟mutations 里的写法是类似的,但是,想使用return,需要将相关数据写在getters里面。
const getters = {
  STAFF_UPDATE (state) {
    return state.websock
  }
}
export default {
  state,
  mutations,
  actions,
  getters
}
相关代码注释在上述代码中已经有了体现,使用方法在下面的代码中:
1.调用websocket的send方法,即点击发送的时候,会调用send方法,将消息发送给服务端,下述代码是针对不同的定义方式,所采取的不同方法,比如,第三个方法是取得getters中的;第二个方法是取得mutations中的注释的STAFF_SEND中的方法;第一个是取得actions中定义的方法。
乍一眼看上去感觉多此一举,我们直接分发 mutation 岂不更方便?实际上并非如此,还记得 mutation 必须同步执行这个限制么?Action 就不受约束!我们可以在 action 内部执行异步操作
// Action 通过 store.dispatch 方法触发
this.$store.dispatch('STAFF_WEBSOCKET')
// this.$store.commit('STAFF_SEND').send('这是来自客户端的消息')
this.$store.getters.STAFF_UPDATE.send('sdfsfs')
Actions 支持同样的载荷方式和对象方式进行分发:
// 以载荷形式分发
store.dispatch('incrementAsync', {
  amount: 10
})
// 以对象形式分发
store.dispatch({
  type: 'incrementAsync',
  amount: 10
})
2.通过websocket实现接收来自服务端的消息,实现方式如下代码:
onmessage () {
  let that = this
  this.$store.getters.STAFF_UPDATE.onmessage = function (evt) {
  let message = JSON.parse(evt.data)
  that.messages.push({
     content: message.content,
     self: false
  })
 }
}
关键是从getters中获取onmessage方法,上面强调过了,在调用该方法之前需要实现onopen方法。
如上,就可以实现聊天了。
实践第三步:websocket的事件和方法
//实例化一个WebSocket对象,并传入要连接的决定URL
var socket = new WebSocket("url");//    url中要使用ws://来代替http:// ;使用wss来代替https://
//当成功建立连接时会触发open事件
socket.onopen = function(){
    alert("established");
}
//当发生错误时会触发error事件
socket.onerror = function(){
    alert("error!");
}
//当连接关闭时会触发close事件
socket.onclose = function(){
    alert("closed!");
}
//使用send() 方法发送数据 只能接受字符串 json对象要先序列化成json字符串
socket.send(str);
//当服务端像客户端发来消息,WebSocket对象就会触发message事件
socket.onmessage = function(event){
    console.log(event.data);//返回的数据 也为字符串形式
}
//调用close()方法 会关闭Web Sockets连接,可在任何时候调用close()方法
 socket.close();
三、Angular项目首页实时推送图表数据
在上述Websocket出现背景的地方,介绍了Websocket出现的原因。其实想来,这也是在该项目中,该需求使用该方案的原因。接触的其他项目中,页面中也会有实时刷新,会在每隔一段时间后,请求近10个接口。 在本次需求中,数据量更大,请求接口量更多,采用了该方式。
下面是实现代码,其实使用过程都是一样的,主要看业务逻辑。
initWebSocket() {
    const url = '*******';
    const ws = new WebSocket(url);
    ws.onopen = () => {
      this.clearWS();
      this.ws = ws;
      ws.onmessage = evt => {
        // 下述是业务逻辑
        const data = JSON.parse(evt.data);
        if (this.simpleMode) {
          this.reciveFromWs4Simple(data);
        } else {
          this.reciveFromWs(data);
        }
      };
      ws.onclose = () => {
        this.clearWS();
      };
      ws.onerror = () => {
        this.clearWS();
      };
    };
  }
   /**
   * 清除引用, 事件
   */
  private clearWS() {
    const ws: WebSocket = this.ws;
    if (ws) {
      ws.onclose = null;
      ws.onopen = null;
      ws.onmessage = null;
    }
    this.ws = null;
  }
  /**
   * 改成心跳监测连接是否断开的逻辑. 每隔10秒检查一次
   */
  private heartbeat() {
    this.reConnectTimer = window.setInterval(() => {
      const ws: WebSocket | null = this.ws;
      if (!this.isWSAvailable(ws)) {
        this.reConnect();
      }
    }, 10000);
  }
   /**
   * 做个重连, 用close和error做个伪重连
   */
  private reConnect() {
    console.warn('reconnect');
    this.clearWS();
    this.initWebSocket();
  }
sendByWs(data) {
    const ws = this.ws;
    if (!ws) {
      console.warn('[ws-发送数据失败]: Websocket 还没准备好或已断开');
      return;
    }
    switch (ws.readyState) {
      case WebSocket.CONNECTING:
        console.warn('[ws-发送数据失败]: Websocket 正在连接中...');
        break;
      case WebSocket.OPEN:
        ws.send(JSON.stringify(data));
        break;
      default:
        console.warn('[ws-发送数据失败]: Websocket 即将断开或已经断开');
    }
  }
  switchLive(live) {
    this.live = live;
    const name = 'live';
    this.sendByWs({ name, live });
  }
ngOnDestroy() {
    this.ws && this.ws.close(1000, '用户已经离开当前页面或页面发生刷新');
    this.reConnectTimer && clearInterval(this.reConnectTimer);
  }
      常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
 - 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
 
- 提示下载完但解压或打开不了?
 
- 找不到素材资源介绍文章里的示例图片?
 
- 模板不会安装或需要功能定制以及二次开发?
 
                    
    
发表评论
还没有评论,快来抢沙发吧!