WebSocket服务端消息推送

前言:移动互联网蓬勃发展的今天,大部分手机 APP和网站都提供了消息推送功能&#x…

前言:移动互联网蓬勃发展的今天,大部分手机 APP和网站都提供了消息推送功能,如新闻客户端的热点新闻推荐,IM 工具的聊天消息提醒,电商产品促销信息,企业应用的通知和审批流程等等。推送对于提高产品活跃度、提高功能模块使用率、提升用户粘性、提升用户留存率起到了重要作用,作为 APP 和网站运营中一个关键的渠道,对消息推送的合理运用能有效促进目标的实现。

一、浅析web端的消息推送原理

股票曲线实时变化,在线IM聊天等等,Web系统里总是能见到消息推送的应用。消息推送用好了能增强用户体验,实现消息推送有N种解决方案。

1.1、什么是消息推送

消息推送(Push)指运营人员通过自己的产品或第三方工具对用户当前网页或移动设备进行的主动消息推送。用户可以在网页上或移动设备锁定屏幕和通知栏看到push消息通知。以此来实现用户的多层次需求,使得用户能够自己设定所需要的信息频道,得到即时消息,简单说就是一种定制信息的实现方式。我们平时浏览邮箱时突然弹出消息提示收到新邮件就属于web端消息推送,在手机锁屏上看到的微信消息等等都属于APP消息推送。

Web网站推送:

当我们在浏览网站观望犹豫时,突然看到了系统发来一条消息,一位神秘的神豪老板竟然爆出了麻痹戒指!!!我的天,于是我果断开始了游戏!这消息很及时!

APP移动推送:

上述两种经典场景,是生活中比较常见的场景,也引出了两大推送种类,Web端消息推送和移动端消息推送。本篇博客主要介绍Web推送,顺便提一句移动端App常见第三方推送SDK有极光推送、小米推送等等。

1.2、Web端实现消息推送的四种方式

主要介绍web端其中的四种实现方式:短轮询、Comet长轮询、Server-sent、WebSocket。

(1)短轮询

指在特定的的时间间隔(如每10秒),由浏览器对服务器发出HTTP request,然后由服务器返回最新的数据给客户端的浏览器。浏览器做处理后进行显示。无论后端此时是否有新的消息产生,都会进行响应。字面上看,这种方式是最简单的。这种方式的优点是,后端编写非常简单,逻辑不复杂。但是缺点是请求中大部分中是无用的,浪费了带宽和服务器资源。总结来说,简单粗暴,适用于小型(偷懒)应用。

(2)Comet长轮询

长轮询是客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求;长连接是在页面中的iframe发送请求到服务端,服务端hold住请求并不断将需要返回前端的数据封装成调用javascript函数的形式响应到前端,前端不断收到响应并处理。Comet的实现原理和短轮询相比,很明显少了很多无用请求,减少了带宽压力,实现起来比短轮询复杂一丢丢。想比用短轮询的同学有梦想时,就可以用Comet来实现自己的推送。

长轮询的优点很明显,在无消息的情况下不会频繁的请求,耗费资小并且实现了服务端主动向前端推送的功能,但是服务器hold连接会消耗资源,返回数据顺序无保证,难于管理维护。WebQQ(好像挂了)就是这样实现的。

(3)Server-sent

服务器推指的是HTML5规范中提供的服务端事件EventSource,浏览器在实现了该规范的前提下创建一个EventSource连接后,便可收到服务端的发送的消息,实现一个单向通信。客户端进行监听,并对响应的信息处理显示。该种方式已经实现了服务端主动推送至前端的功能。优点是在单项传输数据的场景中完全满足需求,开发人员扩展起来基本不需要改后端代码,直接用现有框架和技术就可以集成。

(4)WebSocket

WebSocket是HTML5下一种新的协议,是基于TCP的应用层协议,只需要一次连接,便可以实现全双工通信,客户端和服务端可以相互主动发送消息。客户端进行监听,并对响应的消息处理显示。

这个技术相信基本都听说过,就算没写过代码,也大概知道干嘛的。通过名字就能知道,这是一个Socket连接,一个能在浏览器上用的Socket连接。WebSocket是HTML5标准中的一个内容,浏览器通过javascript脚本手动创建一个TCP连接与服务端进行通讯。优点是双向通信,都可以主动发送消息,既可以满足“问”+“答”的响应机制,也可以实现主动推送的功能。缺点就是编码相对来说会多点,服务端处理更复杂(我觉得当一条有情怀的咸鱼就应该用这个!)。

1.3、实现个性化的推送

上面说了很多实现方案,针对自己系统的应用场景选择合适的推送方案才是合理的,因此最后简单说一下实现个性化推送的两种方式。第一种很简单,直接使用第三方实现的推送,无需复杂的开发运维,直接可以使用。第二种就是自己封装,可以选择如今较为火热的WebSocket来实现系统的消息推送。

①直接用第三方的消息推送服务(并发量多了会收费)

在这里推荐一个第三方推送平台,GoEasy。

推荐理由是GoEasy的理念符合我们的选择(可参考http://t.cn/Ex6jg3q):

(1)更简单的方式将消息从服务器端推送至客户端
(2)更简单的方式将消息从各种客户端推送至客户端

GoEasy具体的使用方式这里不再赘述,详见官网。对于后端后端开发者,可直接使用Rest方式调用推送,对于前端或web开发者,可以从web客户端用javascript脚本进行调用推送。

②封装自己的推送服务

如果是一个老系统进行扩展,那么更推荐使用Server-sent,服务端改动量不会很大。如果是新系统,更推荐websocket,实现的功能功能更全面。

我们如果需要使用websocket技术实现自己的推送服务,需要注意哪些点,或者说需要踩哪些坑呢,本文列出几点供大家参考:

长连接的心跳激活处理;

服务端调优实现高并发量client同时在线(单机服务器可以实现百万并发长连接);

群发消息;

服务端维持多用户的状态;

从WebSocket中获取HttpSession进行用户相关操作;

等等等….


二、WebSocket简介

2.1、websocket的由来

我们经常用的是HTTP协议,而HTTP协议是一种无状态的协议,要实现有状态的会话必须借助一些外部机制如session/cookie或者Token,这或多或少会带来一些不便,尤其是服务端和客户端需要实时交换数据的时候(监控,聊天),这个问题更加明显。为了适应这种环境,websocket就产生了,目的是即时通讯,替代轮询。

2.2、websocket概述

WebSocket 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助HTTP请求完成。WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。

websocket的特点或作用

  • 允许服务端主动向客户端推送数据

  • 在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

websocket使用的优点

  • 更强的实时性

  • 保持连接状态,创建一次连接后,之后通信时可以省略部分状态信息。较少的控制开销,在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小。在不包含扩展的情况下,对于服务器到客户端的内容,此头部大小只有2至10字节(和数据包长度有关);对于客户端到服务器的内容,此头部还需要加上额外的4字节的掩码。相对于HTTP请求每次都要携带完整的头部,此项开销显著减少了

websocket使用的缺点

由于websocket使用的持久连接,与服务器一直保持连接,对服务器压力很大

2.3、websocket请求头和响应头

浏览器发送websocket请求头类似如下:

下面是对请求头部解释(比http协议多了Upgrade和Connection,是告诉服务器包协议设计ws):

  • Accept-Encoding:浏览器可以接受的数据的压缩类型。

  • Accept-Language:浏览器可以接受的语言类型。

  • Cache-Control:no-cache不使用强缓存。

  • Connection:Upgrade 通知服务器通信协议提升。

  • Host:主机名。

  • Origin:用于验证浏览器域名是否在服务器许可范围内。

  • Pragma:no-cache HTTP/1.0定义的不使用本地缓存。

  • Sec-WebSocket-Extensions:permessage-deflate; client_max_window_bits

  • Sec-WebSocket-Key:lb69kw8CsB4CrSk9tKa3 g==
    握手协议密钥,base64编码的16字节的随机字符串。

  • Sec-WebSocket-Version:13 版本号。

  • Upgrade:websocket 使用websocket协议进行传输数据,而不使用HTTP/1.1。

  • User-Agent:用户代理字符串。

服务器接收到客户端请求后做出响应并返回如下:

下面是服务器返回的头部解释:

  • Connection:Upgrade 通信协议提升。

  • Date:通信时间

  • Upgrade: websocket 传输协议升级为websocket。

  • Sec-WebSocket-Extensions:permessage-deflate

  • Sec-WebSocket-Accept:q9g5u1WfIWaAjNgMmjlTQTqkS/k=
    将Sec-WebSocket-Key的值进行一定的运算和该值进行比较来判断是否是目标服务器响应了WebSocket请求。

  • Upgrade: 使用websocket协议进行数据传输

2.4、WebSocket和Socket的区别

短答案:就像Java和JavaScript,并没有什么太大的关系,但又不能说完全没关系。可以这么说:

  • 命名方面,Socket是一个深入人心的概念,WebSocket借用了这一概念;

  • 使用方面,完全两个东西。

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口(不是协议,为了方便使用TCP或UDP而抽象出来的一层,是位于应用层和传输控制层之间的一组接口)。

WebSocket是应用层协议。

2.5、向指定用户发送WebSocket消息并处理对方不在线的情况

给指定用户发送消息:

  • 如果接收者在线,则直接发送消息;

  • 否则将消息存储到redis,等用户上线后主动拉取未读消息。

2.6、WebSocket心跳机制

在使用WebSocket的过程中,有时候会遇到网络异常断开的情况,但是在网络断开的时候服务器端并没有触发onclose的事件。这样会有:服务器会继续向客户端发送多余的连接,并且这些数据还会丢失。所以就需要一种机制来检测客户端和服务端是否处于正常的连接状态,因此就有了WebSocket的心跳机制了。还有心跳,说明还活着,没有心跳说明已经挂掉了。

心跳机制

心跳机制是每隔一段时间会向服务器发送一个数据包,告诉服务器自己还活着,同时客户端会确认服务器端是否还活着,如果还活着的话,就会回传一个数据包给客户端来确定服务器端也还活着,否则的话,有可能是网络断开连接了,需要重连。

2.7、Netty可以实现WebSocket

Netty是由jboss提供的一款开源框架,常用于搭建RPC中的TCP服务器和WebSocket服务器,甚至是类似Tomcat的web服务器,反正就是各种网络服务器,在处理高并发的项目中,功能丰富且性能良好,基于Java中NIO的二次封装,具有比原生NIO更好更稳健的体验。


三、基于Netty实现WebSocket消息推送

因为产品需求,要实现服务端推送消息至客户端,并且支持客户端对用户点对点消息发送的社交功能。服务端给客户端推送消息,可以选择原生的WebSocket,或者更加高级的Netty框架实现

在此我极力推荐netty,因为一款好的框架一般都是在原生的基础上进行包装成更加实用方便,很多我们需要自己考虑的问题都基本可以不用去考虑,不过此文不会去讲netty有多么的高深莫测,因为这些概念性的东西随处可见,而是通过实战来达到推送消息的目的。

这个小节,我们主要讲解下如何整合Netty和WebSocket。我们需要使用netty对接websocket连接,实现双向通信,这一步需要有服务端的netty程序,用来处理客户端的websocket连接操作,例如建立连接,断开连接,收发数据等。

WebSocket消息推送实现思路:

前端使用WebSocket与服务端创建连接的时候,将用户ID传给服务端,服务端将用户ID与channel关联起来存储,同时将channel放入到channel组中。

如果需要给所有用户发送消息,直接执行channel组的writeAndFlush()方法;

如果需要给指定用户发送消息,根据用户ID查询到对应的channel,然后执行writeAndFlush()方法;

前端获取到服务端推送的消息之后,将消息内容展示到文本域中

下面是具体的代码实现,基本上每一步操作都配有注释说明,配合注释看应该还是比较容易理解的。

3.1、引入Netty的依赖

netty-all包含了netty的所有封装,hutool-all封装了常用的一些依赖,如Json相关

3.2、修改配置文件application.yml

3.3、创建NettyConfig

在NettyConfig中定义一个单例的channel组,管理所有的channel,再定义一个map,管理用户与channel的对应关系

3.4、创建Netty的初始化类NettyServer(重点)

定义两个EventLoopGroup,bossGroup辅助客户端的tcp连接请求, workGroup负责与客户端之间的读写操作,需要说明的是,需要开启一个新的线程来执行netty server,要不然会阻塞主线程,到时候就无法调用项目的其他controller接口了。

注意:启动方法需要开启一个新线程执行netty server服务,服务中配置了IdleStateHandler心跳检测,此类要在创建一个通道的第一个设置,如果超时了,则会调用userEventTriggered方法,且会告诉你超时的类型)

3.5、具体实现业务的WebSocketHandler(重点)

创建Netty配置的操作执行类WebSocketHandler,userEventTriggered为心跳检测超时所调用的方法,超时后ctx.channel().close()执行完毕会主动调用handlerRemoved删除通道及用户信息。

3.6、具体消息推送的接口

3.7、消息推送接口实现类

3.8、提供消息推送服务的Controller

主要为了测试

3.9、Web前端通过websocket与服务端连接

3.10、WebSocket断开的原因

原因有很多,最好在WebSocket断开时,将错误打印出来。

错误状态码:

WebSocket断开时,会触发CloseEvent, CloseEvent会在连接关闭时发送给使用 WebSockets 的客户端. 它在 WebSocket 对象的 onclose 事件监听器中使用。CloseEvent的code字段表示了WebSocket断开的原因。可以从该字段中分析断开的原因。

CloseEvent有三个字段需要注意, 通过分析这三个字段,一般就可以找到断开原因

  • CloseEvent.code: code是错误码,是整数类型

  • CloseEvent.reason: reason是断开原因,是字符串

  • CloseEvent.wasClean: wasClean表示是否正常断开,是布尔值。一般异常断开时,该值为false

状态码 名称 描述
0–999 保留段, 未使用.
1000 CLOSE_NORMAL 正常关闭; 无论为何目的而创建, 该链接都已成功完成任务.
1001 CLOSE_GOING_AWAY 终端离开, 可能因为服务端错误, 也可能因为浏览器正从打开连接的页面跳转离开.
1002 CLOSE_PROTOCOL_ERROR 由于协议错误而中断连接.
1003 CLOSE_UNSUPPORTED 由于接收到不允许的数据类型而断开连接 (如仅接收文本数据的终端接收到了二进制数据).
1004 保留. 其意义可能会在未来定义.
1005 CLOSE_NO_STATUS 保留. 表示没有收到预期的状态码.
1006 CLOSE_ABNORMAL 保留. 用于期望收到状态码时连接非正常关闭 (也就是说, 没有发送关闭帧).
1007 Unsupported Data 由于收到了格式不符的数据而断开连接 (如文本消息中包含了非 UTF-8 数据).
1008 Policy Violation 由于收到不符合约定的数据而断开连接. 这是一个通用状态码, 用于不适合使用 1003 和 1009 状态码的场景.
1009 CLOSE_TOO_LARGE 由于收到过大的数据帧而断开连接.
1010 Missing Extension 客户端期望服务器商定一个或多个拓展, 但服务器没有处理, 因此客户端断开连接.
1011 Internal Error 客户端由于遇到没有预料的情况阻止其完成请求, 因此服务端断开连接.
1012 Service Restart 服务器由于重启而断开连接.
1013 Try Again Later 服务器由于临时原因断开连接, 如服务器过载因此断开一部分客户端连接.
1014 由 WebSocket标准保留以便未来使用.
1015 TLS Handshake 保留. 表示连接由于无法完成 TLS 握手而关闭 (例如无法验证服务器证书).
1016–1999 由 WebSocket标准保留以便未来使用.
2000–2999 由 WebSocket拓展保留使用.
3000–3999 可以由库或框架使用.? 不应由应用使用. 可以在 IANA 注册, 先到先得.
4000–4999 可以由应用使用.

四、WebSocket和Http之长连接和短连接区别

4.1、HTTP1.0、HTTP1.1 和 HTTP2.0 的区别

HTTP是一个应用层协议,无状态的,端口号为80。主要的版本有1.0/1.1/2.0.

(1) HTTP/1.0

一次请求-响应,建立一个连接,用完关闭;

(2) HTTP/1.1

HTTP 1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理

串行化单线程处理,可以同时在同一个tcp链接上发送多个请求,但是只有响应是有顺序的,只 有上一个请求完成后,下一个才能响应。一旦有任务处理超时等,后续任务只能被阻塞(线头阻塞);

(3)HTTP/2

HTTP2支持多路复用,所以通过同一个连接实现多个http请求传输变成了可能。请求并行执行,某任务耗时严重,不会影响到任务正常执行。

4.2、什么是websocket?

Websocket是html5提出的一个协议规范,是为解决客户端与服务端实时通信。本质上是一个基于tcp,先通过HTTP/HTTPS协议发起一条特殊的http请求进行握手后创建一个用于交换数据的TCP连接。

WebSocket优势: 浏览器和服务器只需要要做一个握手的动作,在建立连接之后,双方可以在任意时刻相互推送信息。同时,服务器与客户端之间交换的头信息很小。

4.3、什么是Http长连接和短连接

在HTTP/1.0中默认使用短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断TCP连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源(如JavaScript文件、图像文件、CSS文件等),每遇到这样一个Web资源,浏览器就会重新建立一个HTTP会话。

而从HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头加入这行代码:

在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。

HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。

4.4、http和websocket的长连接区别

HTTP1.1通过使用Connection:keep-alive进行长连接,HTTP 1.1默认进行持久连接。在一次 TCP 连接中可以完成多个 HTTP 请求,但是对每个请求仍然要单独发 header,Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。这种长连接是一种“伪链接”

websocket的长连接,是一个真的全双工。长连接第一次tcp链路建立之后,后续数据可以双方都进行发送,不需要发送请求头。

keep-alive双方并没有建立正真的连接会话,服务端可以在任何一次请求完成后关闭。WebSocket 它本身就规定了是正真的、双工的长连接,两边都必须要维持住连接的状态。

4.5、HTTP2.0和HTTP1.X相比的新特性

  • 新的二进制格式(Binary Format),HTTP1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮。

  • 多路复用(MultiPlexing),即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面。

  • header压缩,如上文中所言,对前面提到过HTTP1.x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。

  • 服务端推送(server push),同SPDY一样,HTTP2.0也具有server push功能。


参考链接:

WebSocket使用

SpringBoot+WebSocket+Netty实现消息推送

本文来自网络,不代表软粉网立场,转载请注明出处:https://www.rfff.net/p/1338.html

作者: HUI

发表评论

您的电子邮箱地址不会被公开。

返回顶部