socket接收的消息怎么更新到页面_spring boot 集成 websocket 实现消息主动
前言
http協(xié)議是無狀態(tài)協(xié)議,每次請(qǐng)求都不知道前面發(fā)生了什么,而且只可以由瀏覽器端請(qǐng)求服務(wù)器端,而不能由服務(wù)器去主動(dòng)通知瀏覽器端,是單向的,在很多場景就不適合,比如實(shí)時(shí)的推送,消息通知或者股票等信息的推送;在沒有 websocket 之前,要解決這種問題,只能依靠 ajax輪詢 或者 長輪詢,這兩種方式極大的消耗資源;而websocket,只需要借助http協(xié)議進(jìn)行握手,然后保持著一個(gè)websocket連接,直到客戶端主動(dòng)斷開;相對(duì)另外的兩種方式,websocket只進(jìn)行一次連接,當(dāng)有數(shù)據(jù)的時(shí)候再推送給瀏覽器,減少帶寬的浪費(fèi)和cpu的使用。 WebSocket是html5新增加的一種通信協(xié)議,目前流行的瀏覽器都支持這個(gè)協(xié)議,例如Chrome,Safari,Firefox,Opera,IE等等,對(duì)該協(xié)議支持最早的應(yīng)該是chrome,從chrome12就已經(jīng)開始支持,隨著協(xié)議草案的不斷變化,各個(gè)瀏覽器對(duì)協(xié)議的實(shí)現(xiàn)也在不停的更新。該協(xié)議還是草案,沒有成為標(biāo)準(zhǔn),不過成為標(biāo)準(zhǔn)應(yīng)該只是時(shí)間問題了,從WebSocket草案的提出到現(xiàn)在已經(jīng)有十幾個(gè)版本了,目前對(duì)該協(xié)議支持最完善的瀏覽器應(yīng)該是chrome,畢竟WebSocket協(xié)議草案也是Google發(fā)布的,下面我們教程我們使用springboot 集成 websocket 實(shí)現(xiàn)消息的一對(duì)一以及全部通知功能。
本文使用spring boot 2.1.1+ jdk1.8 + idea。
一:引入依賴
如何創(chuàng)建springboot項(xiàng)目本文不再贅述,首先在創(chuàng)建好的項(xiàng)目pom.xml中引入如下依賴:
org.springframework.boot spring-boot-starter-websocket
org.springframework.boot spring-boot-starter-thymeleaf
二:創(chuàng)建websocket配置類
ServerEndpointExporter 會(huì)自動(dòng)注冊使用了@ServerEndpoint注解聲明的Websocket endpoint。要注意,如果使用獨(dú)立的servlet容器,而不是直接使用springboot的內(nèi)置容器,就不要注入ServerEndpointExporter,因?yàn)樗鼘⒂扇萜髯约禾峁┖凸芾怼?/p>package com.sailing.websocket.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.socket.server.standard.ServerEndpointExporter;/** * @author baibing * @project: springboot-socket * @package: com.sailing.websocket.config * @Description: socket配置類,往 spring 容器中注入ServerEndpointExporter實(shí)例 * @date 2018/12/20 09:46 */@Configurationpublic class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter(){ return new ServerEndpointExporter(); }}
三:編寫websocket服務(wù)端代碼
package com.sailing.websocket.common;import org.springframework.stereotype.Component;import javax.websocket.*;import javax.websocket.server.PathParam;import javax.websocket.server.ServerEndpoint;import java.io.IOException;import java.util.HashMap;import java.util.Map;import java.util.concurrent.atomic.AtomicInteger;/** * @author baibing * @project: springboot-socket * @package: com.sailing.websocket.common * @Description: WebSocket服務(wù)端代碼,包含接收消息,推送消息等接口 * @date 2018/12/200948 */@Component@ServerEndpoint(value = "/socket/{name}")public class WebSocketServer { //靜態(tài)變量,用來記錄當(dāng)前在線連接數(shù)。應(yīng)該把它設(shè)計(jì)成線程安全的。 private static AtomicInteger online = new AtomicInteger(); //concurrent包的線程安全Set,用來存放每個(gè)客戶端對(duì)應(yīng)的WebSocketServer對(duì)象。 private static Map sessionPools = new HashMap<>(); /** * 發(fā)送消息方法 * @param session 客戶端與socket建立的會(huì)話 * @param message 消息 * @throws IOException */ public void sendMessage(Session session, String message) throws IOException{ if(session != null){ session.getBasicRemote().sendText(message); } } /** * 連接建立成功調(diào)用 * @param session 客戶端與socket建立的會(huì)話 * @param userName 客戶端的userName */ @OnOpen public void onOpen(Session session, @PathParam(value = "name") String userName){ sessionPools.put(userName, session); addOnlineCount(); System.out.println(userName + "加入webSocket!當(dāng)前人數(shù)為" + online); try { sendMessage(session, "歡迎" + userName + "加入連接!"); } catch (IOException e) { e.printStackTrace(); } } /** * 關(guān)閉連接時(shí)調(diào)用 * @param userName 關(guān)閉連接的客戶端的姓名 */ @OnClose public void onClose(@PathParam(value = "name") String userName){ sessionPools.remove(userName); subOnlineCount(); System.out.println(userName + "斷開webSocket連接!當(dāng)前人數(shù)為" + online); } /** * 收到客戶端消息時(shí)觸發(fā)(群發(fā)) * @param message * @throws IOException */ @OnMessage public void onMessage(String message) throws IOException{ for (Session session: sessionPools.values()) { try { sendMessage(session, message); } catch(Exception e){ e.printStackTrace(); continue; } } } /** * 發(fā)生錯(cuò)誤時(shí)候 * @param session * @param throwable */ @OnError public void onError(Session session, Throwable throwable){ System.out.println("發(fā)生錯(cuò)誤"); throwable.printStackTrace(); } /** * 給指定用戶發(fā)送消息 * @param userName 用戶名 * @param message 消息 * @throws IOException */ public void sendInfo(String userName, String message){ Session session = sessionPools.get(userName); try { sendMessage(session, message); }catch (Exception e){ e.printStackTrace(); } } public static void addOnlineCount(){ online.incrementAndGet(); } public static void subOnlineCount() { online.decrementAndGet(); }}四:增加測試頁面路由配置類
如果為每一個(gè)頁面寫一個(gè)action太麻煩,spring boot 提供了頁面路由的統(tǒng)一配置,在 spring boot 2.0 以前的版本中我們只需要繼承 WebMvcConfigurerAdapter ,并重寫它的 addViewControllers 方法即可,但是 2.0版本后 WebMvcConfigurerAdapter已經(jīng)被廢棄,使用 WebMvcConfigurer 接口代替(其實(shí)WebMvcConfigurerAdapter也是實(shí)現(xiàn)了WebMvcConfigurer),所以我們只需要實(shí)現(xiàn)它即可:
package com.sailing.websocket.config;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;/** * 在SpringBoot2.0及Spring 5.0 WebMvcConfigurerAdapter已被廢棄,目前找到解決方案就有 * 1 直接實(shí)現(xiàn)WebMvcConfigurer (官方推薦) * 2 直接繼承WebMvcConfigurationSupport * @ https://blog.csdn.net/lenkvin/article/details/79482205 */@Configurationpublic class WebMvcConfig implements WebMvcConfigurer { /** * 為各個(gè)頁面提供路徑映射 * @param registry */ @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/client").setViewName("client"); registry.addViewController("/index").setViewName("index"); }}五:創(chuàng)建測試頁面
在 resources下面創(chuàng)建 templates 文件夾,編寫兩個(gè)測試頁面 index.html 和 client.html 和上面配置類中的viewName相對(duì)應(yīng),兩個(gè)頁面內(nèi)容一模一樣,只是在連接websocket部分模擬的用戶名不一樣,一個(gè)叫 lucy 一個(gè)叫 lily :
WebSocketWelcomeSend Close WebSocketWelcome
Send Close
六:測試webscoket controller
package com.sailing.websocket.controller;import com.sailing.websocket.common.WebSocketServer;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;import java.io.IOException;/** * @author baibing * @project: springboot-socket * @package: com.sailing.websocket.controller * @Description: websocket測試controller * @date 2018/12/20 10:11 */@RestControllerpublic class SocketController { @Resource private WebSocketServer webSocketServer; /** * 給指定用戶推送消息 * @param userName 用戶名 * @param message 消息 * @throws IOException */ @RequestMapping(value = "/socket", method = RequestMethod.GET) public void testSocket1(@RequestParam String userName, @RequestParam String message){ webSocketServer.sendInfo(userName, message); } /** * 給所有用戶推送消息 * @param message 消息 * @throws IOException */ @RequestMapping(value = "/socket/all", method = RequestMethod.GET) public void testSocket2(@RequestParam String message){ try { webSocketServer.onMessage(message); } catch (IOException e) { e.printStackTrace(); } }}七:測試
訪問 http://localhost:8080/index 和 http://localhost:8080/client 分別打開兩個(gè)頁面并連接到websocket,http://localhost:8080/socket?userName=lily&message=helloworld 給lily發(fā)送消息,http://localhost:8080/socket/all?message=LOL 給全部在線用戶發(fā)送消息:
技術(shù)改變一切
總結(jié)
以上是生活随笔為你收集整理的socket接收的消息怎么更新到页面_spring boot 集成 websocket 实现消息主动的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python定义私有变量的方法_Pyth
- 下一篇: 爬取过程中出现验证码_PCBA贴片的过程