服务器推送技术的应用(服务器向客户端推送消息)
发布时间:2022-07-12 22:06:12
文章来源:快乐收录网
访问次数:
一、主流服务器推送技术说明
1.1 需求与背景
若干年前,所有的请求都是由浏览器端发起,浏览器本身并没有接受请求的能力。所以一些特殊需求都是用ajax轮询的方式来实现的。
比如对于某些需要实时更新的数据(例如Facebook/Twitter 更新、股价更新、新的博文、赛事结果等)。又例如我们通过页面启动一个定时任务,前端想知道任务后台的的实时运行状态,就需要以较小的间隔,频繁的向服务器建立http连接询问定时任务是否完成,然后更新页面状态。但这样做的后果就是浪费大量流量,对服务端造成了巨大压力。
在html5被广泛推广之后,我们可以使用服务端主动推动数据的方式来,解决上面提到的问题。
1.2 服务端推送常用技术
1、全双工通信:WebSocket全双工的,全双工就是双向通信,http协议是“对讲机”之间的通话,那我们websocket就是移动电话。本质上是一个额外的tcp连接,建立和关闭时握手使用http协议,其他数据传输不使用http协议 ,更加复杂一些,比较适用于需要进行复杂双向实时数据通讯的场景。 2、服务端主动推送:SSE (Server Send Event)html5新标准,用来从服务端实时推送数据到浏览器端, 直接建立在当前http连接上,本质上是保持一个http长连接,轻量协议 。客户端发送一个请求到服务端 ,服务端保持这个请求直到一个新的消息准备好,将消息返回至客户端,此时不关闭连接,仍然保持它,供其它消息使用。SSE的一大特色就是重复利用一个连接来处理每一个消息(又称event)。
1.3 websocket与SSE比较
|
是否基于新协议 |
是否双向通信 |
是否支持跨域 |
编码难度 |
SSE |
否(Http) |
否(服务器单向) |
否(Firefox 支持跨域) |
低 |
WebSocket |
是(ws) |
是 |
是 |
略高 |
但是IE和Edge浏览器不支持SSE。 |
|
|
|
|
二、服务端推送事件SSE
2.1 模拟网络支付场景
ssetest.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SSE</title>
</head>
<body>
<div id = "message"></div>
<script>
if (window.EventSource) {
var source = new EventSource('orderpay');
innerHTML = '';
source.addEventListener('message', function(e) {
innerHTML += e.data + "<br/>";
document.getElementById("message").innerHTML = innerHTML;
});
source.addEventListener('open', function(e) {
console.log("连接打开.");
}, false);
// 响应finish事件,主动关闭EventSource
source.addEventListener('finish', function(e) {
console.log("数据接收完毕,关闭EventSource");
source.close();
console.log(e);
}, false);
source.addEventListener('error', function(e) {
if (e.readyState == EventSource.CLOSED) {
console.log("连接关闭");
} else {
console.log(e);
}
}, false);
} else {
console.log("你的浏览器不支持SSE");
}
</script>
</body>
</html>
@Controller
@RequestMapping("sse")
public class SSEControler {
public static final ConcurrentHashMap<Long,SseEmitter> sseEmitters = new ConcurrentHashMap<>();
@GetMapping("/test")
public String ssetest(){
return "ssetest";
}
@GetMapping("/orderpay")
public SseEmitter orderpay(){
Long payRecordId = 1L;
//设置默认的超时时间60秒
final SseEmitter emitter = new SseEmitter(60 * 1000L);
try {
System.out.println("连接建立成功");
//TODO 这里可以做一些订单保存的操作
sseEmitters.put(payRecordId,emitter);
}catch (Exception e){
emitter.completeWithError(e);
}
return emitter;
}
@GetMapping("/payback")
public @ResponseBody String payback (){
SseEmitter emitter = sseEmitters.get(1L);
try {
emitter.send("支付成功");
System.out.println("发送finish事件");
emitter.send(SseEmitter.event().name("finish").id("6666").data("哈哈"));
System.out.println("调用complete");
emitter.complete();
} catch (IOException e) {
emitter.completeWithError(e);
}
return "ok";
}
}
对连接超时异常进行全局处理
@ExceptionHandler(AsyncRequestTimeoutException.class)
@ResponseBody
public String handleAsyncRequestTimeoutException(AsyncRequestTimeoutException e) {
return SseEmitter.event().data("timeout!!").build().stream()
.map(d -> d.getData().toString())
.collect(Collectors.joining());
}
2.2访问测试
http://localhost:8888/sse/test
http://localhost:8888/sse/payback
三、双向实时通信websocket
3.1 整合websocket
<!-- 引入websocket依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
开启websocket功能
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
3.2 websocket 用法实验
WebSocketServer内容的一些核心代码,websocket服务端代码
- @ServerEndpoint(value = "/ws/asset")表示websocket的接口服务地址
- @OnOpen注解的方法,为连接建立成功时调用的方法
- @OnClose注解的方法,为连接关闭调用的方法
- @OnMessage注解的方法,为收到客户端消息后调用的方法
- @OnError注解的方法,为出现异常时调用的方法
/**
* WebSocket服务端示例
*/
@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);
SendMessage(session, "连接成功");
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(Session session) {
SessionSet.remove(session);
int cnt = OnlineCount.decrementAndGet();
log.info("有连接关闭,当前连接数为:{}", cnt);
}
/**
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) throws IOException {
log.info("来自客户端的消息:{}",message);
SendMessage(session, "收到消息,消息内容:"+message);
}
/**
* 出现错误
*/
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);
}
}
}
/**
* 指定Session发送消息
* @param sessionId sessionId
* @param message 消息
*/
public static void SendMessage(String sessionId,String message) throws IOException {
Session session = null;
for (Session s : SessionSet) {
if(s.getId().equals(sessionId)){
session = s;
break;
}
}
if(session!=null){
SendMessage(session, message);
} else{
log.warn("没有找到你指定ID的会话:{}",sessionId);
}
}
}
客户端代码,做几次实验,自然明了代码的意思,先不要看代码,先看效果。public/wstest/html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>websocket测试</title>
<style type="text/css">
h3,h4{
text-align:center;
}
</style>
</head>
<body>
<h3>WebSocket测试,在<span style="color:red">控制台</span>查看测试信息输出!</h3>
<h4>
<br>
http://localhost:8888/api/ws/sendOne?message=单发消息内容&id=none
<br>
http://localhost:8888/api/ws/sendAll?message=群发消息内容
</h4>
<h3>请输入要发送给服务器端的消息:</h3><br/>
<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("ws://localhost:8888/ws/asset");
//连接打开事件
socket.onopen = function() {
console.log("Socket 已打开");
socket.send("消息发送测试(From Client)");
};
//收到消息事件
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>
测试页面
http://localhost:8888/wstest.html
3.3 服务端广播与指定session消息发送
@RestController
@RequestMapping("/api/ws")
public class WebSocketController {
/**
* 群发消息内容
* @param message 消息内容
*/
@RequestMapping(value="/sendAll", method=RequestMethod.GET)
AjaxResponse sendAllMessage(@RequestParam String message){
try {
WebSocketServer.BroadCastInfo(message);
} catch (IOException e) {
throw new CustomException(CustomExceptionType.SYSTEM_ERROR,"群发消息失败");
}
return AjaxResponse.success();
}
/**
* 指定会话ID发消息
* @param message 消息内容
* @param id 连接会话ID
*/
@RequestMapping(value="/sendOne", method=RequestMethod.GET)
AjaxResponse sendOneMessage(@RequestParam String message,@RequestParam String id){
try {
WebSocketServer.SendMessage(id,message);
} catch (IOException e) {
throw new CustomException(CustomExceptionType.SYSTEM_ERROR,"指定会话ID发消息失败");
}
return AjaxResponse.success();
}
}
作者:BigJoker
链接:
https://juejin.cn/post/7033268093333798943
来源:稀土掘金
《服务器推送技术的应用(服务器向客户端推送消息)》更新于时间:2022-07-12 22:06:12;由本站小编进行发布,目前浏览的小伙伴达到,感谢你们的支持,后期快乐收录网小编会继续为大家更新更多相关的文章,希望广大网友多多关注快乐收录网工作心得栏目,如果觉得本站不错,那就给我们一个分享的支持吧!
服务器推送技术的应用(服务器向客户端推送消息)特别声明
本站快乐收录网提供的服务器推送技术的应用(服务器向客户端推送消息)都来源于网络,不保证文章的准确性和真实性,同时,对于该文章所造成的影响,不由快乐收录网实际控制,在2022-07-12 22:06:12收录时,该网页上的内容,都属于合规合法,如有侵权违规,可以直接联系网站管理员进行整改或删除,快乐收录网不承担任何责任。
快乐收录网:致力于优质、实用的网络站点资源收集与分享!本文地址:https://nav.klxjz.cn/zixundaquan/wzyh/202207/9450.html转载请注明标签:
- 1华为 Nova 10 和 Nova 10 Pro 配备 120 Hz OLED 显示屏
- 2Realme GT2 Master Explorer Edition设计随着高端智能手机发布之旅的开始而揭晓
- 3Wi-Fi 7 技术将支持 40Gbps 的速度
- 4小米 11T 和 11T Pro 配备相同的 108 MP 摄像头
- 5Garmin Forerunner 955 系列收到软件版本 11.12
- 6到 2026 年翻新智能手机市场的价值预计将增长近 460 亿美元
- 7小米发布 Band 7 Pro 固件更新 进行各种改进和优化
- 8苹果最新的MacBook Air产品将影响 Wintel 笔记本电脑的销售
- 9戴尔 Precision 7770 和 7670 现在可与英特尔第 12 代博锐 CPU 和 Nvidia RTX A5500 显卡一起购买
- 10System76 使用 Intel Alder Lake-U 处理器升级其基于 Linux 的 Lemur Pro 笔记本电脑
- 11苹果计划在今年发布标准 Watch Series 更新的替代品
- 12OnePlus的10T发布了新旗舰智能手机发布前的最高AnTuTu分数
- 13摩托罗拉 Edge 30:搭载 Android 12 的超薄中端智能手机
- 14小米 12智能手机相机是如何拍摄的
- 15NintendoSwitchOnline下周将获得被低估的神奇宝贝经典
- 16MUJI x Honda MS01 电动自行车透露最高时速 25 公里和无钥匙解锁功能
- 17Infinix 最新 Note 12 系列智能手机升级至 5G 起价低于 200 美元
- 18Amazfit 正在举行 2022 年年中的促销活动
- 19AMD 的 RDNA 3 Chiplet 专利详述了尖端着色器优化架构