springboot与服务器推送技术整合

大哥们哪位知道,springboot与服务器推送技术整合
最新回答
槑猫

2024-11-07 01:15:50

一、主流服务器推送技术说明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。

二、服务端推送事件SSE2.1 模拟网络支付场景

ssetest.html

<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>SSE</title></head><body><divid="message"></div><script>if(window.EventSource){varsource=newEventSource('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事件,主动关闭EventSourcesource.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")publicclassSSEControler{publicstaticfinalConcurrentHashMap<Long,SseEmitter>sseEmitters=newConcurrentHashMap<>();@GetMapping("/test")publicStringssetest(){return"ssetest";}@GetMapping("/orderpay")publicSseEmitterorderpay(){LongpayRecordId=1L;//设置默认的超时时间60秒finalSseEmitteremitter=newSseEmitter(60*1000L);try{System.out.println("连接建立成功");//TODO这里可以做一些订单保存的操作sseEmitters.put(payRecordId,emitter);}catch(Exceptione){emitter.completeWithError(e);}returnemitter;}@GetMapping("/payback")public@ResponseBodyStringpayback(){SseEmitteremitter=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(IOExceptione){emitter.completeWithError(e);}return"ok";}}

对连接超时异常进行全局处理

@ExceptionHandler(AsyncRequestTimeoutException.class)@ResponseBodypublicStringhandleAsyncRequestTimeoutException(AsyncRequestTimeoutExceptione){returnSseEmitter.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

三、双向实时通信websocket3.1 整合websocket<!--引入websocket依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>

开启websocket功能

@ConfigurationpublicclassWebSocketConfig{@BeanpublicServerEndpointExporterserverEndpointExporter(){returnnewServerEndpointExporter();}}3.2 websocket 用法实验

WebSocketServer内容的一些核心代码,websocket服务端代码

@ServerEndpoint(value = "/ws/asset")表示websocket的接口服务地址

@OnOpen注解的方法,为连接建立成功时调用的方法

@OnClose注解的方法,为连接关闭调用的方法

@OnMessage注解的方法,为收到客户端消息后调用的方法

@OnError注解的方法,为出现异常时调用的方法

/***WebSocket服务端示例*/@Component@Slf4j@ServerEndpoint(value="/ws/asset")publicclassWebSocketServer{privatestaticfinalAtomicIntegerOnlineCount=newAtomicInteger(0);//concurrent包的线程安全Set,用来存放每个客户端对应的Session对象。privatestaticCopyOnWriteArraySet<Session>SessionSet=newCopyOnWriteArraySet<>();/***连接建立成功调用的方法*/@OnOpenpublicvoidonOpen(Sessionsession)throwsIOException{SessionSet.add(session);intcnt=OnlineCount.incrementAndGet();//在线数加1log.info("有连接加入,当前连接数为:{}",cnt);SendMessage(session,"连接成功");}/***连接关闭调用的方法*/@OnClosepublicvoidonClose(Sessionsession){SessionSet.remove(session);intcnt=OnlineCount.decrementAndGet();log.info("有连接关闭,当前连接数为:{}",cnt);}/***收到客户端消息后调用的方法*@parammessage客户端发送过来的消息*/@OnMessagepublicvoidonMessage(Stringmessage,Sessionsession)throwsIOException{log.info("来自客户端的消息:{}",message);SendMessage(session,"收到消息,消息内容:"+message);}/***出现错误*/publicvoidonError(Sessionsession,Throwableerror){log.error("发生错误:{},SessionID:{}",error.getMessage(),session.getId());}/***发送消息,实践表明,每次浏览器刷新,session会发生变化。*@paramsessionsession*@parammessage消息*/privatestaticvoidSendMessage(Sessionsession,Stringmessage)throwsIOException{session.getBasicRemote().sendText(String.format("%s(FromServer,SessionID=%s)",message,session.getId()));}/***群发消息*@parammessage消息*/publicstaticvoidBroadCastInfo(Stringmessage)throwsIOException{for(Sessionsession:SessionSet){if(session.isOpen()){SendMessage(session,message);}}}/***指定Session发送消息*@paramsessionIdsessionId*@parammessage消息*/publicstaticvoidSendMessage(StringsessionId,Stringmessage)throwsIOException{Sessionsession=null;for(Sessions:SessionSet){if(s.getId().equals(sessionId)){session=s;break;}}if(session!=null){SendMessage(session,message);}else{log.warn("没有找到你指定ID的会话:{}",sessionId);}}}

客户端代码,做几次实验,自然明了代码的意思,先不要看代码,先看效果。public/wstest/html

<!DOCTYPEhtml><html><head><metacharset="UTF-8"><title>websocket测试</title><styletype="text/css">h3,h4{text-align:center;}</style></head><body><h3>WebSocket测试,在<spanstyle="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/><inputid="text"type="text"/><buttononclick="sendToServer()">发送服务器消息</button><buttononclick="closeWebSocket()">关闭连接</button><br>信息:<spanid="message"></span><scripttype="text/javascript">varsocket;if(typeof(WebSocket)=="undefined"){console.log("遗憾:您的浏览器不支持WebSocket");}else{socket=newWebSocket("ws://localhost:8888/ws/asset");//连接打开事件socket.onopen=function(){console.log("Socket已打开");socket.send("消息发送测试(FromClient)");};//收到消息事件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();};}//关闭连接functioncloseWebSocket(){socket.close();}//发送消息给服务器functionsendToServer(){varmessage=document.getElementById('text').value;socket.send(message);}</script></body></html>测试页面

http://localhost:8888/wstest.html

3.3 服务端广播与指定session消息发送@RestController@RequestMapping("/api/ws")publicclassWebSocketController{/***群发消息内容*@parammessage消息内容*/@RequestMapping(value="/sendAll",method=RequestMethod.GET)AjaxResponsesendAllMessage(@RequestParamStringmessage){try{WebSocketServer.BroadCastInfo(message);}catch(IOExceptione){thrownewCustomException(CustomExceptionType.SYSTEM_ERROR,"群发消息失败");}returnAjaxResponse.success();}/***指定会话ID发消息*@parammessage消息内容*@paramid连接会话ID*/@RequestMapping(value="/sendOne",method=RequestMethod.GET)AjaxResponsesendOneMessage(@RequestParamStringmessage,@RequestParamStringid){try{WebSocketServer.SendMessage(id,message);}catch(IOExceptione){thrownewCustomException(CustomExceptionType.SYSTEM_ERROR,"指定会话ID发消息失败");}returnAjaxResponse.success();}}