📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍
文章目录
- 写在前面的话
- 长连接 WebSocket
- 技术简介
- 四个事件
- SpringBoot 整合 WS
- 前端 JS 实现 WS
- 总结陈词
写在前面的话
本系列的上篇文章《知识点扫盲 · 学会 WebService》介绍了企业开发中WebService
技术的实际应用,这边继续介绍一下WebSocket
的基础应用,希望可以帮助到大家。
这里先介绍实战运用,深入的部分后续专题介绍,让我们开始!
Tips:WebSocket,总感觉名字和 WebService 怎么那么像?WS又算谁的缩写呢?
长连接 WebSocket
技术简介
1、WebSocket 是 HTML5 开始提供的一种浏览器与服务器进行全双工通讯的网络技术,属于应用层协议。
2、WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。
3、WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
4、虽然前后端都可以互相推数据,但主要还是后端推送给前端,常见运用场景:实时聊天、通知公告、视频弹幕。
Tips:总结一句话,保持长连接,让前后端可以互相交互,畅通无阻。
四个事件
open Socket.onopen 连接建立时触发
message Socket.onmessage 客户端接收服务端数据时触发
error Socket.onerror 通信发生错误时触发
close Socket.onclose 连接关闭时触发
Tips:混个眼熟。
SpringBoot 整合 WS
Step1、引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>
Step2、添加配置类
@Configuration
public class WebSocketConfig {/*** 注入ServerEndpointExporter,* 这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}
Step3、添加具体Socket服务
@Component
@Slf4j
@ServerEndpoint("/webSocket/{userId}")
public class WebSocketHandle {/*** 与某个客户端的连接会话,需要通过它来给客户端发送数据*/private Session session;/*** 用户ID*/private String userId;/*** concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。* 虽然@Component默认是单例模式的,但SB还是会为每个websocket连接初始化一个bean,所以可以用一个静态set保存起来。*/private static final CopyOnWriteArraySet<WebSocketHandle> WEB_SOCKETS = new CopyOnWriteArraySet<>();/*** 用来存在线连接用户信息*/private static final ConcurrentHashMap<String, Session> SESSION_POOL = new ConcurrentHashMap<>();@Overridepublic boolean equals(Object o) {if (this == o) {return true;}if (o == null || getClass() != o.getClass()) {return false;}WebSocketHandle that = (WebSocketHandle) o;return Objects.equals(session, that.session) && Objects.equals(userId, that.userId);}@Overridepublic int hashCode() {return Objects.hash(session, userId);}/*** 链接成功调用的方法*/@OnOpenpublic void onOpen(Session session, @PathParam(value = "userId") String userId) {//初始化设置当前实例的信息this.session = session;this.userId = userId;//加入全局Socket管理(Set)WEB_SOCKETS.add(this);//加入全局session管理(Map)SESSION_POOL.put(userId, session);log.info("WebSocket有新的连接,用户为:{}, 总数为:{}", userId, WEB_SOCKETS.size());}/*** 链接关闭调用的方法*/@OnClosepublic void onClose(Session session) {WEB_SOCKETS.remove(this);SESSION_POOL.remove(this.userId);log.info("WebSocket有连接断开,用户为:{}, 总数为:{}", userId, WEB_SOCKETS.size());}/*** 收到客户端消息后调用的方法*/@OnMessagepublic void onMessage(Session session, String message) {log.info("WebSocket收到客户端消息,用户为:{}, 消息为:{}:", this.userId, message);}/*** 发送错误时的处理*/@OnErrorpublic void onError(Session session, Throwable error) {log.error("用户错误,原因:" + error.getMessage());error.printStackTrace();}/*** 发消息给全部人*/public void sendAllMessage(String message) {log.info("【websocket消息】广播消息:" + message);for (WebSocketHandle webSocket : WEB_SOCKETS) {try {if (webSocket.session.isOpen()) {webSocket.session.getAsyncRemote().sendText(message);}} catch (Exception e) {e.printStackTrace();}}}/*** 发消息给单个人*/public void sendOneMessage(String userId, String message) {Session session = SESSION_POOL.get(userId);if (session != null && session.isOpen()) {try {log.info("【websocket消息】 单点消息:" + message);session.getAsyncRemote().sendText(message);} catch (Exception e) {e.printStackTrace();}}}/*** 发消息给多个人*/public void sendMoreMessage(String[] userIds, String message) {for (String userId : userIds) {Session session = SESSION_POOL.get(userId);if (session != null && session.isOpen()) {try {log.info("【websocket消息】 单点消息:" + message);session.getAsyncRemote().sendText(message);} catch (Exception e) {e.printStackTrace();}}}}
}
Step4、测试服务端效果
参考:WebSocket在线测试
按上述步骤改造完,就可以得到WS的服务端,地址形如:ws://127.0.0.1:8180/webSocket/123
如果想不写客户端,可以直接使用测试网站进行测试,如下图。
Step5、测试发送消息
随便写一个接口,就可以触发调用了,看看客户端是否有接收到。
注意,这里的webSocketHandle确实是单例的,但是单个Socket连接也是存在的,而且Bean实例里面的static类型的Map和Set还是存了后续想要操作的内容。
@RequestMapping("/socketTest")
public void socketTest(String id) throws Exception {webSocketHandle.sendOneMessage(id, "hello~");
}
前端 JS 实现 WS
前端使用WebSocket,可以用原生的方式,参考如下。
当然,也可以引用第三方库,比较出名的是socket.io。
/*** 备忘* 页面地址:http://localhost:8180/test/socketDemo.html* 后端发消息:http://localhost:8180/socketTest?id=123*/if ("WebSocket" in window) {let $scope = {}let wsUrl = "ws://127.0.0.1:8180/webSocket/123"let ws;let tt;let lockReconnect = false;// 创建WS链接$scope.createWebSocket = function (wsUrl) {try {ws = new WebSocket(wsUrl);$scope.webSocketInit();} catch (e) {lockReconnect = false$scope.webSocketReconnect(wsUrl)//重连函数}};// 初始化WS的方法$scope.webSocketInit = function () {ws.onclose = function (error) {//连接关闭的回调函数,进行重连console.log("连接已关闭...", error);$scope.webSocketReconnect(wsUrl)};ws.onerror = function (error) {//连接错误的回调函数,进行重连console.log("连接错误...", error);$scope.webSocketReconnect(wsUrl)};ws.onopen = function () {//连接建立//发一个初始化连接消息ws.send('初始化连接');//启动心跳检测$scope.heartCheck.start();};ws.onmessage = function (event) {if(event.data !== 'pong'){let $test = $('#textA')let temp = $test.text();$test.text(temp + event.data + "\r\n");console.log("收到后端的消息:", event.data);} else {console.log('收到pong消息,连接还正常~')}//接收一次后台推送的消息,即进行一次心跳检测重置$scope.heartCheck.reset();};};$scope.webSocketReconnect = function (url) {console.log("socket 连接断开,正在尝试重新建立连接");//TODO 下面这段代码会导致只重连一次,后续改进了再开放//TODO 长时间如果没收到pong消息应该也要处理,提示一下报错之类的/*if (lockReconnect) {return;}lockReconnect = true;*///没连接上会一直重连,设置延迟,避免请求过多tt && clearTimeout(tt);tt = setTimeout(function () {$scope.createWebSocket(url);}, 4000)};//心跳检测//onopen连接上,就开始start及时,如果在定时时间范围内,onmessage获取到了服务端消息,就重置reset倒计时,距离上次从后端获取消息30秒后,执行心跳检测,看是不是断了。$scope.heartCheck = {timeout: 5000, //默认30秒timeoutObj: null,reset: function () { //接收成功一次推送,就将心跳检测的倒计时重置为30秒clearTimeout(this.timeoutObj);//重置倒计时this.start();},start: function () {//启动心跳检测机制,设置倒计时30秒一次this.timeoutObj = setTimeout(function () {//启动心跳ws.send("ping");}, this.timeout)}};//开始创建webSocket连接$scope.createWebSocket(wsUrl);// 点击发消息给后端,后端收到后回复信息function sendMessage() {let message = document.getElementById("messageInput").value;if (message) {ws.send(message);}}} else {// 浏览器不支持 WebSocketalert("您的浏览器不支持 WebSocket!");
}
总结陈词
此篇文章介绍了WebSocket
的基础应用,仅供学习参考。
💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。