消息推送的常见方式
轮询
浏览器以指定的时间间隔,向服务器发送HTTP请求,服务器将会实时地返回数据给浏览器。
例如,浏览器端可以定时每秒钟一次给后端发HTTP请求,服务端若有数据会实时返回给浏览器,浏览器通过前端展示出来。假设服务器端没有数据更新,则依然会返回一个空的数据。
轮询的弊端:
- 易造成浏览器端数据更新的延迟
- 服务器需要不停地处理浏览器发过来的请求,即使没有数据更新也需要定时发送空数据,给服务器造成较大负担
长轮询
长轮询时浏览器会发出ajax请求(异步的javascript和xml请求),服务器端接收到请求后,会阻塞请求,直到有数据更新或者超时,才会返回。HTTP/1.1采用的长链接就是这种方式。
与轮询的区别就在于:若服务器端无数据更新,轮询会返回空数据,长轮询则会阻塞住,直到超时或数据更新,减少了返回的次数,也减轻了服务器的压力。
SSE(server-sent event):服务器发送事件
SSE会在服务器和客户端之间打开一个单向通道(服务器向浏览器传输),服务器响应的不再是一次性的数据包,而是text/event-stream类型的数据流信息,当服务器有数据变更时,将数据流式地传输到客户端。
例如浏览器请求下载一个大文件时,就会以流式传输源源不断下载数据到客户端。
浏览器先发出请求,服务器响应并在二者之间打开一个单向通道,然后服务器开始向浏览器源源不断地传输流式数据,直到连接关闭。
WebSocket
WebSocket简介
WebSocket是一种基于TCP连接上进行全双工通信的协议。
(附HTTP协议工作模式:
http1.0:单工。因为是短连接,客户端发起请求之后,服务端处理完请求并收到客户端的响应后即断开连接
http1.1:半双工。默认开启长连接keep-alive,开启一个连接可发送多个请求
http2.0:全双工,允许服务端主动向客户端发送数据)
客户端先发送HTTP请求,请求中包含一个upgrade:websocket字段,即请求将HTTP协议升级为websocket协议,服务端响应头状态为101,表示同意将HTTP协议升级成websocket协议。
切换为websocket协议后,就可以实现全双工通信了。服务器可以主动发数据给客户端,二者之间也可以同时互相发送消息。
WebSocket API
客户端API
现在HTML5已经支持websocket协议。
- 创建websocket对象:
let ws = new WebSocket(URL);
- websocket对象相关事件:
open 连接建立时触发:ws.open
message 客户端接收到服务器发送的数据时触发 ws.onmessage
close 连接关闭时触发 ws.onclose - websocket对象提供的方法
send()方法:通过websocket对象调用send()方法发送数据给服务端,参数即为要发送的数据
<script>
let ws = new WebSocket("ws://localhost/chat");
ws.open = function(){
};
es.onmessage = function(evt){
//通过evt.data可以获取服务器发送的数据
};
ws.onclose = function(){
};
</script>
服务端API
Tomcat从7.0.5开始支持WebSocket,并实现了Java WebSocket规范。
Java WebSocket应用由一系列的Endpoint组成。Endpoint是一个Java对象,代表了WebSocket链接的一端。
(例如,某用户用浏览器和服务端建立了websocket连接之后,服务器就会为该用户创建一个自己的Endpoint。即服务端会有和每个客户端一一对应的Endpoint)
所以,对于服务端来说,Endpoint可以视为是处理客户端具体websocket消息的一个接口,专门和某一个浏览器建立一对一的关系,处理其发送的消息,并返回消息等。
Endpoint对象在Websocket握手时创建,并在客户端与服务端链接过程中有效,最后在链接关闭时结束。
通常用注解式方式实现Endpoint,即定义一个POJO,并添加@ServerEndpoint相关注解。
在Endpoint接口中定义了与其生命周期相关的方法,注解式实现如下:
- @OnOpen:当开启一个新的会话时调用,该方法是客户端与服务端握手成功后调用的方法
- @OnClose:当会话关闭时调用
- @OnError:当连接过程异常时调用
了解了Endpoint生命周期,接下来要开始数据传输了!
服务端接收客户端发送的数据:
定义Endpoint时,用@OnMessage注解指定接收消息的方法,收到消息后会自动执行;
服务端推送数据给客户端:
发送消息由RemoteEndpoint完成,其实例由Session维护。
发送消息有同步和异步两种方式:
同步:通过session,getBasicRemote获取同步消息发送的实例,然后调用其sendXxx()方法发送消息
异步:通过session,getAsyncRemote获取异步消息发送实例,然后调用其sendXxx()方法发送消息
(注:如sendText()就是发送文本数据)
@ServerEndpoint("/chat")
@***ponent
public class ChatEndpoint{
@OnOpen
//连接建立时被调用
public void onOpen(Session session, EndpointConfig config){
}
@OnMessage
//接收客户端发送的数据时被调用
public void Message(String message){
}
@OnClose
//连接关闭时调用
public void onClose(Session session){
}
}
Web浏览器和服务器建立websocket连接之后,OnOpen事件就会触发,onOpen方法就会自动执行。Tomcat会解析这些注解,然后传递onOpen方法中的参数。
实现小小聊天室
实现流程
- ws client:为客户端组件,即浏览器;login页面输入用户名密码登录后,会请求UserController里的方法;登录成功后,浏览器会向服务端发送websocket请求,触发OnOpen事件;在发送消息时,将消息推送到服务器端,触发OnMessage事件;当关闭连接时,触发OnClose事件;
- UserController:用于登录请求的接收,校验用户名密码是否正确;用户名不为空即可,密码要求为123;登录成功后跳转到聊天室页面;
- @OnOpen:OnOpen事件被触发后,在该方法中要记录session和httpsession,并广播消息;因为登录并建立会话之后,需要通知其他用户我已登录,并把最新的用户列表推送给所有的客户端;
- @OnMessage:OnMessage事件被触发后,需要解析消息,并判断收消息的人是谁,最后把消息推送给指定的人;
- @OnClose:Onclose事件被触发后,断开与客户端的连接。
消息格式
消息均为Json格式,分为客户端推送给服务端的消息,以及服务端返回客户端的消息。服务端发往客户端的消息又分两种:一种是系统消息,如显示所有好友信息;另一种是推送给用户的消息,即聊天消息。
客户端–>服务端
消息格式:{“toName”:“刘能”, “message”:“你好”}
服务端–>客户端
- 系统消息格式:{“system”:true, “fromName”:null, “message”:[“好友1”,“好友2”]}
- 推送给某一个用户的消息格式:{“system”:false, “fromName”:刘能, “message”:“你好”}