WEB 实时推送技术的总结
前言
随着 Web 的发展,用户对于 Web 的实时推送要求也越来越高 ,比如,工业运行监控、Web 在线通讯、即时报价系统、在线游戏等,都需要将后台发生的变化主动地、实时地传送到浏览器端,而不需要用户手动地刷新页面。本文对过去和现在流行的 Web 实时推送技术进行了比较与总结。
本文完整的源代码请猛戳Github博客,纸上得来终觉浅,建议大家动手敲敲代码。
一、双向通信
HTTP 协议有一个缺陷:通信只能由客户端发起。举例来说,我们想了解今天的天气,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息。这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。在WebSocket协议之前,有三种实现双向通信的方式:轮询(polling)、长轮询(long-polling)和iframe流(streaming)。
1.轮询(polling)
轮询是客户端和服务器之间会一直进行连接,每隔一段时间就询问一次。其缺点也很明显:连接数会很多,一个接受,一个发送。而且每次发送请求都会有Http的Header,会很耗流量,也会消耗CPU的利用率。
- 优点:实现简单,无需做过多的更改
- 缺点:轮询的间隔过长,会导致用户不能及时接收到更新的数据;轮询的间隔过短,会导致查询请求过多,增加服务器端的负担
// 1.html <div id="clock"></div> <script> let clockDiv = document.getElementById('clock'); setInterval(function(){ let xhr = new XMLHttpRequest; xhr.open('GET','/clock',true); xhr.onreadystatechange = function(){ if(xhr.readyState == 4 && xhr.status == 200){ console.log(xhr.responseText); clockDiv.innerHTML = xhr.responseText; } } xhr.send(); },1000); </script>
//轮询 服务端 let express = require('express'); let app = express(); app.use(express.static(__dirname)); app.get('/clock',function(req,res){ res.end(new Date().toLocaleString()); }); app.listen(8080);
启动本地服务,打开http://localhost:8080/1.html
,得到如下结果:
2.长轮询(long-polling)
长轮询是对轮询的改进版,客户端发送HTTP给服务器之后,看有没有新消息,如果没有新消息,就一直等待。当有新消息的时候,才会返回给客户端。在某种程度上减小了网络带宽和CPU利用率等问题。由于http数据包的头部数据量往往很大(通常有400多个字节),但是真正被服务器需要的数据却很少(有时只有10个字节左右),这样的数据包在网络上周期性的传输,难免对网络带宽是一种浪费。
- 优点:比 Polling 做了优化,有较好的时效性
- 缺点:保持连接会消耗资源; 服务器没有返回有效数据,程序超时。
// 2.html 服务端代码同上 <div id="clock"></div> <script> let clockDiv = document.getElementById('clock') function send() { let xhr = new XMLHttpRequest() xhr.open('GET', '/clock', true) xhr.timeout = 2000 // 超时时间,单位是毫秒 xhr.onreadystatechange = function() { if (xhr.readyState == 4) { if (xhr.status == 200) { //如果返回成功了,则显示结果 clockDiv.innerHTML = xhr.responseText } send() //不管成功还是失败都会发下一次请求 } } xhr.ontimeout = function() { send() } xhr.send() } send() </script>
3.iframe流(streaming)
iframe流方式是在页面中插入一个隐藏的iframe,利用其src属性在服务器和客户端之间创建一条长连接,服务器向iframe传输数据(通常是HTML,内有负责插入信息的javascript),来实时更新页面。
- 优点:消息能够实时到达;浏览器兼容好
- 缺点:服务器维护一个长连接会增加开销;IE、chrome、Firefox会显示加载没有完成,图标会不停旋转。
// 3.html <body> <div id="clock"></div> <iframe src="/clock" style="display:none"></iframe> </body>
//iframe流 let express = require('express') let app = express() app.use(express.static(__dirname)) app.get('/clock', function(req, res) { setInterval(function() { let date = new Date().toLocaleString() res.write(` <script type="text/javascript"> parent.document.getElementById('clock').innerHTML = "${date}";//改变父窗口dom元素 </script> `) }, 1000) }) app.listen(8080)
启动本地服务,打开http://localhost:8080/3.html
,得到如下结果:
上述代码中,客户端只请求一次,然而服务端却是源源不断向客户端发送数据,这样服务器维护一个长连接会增加开销。
以上我们介绍了三种实时推送技术,然而各自的缺点很明显,使用起来并不理想,接下来我们着重介绍另一种技术--websocket,它是比较理想的双向通信技术。
二、WebSocket
1.什么是websocket
WebSocket是一种全新的协议,随着HTML5草案的不断完善,越来越多的现代浏览器开始全面支持WebSocket技术了,它将TCP的Socket(套接字)应用在了webpage上,从而使通信双方建立起一个保持在活动状态连接通道。
一旦Web服务器与客户端之间建立起WebSocket协议的通信连接,之后所有的通信都依靠这个专用协议进行。通信过程中可互相发送JSON、XML、HTML或图片等任意格式的数据。由于是建立在HTTP基础上的协议,因此连接的发起方仍是客户端,而一旦确立WebSocket通信连接,不论服务器还是客户端,任意一方都可直接向对方发送报文。
初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?
2.HTTP的局限性
- HTTP是半双工协议,也就是说,在同一时刻数据只能单向流动,客户端向服务器发送请求(单向的),然后服务器响应请求(单向的)。
- 服务器不能主动推送数据给浏览器。这就会导致一些高级功能难以实现,诸如聊天室场景就没法实现。
3.WebSocket的特点
- 支持双向通信,实时性更强
- 可以发送文本,也可以发送二进制数据
- 减少通信量:只要建立起WebSocket连接,就希望一直保持连接状态。和HTTP相比,不但每次连接时的总开销减少,而且由于WebSocket的首部信息很小,通信量也相应减少了
相对于传统的HTTP每次请求-应答都需要客户端与服务端建立连接的模式,WebSocket是类似Socket的TCP长连接的通讯模式,一旦WebSocket连接建立后,后续数据都以帧序列的形式传输。在客户端断开WebSocket连接或Server端断掉连接前,不需要客户端和服务端重新发起连接请求。在海量并发和客户端与服务器交互负载流量大的情况下,极大的节省了网络带宽资源的消耗,有明显的性能优势,且客户端发送和接受消息是在同一个持久连接上发起,实时性优势明显。
接下来我看下websocket如何实现客户端与服务端双向通信:
// websocket.html <div id="clock"></div> <script> let clockDiv = document.getElementById('clock') let socket = new WebSocket('ws://localhost:9999') //当连接成功之后就会执行回调函数 socket.onopen = function() { console.log('客户端连接成功') //再向服务 器发送一个消息 socket.send('hello') //客户端发的消息内容 为hello } //绑定事件是用加属性的方式 socket.onmessage = function(event) { clockDiv.innerHTML = event.data console.log('收到服务器端的响应', event.data) } </script>
// websocket.js let express = require('express') let app = express() app.use(express.static(__dirname)) //http服务器 app.listen(3000) let WebSocketServer = require('ws').Server //用ws模块启动一个websocket服务器,监听了9999端口 let wsServer = new WebSocketServer({ port: 9999 }) //监听客户端的连接请求 当客户端连接服务器的时候,就会触发connection事件 //socket代表一个客户端,不是所有客户端共享的,而是每个客户端都有一个socket wsServer.on('connection', function(socket) { //每一个socket都有一个唯一的ID属性 console.log(socket) console.log('客户端连接成功') //监听对方发过来的消息 socket.on('message', function(message) { console.log('接收到客户端的消息', message) socket.send('服务器回应:' + message) }) })
启动本地服务,打开http://localhost:3000/websocket.html
,得到如下结果:
三、Web 实时推送技术的比较
综上所述:Websocket协议不仅解决了HTTP协议中服务端的被动性,即通信只能由客户端发起,也解决了数据同步有延迟的问题,同时还带来了明显的性能优势,所以websocket
是Web 实时推送技术的比较理想的方案,但如果要兼容低版本浏览器,可以考虑用轮询来实现。
- 原文:WEB 实时推送技术的总结
- 作者:浪里行舟

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
java提高(15)---java深浅拷贝
java提高(15)---java深浅拷贝 一、前言 为什么会有深浅拷贝这个概念? 我觉得主要跟JVM内存分配有关,对于基本数据类型,只存在栈内存,所以它的拷贝不存在深浅拷贝这个概念。而对于对象而言,一个对象的创建会在内存中分配两块空间,一个在栈内存存对象的引用指针,一个在堆内存存放对象。这个时候会有一个问题,你拷贝的只是这个引用指针还是拷贝两块内存一起拷贝,这个时候就会有深浅拷贝一说。 还有之前我认为Arrays.copyOf()是深度拷贝,亲测后发现原来它也是浅拷贝。下面进行具体说明。 二、数据类型 数据分为基本数据类型(int, boolean, double, byte, char等)和对象数据类型。 基本数据类型的特点:直接存储在栈(stack)中的数据. 引用数据类型的特点:在栈内存存储对象引用,真实的数据存放在堆内存里 引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。 三、什么是浅拷贝和深拷贝 首先需要明白,深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。那先来看看...
- 下一篇
Python知识点:lambda, map, filter
通过示例介绍Python中的lambda,map,filter 函数的使用方法。 lambdalambda 操作符(或 lambda函数)通常用来创建小巧的,一次性的匿名函数对象。它的基本语法如下: lambda arguments : expression lambda操作符可以有任意数量的参数,但是它只能有一个表达式,且不能包含任何语句,返回一个可以赋值给任何变量的函数对象。 下面通过一个例子来理解一下。首先看看一个Python函数: def add(x, y): return x+y # call the function add(1, 2) # Output: 3 上述函数名为add, 它需要两个参数x和y,并返回它们的和。接下来,我们把上面的函数变成一个lambda函数: add = lambda x, y : x + y print(add(1,2)) # Output: 3 在lambda x, y : x + y中,x和y是函数的参数,x+y是表达式,它被执行并返回结果。 lambda x, y : x + y返回的是一个函数对象,它可以被赋值给任何变量。在本例中函数对...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS6,CentOS7官方镜像安装Oracle11G
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2全家桶,快速入门学习开发网站教程
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS8编译安装MySQL8.0.19