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
WebIM技术---编写前端WebSocket组件的更多相关文章
- JavaScript 是如何工作:Shadow DOM 的内部结构 + 如何编写独立的组件!
这是专门探索 JavaScript 及其所构建的组件的系列文章的第 17 篇. 如果你错过了前面的章节,可以在这里找到它们: JavaScript 是如何工作的:引擎,运行时和调用堆栈的概述! Jav ...
- 基于AngularJS的个推前端云组件探秘
基于AngularJS的个推前端云组件探秘 AngularJS是google设计和开发的一套前端开发框架,帮助开发人员简化前端开发的负担.AngularJS将帮助标准化的开发web应用结构并且提供了针 ...
- 基于微软XAML技术的前端开发方法
使用XAML技术的平台目前包括WPF,Silverlight,Windows8等平台,未来的Windows10统一Windows App也使用XAML技术. 前端开发指通过可视化集成开发环境进行用户界 ...
- 在spring boot中使用webSocket组件(一)
最近在项目中使用到了spring的webSocket组件,在这里和大家分享下,如有错误,欢迎大家指正. 在这里我使用的IDE工具是Intellij idea,框架是spring boot.spring ...
- 从零开始实现放置游戏(十三)——实现战斗挂机(4)添加websocket组件
前两张,我们已经实现了登陆界面和游戏的主界面.不过游戏主界面的数据都是在前端写死的文本,本章我们给game模块添加websocket组件,实现前后端通信,这样,前端的数据就可以从后端动态获取到了. 一 ...
- [技术博客]iview组件样式踩坑记录
[技术博客]iview组件样式踩坑记录 iview官方文档. 在本次项目开发中,前端项目主要使用vue框架+iview组件构建,其中iview组件在使用过程中遇到了许多官方文档中没有明确说明或是很难注 ...
- 基于Vue的前端UI组件库的比对和选型
大家好,我是张飞洪,感谢您的阅读,我会不定期和你分享学习心得,希望我的文章能成为你成长路上的垫脚石,让我们一起精进. 由于录制视频的需要,要做前端UI组件库的选型.平时国内外也见了不少基于Vue的UI ...
- artDialog是一个基于javascript编写的对话框组件,它拥有精致的界面与友好的接口
artDialog是一个基于javascript编写的对话框组件,它拥有精致的界面与友好的接口 自适应内容 artDialog的特殊UI框架能够适应内容变化,甚至连外部程序动态插入的内容它仍然能自适应 ...
- jmgraph前端画图组件(html5版)
原文:jmgraph前端画图组件(html5版) jmgraph是一个基于html5的WEB前端画图组件. 前端画图对象控件化,支持鼠标和健盘事件响应,可对单个控件样式设定,支 ...
随机推荐
- Chrome插件-把网页图片上传到七牛空间一
功能:图片上右键,点击上传图片到七牛,把图片上传的指定空间并返回图片URL. Chrome插件本质就是js代码,但是里面有一些限制,比如事件绑定等和普通js的绑定是有区别的,主要是为了安全性考虑. 做 ...
- SQL2005 镜像配置
新添加了一台服务器,做原来服务器的备份机,用交叉线+双网卡配置了内网. 系统环境:Windows 2008 R2 数据库:SQL2005 SP3 设置镜像可以用证书和域两种情况,不过域设置的权限比 ...
- 总想自己动动手系列·2·本地和外网(Liunx服务器上部署的web项目)按照自定义的报文格式进行交互(完结篇)
一.准备工作 (1)创建一个web-project工程,部署本地可正常访问,部署在云服务上可访问. (2)理解如何在web.xml文件中配置过滤器,和创建一个自定义的过滤器. (3)懂得如何打war包 ...
- setTimeout模拟setInterval调用
在开发环境下,尽量不用间歇调用,原因是后一个间歇调用可能会在前一个间歇调用结束之前启动. 尽量使用超时调用来模拟间歇调用. 示例代码: <!DOCTYPE html> <html l ...
- JavaScript操作符
一元操作符 只能操作一个值的操作符叫做一元操作符. 递增和递减操作符 递增和递减操作符遵循下列规则: 在应用于一个包含有效数字字符的字符串时,先将其转换为数字值,再执行加减 1 的操作 ...
- oracle 存储过程 变量的声明和赋值的3种方式
oracle 存储过程 变量的声明和赋值的3种方式 CreationTime--2018年8月31日16点00分 Author:Marydon 1.声明变量的3种方式 按照数据类型的声明方式进行区 ...
- java反射-获取方法信息
例子代码如下: package com.reflect; import java.lang.reflect.Method; public class ClassUtill { /* * 打印类的信息, ...
- C# 实体集合和实体转换成相应的string、XDocument、XElement、XDocument
https://msdn.microsoft.com/zh-cn/library/system.xml.linq.xelement(v=vs.110).aspx XElement.Parse 方法 ( ...
- Android 应用开发实例之情景模式
2013-07-01 Android 应用开发实例 1. 情景模式 使用TabHost来实现主界面的布局. 设置一组RadioButton来切换不同的情景模式. 对比普通情景模式,定时情景模式需要加上 ...
- Android软件开发之EditText 详解
EditText在API中的结构 java.lang.Objectandroid.view.Viewandroid.widget.TextView android.widget.Edit ...