在浏览某些网页的时候,例如 WebQQ、京东在线客服服务、CSDN私信消息等类似的情况下,我们可以在网页上进行在线聊天,或者即时消息的收取与回复,可见,这种功能的需求由来已久,并且应用广泛。

网上关于这方面的文章也能搜到一大堆,不过基本上都是理论,真正能够运行的代码很少,原理性的东西我就不当搬运工了,本文主要是贴示例代码,最多在代码中穿插一点便于理解,本文主要的示例代码基于 javascript,服务端基于 nodejs 的 koa(1/2)框架实现。


模拟推送

Web端 常见的消息推送实际上大多数都是模拟推送,之所以是模拟推送,是因为这种实现并不是服务器主动推送,本质依旧是客户端发起请求,服务端返回数据,起主动作用的是客户端。


短轮询

实现上最简单的一种模拟推送方法,原理就是客户端不断地向服务端发请求,如果服务端数据有更新,服务端就把数据发送回来,客户端就能接收到新数据了。

一种实现的示例如下:

const loadXMLDoc = (url, callback) => {let xmlhttpif(window.XMLHttpRequest) {//  IE7+ Firefox Chrome Safari 等现代浏览器执行的代码xmlhttp = new XMLHttpRequest()} else {// IE5 IE6浏览器等老旧浏览器执行的代码xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')}xmlhttp.onreadystatechange = () => {if(xmlhttp.readyState === 4 && xmlhttp.status === 200) {document.getElementById('box1').innerHTML = xmlhttp.responseTextcallback && callback()}}// 打开链接发送请求xmlhttp.open('GET', 'http://127.0.0.1:3000/' + url, true)xmlhttp.send()
}// 轮询
setInterval(function() {loadXMLDoc('fetchMsg')
}, 2000)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

上述代码,设置定时任务,每隔 2s使用 ajax发起一次请求,客户端根据服务端返回的数据来进行决定执行对应的操作,除了发送 ajax,你还可以使用 fetch

fetch('localhost:3000/fetchMsg', {headers: {'Accept': 'application/json, text/plain, */*'}
}
  • 1
  • 2
  • 3
  • 4
  • 5

引申:fetch目前的浏览器支持度还很低,所以在实际生产环境中使用的时候,最好添加一些 polyfill,一种垫片使用顺序示例如下: 
es5 的 polyfill — es5-shim 
Promise 的 polyfill — es6-promise - IE8+ 
fetch 的 polyfill — fetch - IE10+

如果你在使用某种框架,例如 vue 或者 angular,那么你同样可以使用这些框架自带的请求方法,总之基于页面的友好访问性,在发送请求的同时不要刷新页面就行了。

优点:

前后端程序都很容易编写,没什么技术难度

缺点:

这种方法因为需要对服务器进行持续不断的请求,就算你设置的请求间隔时间很长,但在用户访问量比较大的情况下,也很容易给服务器带来很大的压力,而且绝大部分情况下都是无效请求,浪费带宽和服务器资源,一般不会用于实际生产环境的,自己知道一下就行了。


长轮询

相比于上一种实现,长轮询同样是客户端发起请求,服务端返回数据,只不过不同的是,在长轮询的情况下,服务器端在接到客户端请求之后,如果发现数据库中的数据并没有更新或者不符合要求,那么就不会立即响应客户端,而是 hold住这次请求,直到符合要求的数据到达或者因为超时等原因才会关闭连接,客户端在接收到新数据或者连接被关闭后,再次发起新的请求。

为了节约资源,一次长轮询的周期时间最好在 10s ~ 25s左右,长连接也是实际生产环境中,被广泛运用于实时通信的技术。

客户端代码如下:

function getData() {loadXMLDoc('holdFetchMsg', ()=>{getData()})
}
getData()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

想要在连接断开或发生错误的时候,再次发起请求连接,实现也很简单,以下问使用 fetch 实现示例:

function getData() {let result = fetch('http://127.0.0.1:3000/holdFetchMsg', {headers: {'Accept': 'application/json, text/plain, */*'}})result.then(res=> {return res.text()}).then(data=> {document.getElementById('box1').innerHTML = data}).catch(e=> {console.log('Catch Error:', e)}).then(() => {getData()})
}
getData()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

一种较为直观的服务器 hold住连接的实现如下:

router.get('/holdFetchMsg', (ctx, next)=> {let i = 0while(true) {// 这里的条件在实际环境中可以换成是到数据库查询数据的操作// 如果查询到了符合要求的数据,再 break// 不过这种可能会导致服务器进行例如疯狂查询数据库的操作,非常不友好if(++i > 2222222222) {ctx.body = '做我的狗吧'break}}
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

还有一种方法,不过这种纯粹是为了 hold住而 hold住,可以作为上一种方法的辅助,解决诸如服务端进行疯狂查询数据库的操作,类似于 Java中的 Thread.sleep()操作

let delay = 2000, i = 0while(true) {let startTime = new Date().getTime()// 这里的条件在实际环境中可以换成是到数据库查询数据的操作if(++i > 3) {ctx.body = '做我的狗吧'break} else {// 休息会,别那么频繁地进行诸如查询数据库的操作// delay 为每次查询后 sleep的时间while(new Date().getTime() < startTime + delay);}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

如果你现在的 Nodejs版本支持 ES6中的 Generator的话,那么还可以这样(koa1环境, Generator写法):

app.use(function* (next){let i = 0const sleep = ms=> {return new Promise(function timer(resolve){setTimeout(()=>{if(++i > 3) {resolve()} else {timer(resolve)}}, ms)})}yield sleep(2000)this.body = '做我的狗吧'
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

如果你现在的 Nodejs版本支持 ES7中的 async/await的话,,那么还有一种 hold住连接的方法可供选择(koa2环境):

router.get('/holdFestchMsg', async(ctx, next) => {let i = 0const sleep = ms => {return new Promise(function timer(resolve) {setTimeout(async()=>{// 这里的条件在实际环境中可以换成是到数据库查询数据的操作if(++i > 3) {resolve()} else {timer(resolve)}}, ms)})}await sleep(2000)ctx.body = '做我的狗吧'
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

优点:

尽管长轮询不可能做到每一次的响应都是有用的数据,因为服务器超时或者客户端网络环境的变化,以及服务端为了更好的分配资源而自动在一个心跳周期的末尾断掉连接等原因,而导致长轮询不可能一直存在,必须要不断地进行断开和连接操作,但无论如何,相比于短轮询来说,长轮询耗费资源明显小了很多

缺点:

服务器 hold连接依旧会消耗不少的资源,特别是当连接数很大的时候,返回数据顺序无保证,难于管理维护。


长连接

这种是基于 iframe 或者 script实现的,主要原理大概就是在主页面中插入一个隐藏的 iframe(script),然后这个 iframe(script)的 src属性指向服务端获取数据的接口,因为是iframe(script)是隐藏的,而且 iframe(script)的 刷新也不会导致 主页面刷新,所以可以为这个 iframe(script)设置一个定时器,让其每隔一段时间就朝服务器发送一次请求,这样就能获得服务端的最新数据了。

先说一下 利用 script的长连接:

前端实现:

<script>function callback(msg) {// 得到后端返回的数据console.log(msg);}function createScript() {let script = document.createElement('script')script.src = 'http://127.0.0.1:3000/fetchMsg'document.body.appendChild(script)document.body.removeChild(script)}</script>
<script>window.onload = function() {setInterval(()=>{createScript()}, 3000)}</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

后端实现:

router.get('/fetchMsg', (ctx, next)=> {ctx.body = 'callback("做我的狗吧")'
})
  • 1
  • 2
  • 3

主要是在前端,一共两条 script脚本,大致左右就是在一定的时间间隔内(示例为 3s)就动态地在页面中增删一个链接为用于请求后端数据的 script脚本。

后端则返回一段字符串,这段字符串在返回前端时,有一个 callback字段调用前端的代码,类似于 jsonp的请求。

注意:修改一个已经执行过的 script脚本的 src属性是没什么卵用的,修改之后,最多在页面的 DOM上发生一些变化,而浏览器既不会发请求,也不会执行脚本,所以这里采用动态增删整个 script标签的做法。

可以看到,这种方法其实与短轮询没什么区别,唯一的区别在于短轮询保证每次请求都能收到响应,但上述示例的长连接不一定每次都能得到响应,如果下一次长连接开始请求,上一次连接还没得到响应,则上一次连接将被终止。

当然,如果你想长连接每次也都能保证得到响应也是可以的,大致做法就是在页面中插入不止一条 script标签,每条标签对应一个请求,等到当前请求到达再决定是否移除当前 script标签。

如果想要得到有序的数据响应,则还可以将 setInterval换成递归调用,例如:

function createScript() {let script = document.createElement('script')script.src = 'http://127.0.0.1:3000/fetchMsg'document.body.appendChild(script)script.onload = ()=> {document.body.removeChild(script)// 约束轮询的频率setTimeout(()=>{createScript()}, 2000)}
}window.onload = function() {createScript()
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

使用 iframe的方式与此类似,就不赘述了,不过需要注意的是, iframe可能存在跨域的情况,可能会比 script方式麻烦一些。


WebSocket

WebSoket是 HTML5新增的 API,具体介绍如下(来源w3c菜鸟教程)

WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。

当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。

上面所提到的短轮询、长轮询、长连接,本质都是单向通信,客户端主动发起请求,服务端被动响应请求,但 WebSocket则已经是全双工通讯了,也就是说无论是客户端还是服务端都能主动向对方发起响应,服务器具备了真正的 推送能力。

一段简单的 客户端 WebSocket代码示例如下:

function myWebSocket() {let ws = new WebSocket('ws://localhost:3000')ws.onopen = ()=> {console.log('send data')ws.send('client send data')}ws.onmessage = (e)=> {let receiveMsg = e.dataconsole.log('client get data')}ws.onerror = (e)=>{console.log('Catch Error:', e)}ws.onclose = ()=> {console.log('ws close')}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

想要让客户端的 WebSocket能够连接上服务器,服务端必须要具备能够响应 WebSocket类型的请求才行,一般的服务器是没有自带这种能力的,所以必须要对服务器端程序代码做出些改变。

自己封装服务器端响应 WebSocket的代码可能会涉及到很底层的东西,所以一般都是使用第三方封装好的库,基于nodejs的 WebSocket库有很多,ws 功能简单, API形式更贴近于原生,大名鼎鼎的 socket.io 是与 Nodejs联手开发,功能齐全,被广泛运用于游戏、实时通讯等应用。

以下给出一种基于 socket.io 实现 简单客户端和服务端通信的示例:

客户端:

// HTML
<body><ul id="messages"></ul><form action="" id="msgForm"><input id="m" autocomplete="off" /><input type="submit" class="submit" value="submit"></form>
</body>// 引入 socket.io
<script src='/socket.io/socket.io.js'></script>
<script>function appendEle(parent, childValue, position = 'appendChild') {let child = document.createElement('li')child.innerHTML = childValueparent[position](child)}function socketIO(msgForm, msgBox, msgList) {const socket = io()msgForm.addEventListener('submit', (e)=>{e.preventDefault()socket.emit('chat message', msgBox.value)appendEle(msgList, '<b>Client: </b>' + msgBox.value)msgBox.value = ''})socket.on('chat message', (msg)=>{appendEle(msgList, msg)})}window.onload = ()=>{let msgForm = document.querySelector('#msgForm')let msgBox = document.querySelector('#m')let msgList = document.querySelector('#messages')socketIO(msgForm, msgBox, msgList)}</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

服务端实现:

const app = require('express')()
const http = require('http').Server(app)
const io = require('socket.io')(http)app.get('/', (req, res)=> {res.sendFile(__dirname + '/index.html')
})io.on('connection', socket=>{console.log('a user connected')socket.on('disconnect', ()=>{console.log('user disconnect')})socket.on('chat message', (msg)=>{console.log('clien get message: ', msg)setTimeout(()=>{io.emit('chat message', '<b>Server:</b>' + ' Are you Sure? -- Come from your father')}, 1500)})
})http.listen(3000, ()=> {console.log('Server running at 3000.')
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

效果如下:

注、websocket是javaweb实现即时消息推送最佳方案,但是需要服务器jdk在版本7以上支持,低版本浏览器还不支持,所以要支持低版本即时消息推送还需要选择另外一种方法。

使用反向ajax框架DWR

DWR(Direct Web RemoTIng)是一个Web远程调用AJAX扩展框架,通过DWR客户端的JavaScript可以直接调用Web服务器上的JavaBean类的方法,解决了原有AJAX应用必需请求HTTP控制组件(如Servlet,Struts的AcTIon等)才能调用服务器端业务类的方法,从而简化了AJAX应用的开发。使用DWR可以不需要编写复杂的控制层组件。

  1.2 DWR反向AJAX技术

  正常情况下,DWR调用服务器端的JavaBean对象方法使用正向请求/响应模式,也称为拉模式(Pull Model),由客户端JavaScript调用JavaBean方法,返回结果通过回调方法更新页面上的HTML元素,实现监控数据的显示。这种正向模式符合一般的管理系统应用,但对监控系统实时性要求较高的应用却力不从心。而反向模式即推模式(Push Model),是适应监控系统的最佳方式,由服务器组件将取得的监控数据推送到Web客户端,不需要客户端主动请求,而是被动接收。因而无需进行Web层进行页面刷新,即可实现数据更新显示。

  最新版本的DWR 2.X增加了反向(Reverse AJAX)功能,通过反向AJAX功能,服务器端的JavaBean对象可以将取得的数据直接推送到指定的客户端页面,写到指定的HTML元素内,这个过程不需要客户端进行任何的请求操作。

javaweb实现即时消息推送功能相关推荐

  1. 拾人牙慧篇之——基于HTML5中websocket来实现消息推送功能

    一.写在前面 要求做一个,后台发布信息,前台能即时得到通知的消息推送功能.网上搜了也有很多方式,ajax的定时询问,Comet方式,Server-Sent方式,以及websocket.表示除了定时询问 ...

  2. php 企业号文本消息推送,Python如何实现微信企业号文本消息推送功能的示例

    这篇文章主要介绍了Python编程实现微信企业号文本消息推送功能,结合实例形式分析了Python微信企业号文本消息推送接口的调用相关操作技巧,需要的朋友可以参考下 本文实例讲述了Python微信企业号 ...

  3. 环境监控告警系统之TIM即时消息推送部署

    TIM是由腾讯发布的多平台客户端应用.TIM是轻聊的QQ,更方便办公.TIM用在QQ轻聊版的基础上加入了协同办公服务的支持,消息完全同步,支持多人在线编辑Word.Excel文档等,更加适合办公使用. ...

  4. tornado服务器动态文件,tornado 实现服务器消息推送功能

    这篇文章介绍tornado 服务器消息推送功能服务器端与客户端实现的的方法. 消息推送的过程: 客户端1 连接请求,服务器先hold 住,别返回: 客户端2 发送消息,服务器把信息返回给 客户端1. ...

  5. nodejs android 推送,利用Nodejs怎么实现一个微信小程序消息推送功能

    利用Nodejs怎么实现一个微信小程序消息推送功能 发布时间:2021-01-20 13:55:29 来源:亿速云 阅读:92 作者:Leah 今天就跟大家聊聊有关利用Nodejs怎么实现一个微信小程 ...

  6. openfire消息通知推送_APP消息推送功能之前端后台设计

    APP消息推送功能之前端后台设计 最近有不少小伙伴问APP消息推送功能,前端.后台如何设计的?消息系统的架构是什么样的?最近刚好做到后台消息推送这块,简单谈谈个人心得,欢迎拍砖. 消息推送是让自己的用 ...

  7. java发送qq消息_Java点餐系统和点餐小程序新加微信消息推送功能

    其实想给点餐系统加推送很久了,之前也有单门写过Java版的微信消息推送和云开发版的微信消息推送.之所以一直没有加,也是考虑到大家的学习接受度,因为做订阅消息推送是一个综合性的开发工作. 需要你既要会小 ...

  8. 小程序云开发之消息推送功能

    小程序云开发之消息推送功能(图文) 一:新建项目 APPID获取方法:1.在微信公众平台上注册账号,选择小程序(也可以从服务号注册,前提你有一个服务号)注册后登录,登录时微信扫码验证一下 2.填写小程 ...

  9. 微信小程序消息推送功能开发(java实现)

    先好好把官方文档看一看,链接https://developers.weixin.qq.com/miniprogram/dev/framework/server-ability/message-push ...

  10. 友盟消息推送服务器demo,umeng友盟消息推送功能集成

    umeng友盟消息推送功能集成(本人使用的是eclipse开发) 1.首先请自行观看友盟消息推送集成的API文档. 观看地址如下: 2.集成步骤如下 下载sdk 注意:有两种sdk如果用户已经集成支付 ...

最新文章

  1. 图像处理中ct图的通道是多少_常见医疗扫描图像处理步骤
  2. java udp分别用DatagramSocket和DatagramChannel实现多计算机接收广播数据
  3. Linux快速复制或删除大量小文件
  4. 出现ESXi系统无法连接FreeNAS的情况?90%以上的人都做错了!
  5. 63.ExtJs事件(自定义事件、on、eventManager)示例
  6. Android拍照与相册选取图片
  7. java swing 飞机大战游戏 github 免费 开源 公开 源码
  8. java reader类 实例_java字符流-java writer-java reader-嗨客网
  9. 贪心科技NLP实习面试
  10. Linux磁盘管理之GPT分区,Linux磁盘管理之GPT分区
  11. 【Mapreduce】利用job嵌套,多重Mapreduce,求解二度人脉
  12. 1446. 连续字符【我亦无他唯手熟尔】
  13. Hive distribute 问题
  14. 【快乐摸鱼】— 用python开发益智游戏
  15. Java char数组的神奇打印(数组名直接输出内容!)
  16. Python开发网站
  17. 无线通信基础知识3:电磁波的传播
  18. 【网络工程师】<软考中级>局域网与城域网
  19. API接口开放平台-淘宝API接口详解
  20. 中国人工晶状体行业运行态势分析及发展战略规划建议报告2022-2028年版

热门文章

  1. c c++ 实现代理服务器
  2. EGO Planner代码解析bspline_optimizer部分(3)
  3. 使用WinImage的命令行修改img文件
  4. android最新版安装教程,在PC上安装Android系统的图文教程
  5. 投影技术的分类与应用
  6. 快速搭建python文件服务器,上传下载文件,快速搭建。
  7. java代码意思,[求助]java代码的意思?
  8. java file数组 初始化_Java之处理数组
  9. PMP-32项目成本管理
  10. html实现图片轮播切换箭头,最简单jquery实现带左右箭头和数字焦点的图片轮播...