集成WebSocket

有道云:有道云笔记

spingboot3.0系列示例代码采用3.1.0版本,jdk版本使用17+

什么是WebSocket

WebSocket 是 HTML5 标准中的一种新协议,它允许浏览器和服务器之间建立一条持久的连接,双方就可以在没有关闭连接的情况下随时发送数据,而不必像传统的 HTTP 协议那样每次都需要重新建立连接。

WebSocket 协议是基于 TCP 协议实现的,它的通信速度更快,支持全双工通信,可以实现服务器向客户端推送数据。这种通信方式对于需要实时交互的应用非常有用,例如在线游戏、实时交易平台、聊天应用等。

WebSocket的主要特点:

  1. 全双工通信:服务器和客户端可以同时发送和接收数据,实现了真正的实时交互。
  2. 协议切换:WebSocket连接是在HTTP协议的基础上升级而来的,因此它可以与现有的HTTP网络应用进行平滑过渡。
  3. 轻量级连接:WebSocket连接的建立和关闭开销较小,可以提高网络通信的效率。
  4. 高度可扩展:WebSocket协议可以根据需要进行扩展,支持各种自定义消息格式和数据类型。

集成WebSocket

工程代码

0

1.引入pom依赖

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>

2.配置文件

server.port=8080 server.servlet.encoding.charset=UTF-8 server.servlet.context-path=/socket spring.application.name=springboot3-websocket spring.freemarker.request-context-attribute=request spring.freemarker.prefix=/templates/ spring.freemarker.suffix=.html spring.freemarker.content-type=text/html spring.freemarker.enabled=true spring.freemarker.cache=false spring.freemarker.charset=UTF-8 spring.freemarker.allow-request-override=false spring.freemarker.expose-request-attributes=true spring.freemarker.expose-session-attributes=true spring.freemarker.expose-spring-macro-helpers=true # Logger Config logging.level.com.lfz.websocket: debug

3.加载WebSocket配置

com.lfz.websocket.config.WebSocketConfig

package com.lfz.websocket.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }

4.创建WebSocket服务端WebSocketServer

com.lfz.websocket.server.WebSocketServer

package com.lfz.websocket.server; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import io.micrometer.common.util.StringUtils; import jakarta.websocket.*; import jakarta.websocket.server.PathParam; import jakarta.websocket.server.ServerEndpoint; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @Component @ServerEndpoint("/server/{uid}") @Slf4j public class WebSocketServer { /** * 记录当前在线连接数 */ private static int onlineCount = 0; /** * 使用线程安全的ConcurrentHashMap来存放每个客户端对应的WebSocket对象 */ private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>(); /** * 与某个客户端的连接会话,需要通过它来给客户端发送数据 */ private Session session; /** * 接收客户端消息的uid */ private String uid = ""; /** * 连接建立成功调用的方法 * @param session * @param uid */ @OnOpen public void onOpen(Session session, @PathParam("uid") String uid) { this.session = session; this.uid = uid; if (webSocketMap.containsKey(uid)) { webSocketMap.remove(uid); //加入到set中 webSocketMap.put(uid, this); } else { //加入set中 webSocketMap.put(uid, this); //在线数加1 addOnlineCount(); } log.info("用户【" + uid + "】连接成功,当前在线人数为:" + getOnlineCount()); try { sendMsg("连接成功"); } catch (IOException e) { log.error("用户【" + uid + "】网络异常!", e); } } /** * 连接关闭调用的方法 */ @OnClose public void onClose() { if (webSocketMap.containsKey(uid)) { webSocketMap.remove(uid); //从set中删除 subOnlineCount(); } log.info("用户【" + uid + "】退出,当前在线人数为:" + getOnlineCount()); } /** * 收到客户端消息后调用的方法 * @param message 客户端发送过来的消息 * @param session 会话 */ @OnMessage public void onMessage(String message, Session session) { log.info("用户【" + uid + "】发送报文:" + message); //群发消息 if (StringUtils.isNotBlank(message)) { try { //解析发送的报文 ObjectMapper objectMapper = new ObjectMapper(); Map<String, String> map = objectMapper.readValue(message, new TypeReference<Map<String, String>>(){}); //追加发送人(防止串改) map.put("fromUID", this.uid); String toUID = map.get("toUID"); //传送给对应的toUserId用户的WebSocket if (StringUtils.isNotBlank(toUID) && webSocketMap.containsKey(toUID)) { webSocketMap.get(toUID).sendMsg(objectMapper.writeValueAsString(map)); } else { //若果不在这个服务器上,可以考虑发送到mysql或者redis log.error("请求目标用户【" + toUID + "】不在该服务器上"); } } catch (Exception e) { log.error("用户【" + uid + "】发送消息异常!", e); } } } /** * 处理错误 * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { log.error("用户【" + this.uid + "】处理消息错误,原因:" + error.getMessage()); error.printStackTrace(); } /** * 实现服务器主动推送 * @param msg * @throws IOException */ private void sendMsg(String msg) throws IOException { this.session.getBasicRemote().sendText(msg); } /** * 发送自定义消息 * @param message * @param uid * @throws IOException */ public static void sendInfo(String message, @PathParam("uid") String uid) throws IOException { log.info("发送消息到用户【" + uid + "】发送的报文:" + message); if (!StringUtils.isEmpty(uid) && webSocketMap.containsKey(uid)) { webSocketMap.get(uid).sendMsg(message); } else { log.error("用户【" + uid + "】不在线!"); } } private static synchronized int getOnlineCount() { return onlineCount; } private static synchronized void addOnlineCount() { WebSocketServer.onlineCount++; } private static synchronized void subOnlineCount() { WebSocketServer.onlineCount--; } }

5.创建WebSocket测试请求WebSocketController

com.lfz.websocket.controller.WebSocketController

package com.lfz.websocket.controller; import com.lfz.websocket.server.WebSocketServer; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView; @Controller public class WebSocketController { @GetMapping("/page") public ModelAndView page() { return new ModelAndView("/webSocket"); } @RequestMapping("/push/{toUID}") @ResponseBody public ResponseEntity<String> pushToClient(String message, @PathVariable String toUID) throws Exception { WebSocketServer.sendInfo(message, toUID); return ResponseEntity.ok("Send Success!"); } }

6.resources/templates目录添加webSocket.html测试页面

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>WebSocket消息通知</title> </head> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script> <script> var socket; //打开WebSocket function openSocket() { if (typeof (WebSocket) === "undefined") { console.log("您的浏览器不支持WebSocket"); } else { console.log("您的浏览器支持WebSocket"); //实现化WebSocket对象,指定要连接的服务器地址与端口,建立连接. var socketUrl = "http://127.0.0.1:8080/socket/server/" + $("#uid").val(); //将https与http协议替换为ws协议 socketUrl = socketUrl.replace("https", "ws").replace("http", "ws"); console.log(socketUrl); if (socket != null) { socket.close(); socket = null; } socket = new WebSocket(socketUrl); //打开事件 socket.onopen = function () { console.log("WebSocket已打开"); //socket.send("这是来自客户端的消息" + location.href + new Date()); }; //获得消息事件 socket.onmessage = function (msg) { console.log(msg.data) if(msg.data!="连接成功"){ let msgjson=JSON.parse(msg.data); let mb=$("<div style=\"width: 280px;margin: 5px;\">\n" + " <span style=\"display: inline-block;padding: 5px;\">"+msgjson.fromUID+"</span>\n" + " <span style=\"max-width: 200px;word-break: break-all;display: inline-block;background-color: #fff;border-radius: 5px;border: 1px solid black;padding: 5px\">"+msgjson.Msg+"</span>\n" + " </div>"); $("#messagebox").append(mb); var getreviewHeight = $('#messagebox').prop("scrollHeight"); //等同 $('.reviewRuleList')[0].scrollHeight $('#messagebox').animate({scrollTop: getreviewHeight}, 50);//使滚动条显示在底部 }else{ alert("链接成功!"); $("#btnsend").removeAttr("disabled"); $("#btnopen").attr("disabled","disabled"); $("#uid").attr("readonly","readonly"); } }; //关闭事件 socket.onclose = function () { console.log("WebSocket已关闭"); }; //发生了错误事件 socket.onerror = function () { console.log("WebSocket发生了错误"); } } } //发送消息 function sendMessage() { if (typeof (WebSocket) === "undefined") { console.log("您的浏览器不支持WebSocket"); } else { // console.log("您的浏览器支持WebSocket"); console.log('{"toUID":"' + $("#toUID").val() + '","Msg":"' + $("#msg").val() + '"}'); socket.send('{"toUID":"' + $("#toUID").val() + '","Msg":"' + $("#msg").val() + '"}'); let mb=$("<div style=\"width: 280px;margin: 5px;float: right\">\n" + " <span style=\"display: inline-block;padding: 5px;float: right\">"+$("#uid").val()+"</span>\n" + " <span style=\"max-width: 200px;word-break: break-all;float: right;display: inline-block;background-color: greenyellow;border-radius: 5px;border: 1px solid black;padding: 5px\">" + $("#msg").val() +"</span>\n" + " </div>"); $("#messagebox").append(mb); $("#msg").val(""); var getreviewHeight = $('#messagebox').prop("scrollHeight"); //等同 $('.reviewRuleList')[0].scrollHeight $('#messagebox').animate({scrollTop: getreviewHeight}, 200);//使滚动条显示在底部 } } </script> <body> <p>【第一步操作:】:</p> <div>【发送人】:<input id="uid" name="uid" type="text" value=""> <button οnclick="openSocket()" id="btnopen">开启socket</button></div> <div>【接收人】:<input id="toUID" name="toUID" type="text" value=""></div> <div id="messagebox" style="border: 1px solid black;width: 320px;height: 300px;overflow: auto;background-color: #ddd"> </div> <p>【第二步操作:】:</p> <div>【消息】: <input id="msg" name="msg" type="text" value=""></div> <button οnclick="sendMessage()" disabled id="btnsend">发送消息</button> </body> </html>

运行结果:

0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值