服务器推送技术
需求与背景
之前所有的请求都是浏览器发起,浏览器本身没有接受请求的能力。所以一些特殊需求都是用ajax轮询的方式来实现的。
比如:
- 股价展示页面实时的获取股价更新
- 赛事的文字直播,实时更新赛况
- 通过页面启动一个任务,前端想知道任务后台的实时运行状态
HTML5推广之后,服务端主动推送数据,浏览器接受数据的方式来解决
全双工通信(full-duplex communication)—— WebSocket
全双工就是双向通信,WebSocket就是移动电话(可以随时发送信息与接受信息,就是全双工)
本质上是一个额外的TCP连接,建立和关闭时握手使用HTTP协议,其他数据传输不使用HTTP协议
- HTTP协议适用于复杂双向实时数据通讯场景
- 在Web网页上的客服、聊天室一般使用WebSocket开发
服务端主动推送: SSE(Server Send Event)
HTML5新标准,用来从服务端实时推送数据到浏览器端,直接建立在当前HTTP连接上
本质上是保持一个HTTP长连接、轻量协议。
=》 客户端发送一个请求 到服务端,服务端保持这个请求连接知道一个新的消息准备好,将消息返回到客户端。除非主动关闭,否则一直保持连接。
- 建立连接
- 服务端 -> 浏览器(连接保持)
- 关闭连接
SSE一大特色: 重复利用1个连接来接受服务器发送的消息(event),从而避免不断轮询请求建立连接,造成服务资源紧张。
是否基于新协议 | 是否双向通信 | 是否支持跨域 | |
---|---|---|---|
SSE | 否(Http) | 否(单向) | 否(Firefox 支持跨域) |
WebSocket | 是(ws) | 是 | 是 |
模拟网络支付场景
Eg: 淘宝买一个产品之后进行扫码支付,结合SSE实现这个过程
关键代码
浏览器前端实现
对于服务器端像浏览器发送的数据,浏览器端需要在JavaScript中使用EventSource对象来进行处理。EventSource使用的是标准的事件监听器方式,只需要在对象上添加相应事件处理方法。
事件名称 | 事件触发说明 | 事件处理方法 |
---|---|---|
open | 当服务器向浏览器第一次发送数据时产生 | onopen |
message | 当收到服务器发送的消息时产生 | onmessage |
error | 当出现异常时产生 | onerror |
兼容HTTPS协议
- WebSocket的ws协议是基于HTTP协议实现的。
- WebSocket的wss协议是基于HTTPS协议实现的。
一旦你的项目里面使用了https协议,你的websocket就要使用wss协议才可以。怎么让Spring Boot项目支持WSS协议?
WebSocket编程基础
连接的建立
- 前端JS向后端发送WSS连接建立请求
socket = new WebSocket("wss://localhost:8888/ws/asset");
- SpringBoot服务端WebSocket服务接收类定义如下:
@Component @Slf4j @ServerEndpoint(value = "/ws/asset") public class WebSocketServer{ }
- 全双工数据交互
前端后端都有
onopen事件监听,处理连接建立事件
onmessage事件监听,处理对方发过来的消息数据
onclose事件监听,处理连接关闭
onerror事件监听,处理交互过程中的异常
示例: websocket实现聊天软件
WebSocketServer本节内容的核心代码,websocket服务端代码
@ServerEndpoint(value = "/ws/asset")表示websocket的接口服务地址
@OnOpen注解的方法,为连接建立成功时调用的方法
@OnClose注解的方法,为连接关闭调用的方法
@OnMessage注解的方法,为收到客户端消息后调用的方法
@OnError注解的方法,为出现异常时调用的方法
- 服务端代码
@Component @Slf4j @ServerEndpoint(value = "/ws/asset") public class WebSocketServer { //用来统计连接客户端的数量 private static final AtomicInteger OnlineCount = new AtomicInteger(0); // concurrent包的线程安全Set,用来存放每个客户端对应的Session对象。 private static CopyOnWriteArraySet<Session> SessionSet = new CopyOnWriteArraySet<>(); /** * 连接建立成功调用的方法 */ @OnOpen public void onOpen(Session session) throws IOException { SessionSet.add(session); int cnt = OnlineCount.incrementAndGet(); // 在线数加1 log.info("有连接加入,当前连接数为:{}", cnt); } /** * 收到客户端消息后调用的方法 * @param message 客户端发送过来的消息 */ @OnMessage public void onMessage(String message, Session session) throws IOException { log.info("来自客户端的消息:{}",message); sendMessage(session, "Echo消息内容:"+message); // broadCastInfo(message); 群发消息 } /** * 连接关闭调用的方法 */ @OnClose public void onClose(Session session) { SessionSet.remove(session); int cnt = OnlineCount.decrementAndGet(); log.info("有连接关闭,当前连接数为:{}", cnt); } /** * 出现错误 */ @OnError public void onError(Session session, Throwable error) { log.error("发生错误:{},Session ID: {}",error.getMessage(),session.getId()); } /** * 发送消息,实践表明,每次浏览器刷新,session会发生变化。 * @param session session * @param message 消息 */ private static void sendMessage(Session session, String message) throws IOException { session.getBasicRemote().sendText(String.format("%s (From Server,Session ID=%s)",message,session.getId())); } /** * 群发消息 * @param message 消息 */ public static void broadCastInfo(String message) throws IOException { for (Session session : SessionSet) { if(session.isOpen()){ sendMessage(session, message); } } } }
客户端代码:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>websocket测试</title> <style type="text/css"> h3,h4{ text-align:center; } </style> </head> <body> <h3>请输入要发送给服务器端的消息:</h3><br/> <label for="text">输入发送信息</label><input id="text" type="text" /> <button onclick="sendToServer()">发送服务器消息</button> <button onclick="closeWebSocket()">关闭连接</button> <br> 信息: <span id="message"> </span> <script type="text/javascript"> var socket; if (typeof (WebSocket) == "undefined") { console.log("遗憾:您的浏览器不支持WebSocket"); } else { socket = new WebSocket("wss://localhost:8888/ws/asset"); //连接打开事件 socket.onopen = function() { console.log("Socket已打开"); }; //收到消息事件 socket.onmessage = function(msg) { document.getElementById('message').innerHTML += msg.data + '<br/>'; }; //连接关闭事件 socket.onclose = function() { console.log("Socket已关闭"); }; //发生了错误事件 socket.onerror = function() { alert("Socket发生了错误"); }; //窗口关闭时,关闭连接 window.unload=function() { socket.close(); }; } //关闭连接 function closeWebSocket(){ socket.close(); } //发送消息给服务器 function sendToServer(){ var message = document.getElementById('text').value; socket.send(message); } </script> </body> </html>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)