常见的消息推送方式
1:轮询方式
浏览器以指定的时间间隔向服务器发出HTTP请求,服务器实现试试返回数据给浏览器
缺点:数据有延时、服务器压力较大。
2:长轮询
浏览器发出ajax(异步)请求,服务器接收端接收到请求后,会阻塞请求直到有数据或者超时才返回。
缺点:
3:SSE服务器发送事件
SSE在服务器和客户端之间打开一个单向通道
服务端响应的不再是一次性的数据包,而是Text/event-stream类型的数据流信息
服务器有数据变更时将数据流式传输到客户端
4:webSocket
基于TCP连接上及逆行全双工通信的协议
全双工:允许数据在两个方向上同时传输。
半双工:允许数据在两个方向上传输,但是同一个时间段内只允许一个方向上的传输。
websocket API
客户端【浏览器】API
websocket对象提供的方法
send() 通过websocket对象调用该方法发送数据给服务端
案例
服务端API
Tomcat从7.0.5才支持websocket
Java WebSocket应用由一系列的Endpoint组成,Endpoint是一个Java对象,代表WebSocket链接的一端,对于服务端,我们可以视为处理具体websocket消息的接口
定义Endpoint两种方式:
编程式,继承类javax.websocket.Endpoint并实现其方法。
注解式,定义一个POJO,并添加@ServiceEndpoint相关注解。
Endpoint实例在WebSocket握手时创建,并在客户端于服务端链接过程中有效,最后在链接关闭时结束。在Endpoint接口中明确定义了与其生命周期相关的方法,规范实现者确保生命周期的各个阶段调用示例的相关方法,生命周期方法如下:
方法 | 描述 | 注解 |
onOpen() | 当开启一个新的会话时调用,该方法是客户端与服务端握手成功后调用的方法 | @OnOpen |
onClose() | 当会话关闭时调用 | @OnClose |
onError() | 当连接过程异常时调用 | @OnError |
两个问题
服务端如何接收客户端发送的数据呢?
1:编程式
通过添加MessageHandler消息处理器来接收消息
2:注解式
在定义Endpoint时,通过@OnMessage注解指定接收消息的方法
服务端如何推送数据给客户端呢?
发送消息则由RemoteEndpoint完成,其实例由Session维护。
发送消息有两种方式
1:通过session.getBasicRemote获取同步消息发送的实例,然后调用其sendXxx()方法发送消息
2:通过session.getAsyncRemote获取异步消息发送实例,然后调用其sendXxx()方法发送消息
例子
代码实现
引入依赖资源
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId><version>2.0.0.RELEASE</version> </dependency>
编写配置类,扫描添加有@ServerEndpoint注解的Bean
@Configuration public class FileManageWebSocketConfigMessage {/*** 注入ServerEndpointExporter,自动注册使用@ServerEndpoint注解的* @return*/public ServerEndpointExporter serverEndpointExporter(){return new ServerEndpointExporter();} }
编写配置类,获取HttpSession对象
public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator {public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {//强转HttpSession httpSession =(HttpSession) request.getHttpSession();//将httpSession对象存储到配置对象中config.getUserProperties().put(HttpSession.class.getName(),httpSession);}
使用
在@ServerEndpoint注解种引入配置器
@ServerEndpoint(value="/chat",configurator = GetHttpSessionConfigurator.class)
案例代码
webSocket.util
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;@Component
@ServerEndpoint("/webSocket/{uId}")
@Slf4j
public class WebSocketServerUtil {private Session session;private static CopyOnWriteArraySet<WebSocketServerUtil > webSocketSet = new CopyOnWriteArraySet<>();private static ConcurrentHashMap<Long,WebSocketServerUtil > webSocketMap = new ConcurrentHashMap<>();private Long uId = null;@OnOpenpublic void onOpen(Session session, @PathParam("uId") Long uId){this.session = session;this.uId = uId;if(webSocketMap .containsKey(uId)){webSocketMap .remove(uId);webSocketMap .put(uId,this);}else{webSocketMap .put(uId,this);webSocketSet.add(this);}log.info("【websocket消息】有新的连接,总数:{}",webSocketMap.size());}@OnClosepublic void onClose(){if(webSocketMap.containsKey(uId)){webSocketMap.remove(uId);//从set中删除webSocketSet.remove(this);}log.info("【websocket消息】连接断开,总数:{}",webSocketSet.size());}@OnMessagepublic void onMessage(String message){log.info("【websocket消息】收到客户端发来的消息:{}",message);}public void sendMessage(String message){try {this.session.getBasicRemote().sendText(message);} catch (IOException e) {e.printStackTrace();}}/*** 发送自定义消息* */public static void sendInfo(String message,Long uId) throws Exception {//log.info("发送消息到:"+uId+",报文:"+message);if(webSocketMap.containsKey(uId)){webSocketMap.get(uId).sendMessage(message);}else{log.error("用户"+uId+",不在线!");throw new Exception("连接已关闭,请刷新页面后重试");}}
}
调用Util方法
Long uId = new Long("1");Map msgMap = new HashMap();msgMap.put("step",1);msgMap.put("type",2);msgMap.put("msg","hello");WebSocketServerUtil.sendInfo(JsonUtil.toJson(msgMap),uId);
前端JS代码
/*** 初始化websocket连接*/
function initWebSocket() {let uId = 1;var websocket = null;if('WebSocket' in window) {websocket = new WebSocket("ws://localhost:8009/webSocket"+uId );} else {alert("该浏览器不支持websocket!");}websocket.onopen = function(event) {console.log("建立连接");websocket.send('Hello WebSockets!');}websocket.onclose = function(event) {console.log('连接关闭')reconnect(); //尝试重连websocket}//建立通信后,监听到后端的数据传递websocket.onmessage = function(event) {let data = JSON.parse(event.data);//业务处理....if(data.step == 1){alert(data.msg);}}websocket.onerror = function() {// notify.warn("websocket通信发生错误!");// initWebSocket()}window.onbeforeunload = function() {websocket.close();}
// 重连
function reconnect() {console.log("正在重连");// 进行重连setTimeout(function () {initWebSocket();}, 1000);
}