> 技术文档 > Websocket_webstocket

Websocket_webstocket


1. Websocket介绍

1. 概述

Websocket 是基于TCP的一种新的网络协议。允许在单个 TCP 连接上进行全双工通信。与传统的 HTTP 请求-响应模式不同,WebSocket 提供了持久的连接,服务器和客户端可以随时主动推送数据,无需反复建立连接,可以进行双向数据传输。客户端可以向服务器发送数据,服务器也可以向客户端发送消息。

HTTP协议与WebSocket协议对比

不同点

HTTP是短连接,WebStocket是长连接。

HTTP通信是单向的,WebSocket通信是持久双向的

相同点

二者的底层都是TCP连接

HTTP 连接响应 

客户端请求一次,服务端响应一次。

WebStocket 连接响应

客户端与服务端建立连接后,服务端可随时向客户端发送消息

应用场景

建立一次连接后,客户端与服务器持久连接,客户端可以向服务器发送数据,服务器也可以向客户端发送消息。

  • 视频弹窗,如视频弹幕
  • 网页聊天,如百度上的一对一客服
  • 股票基金报价实时更新

2. WebSocket 前端 API

websocket对象创建

let ws = new WebSocket(URL格式)

URL格式:协议://ip地址/访问路径

                   协议:协议名称为 ws

WebSocket 事件

事件 事件处理程序 描述 open ws.onopen 连接建立时触发 message ws.onmessage 客户端接收服务端数据时触发 error ws.onerror 通信发生错误时触发 close ws.onclose 连接关闭时触发
 let ws = new WebSocket(\"ws://localhost/chat\") ws.onopen = function() { } ws.onmessage = function(evt) { // 通过 evt.data 可以获取服务器发送的数据 } // websocket 连接断开后触发 ws.onclose = function() { }

 WebSocket 方法

方法 描述 ws.send() 连接成功后,关闭前,发送消息 ws.close() 关闭连接

3. WebSocket 后端API

tomcat 7.0.5 版本开始支持websocket 

java websocket 应用由一系列的 Endpoint 组成,Endpoint 是个java对象,代表websocket 链接的一端,对于服务端,可以视作处理具体websocket消息的接口。可以理解为两个人面对面聊天所建立的一对一关系。

两种方式定义Endpoint 

第一种编程式: 即集成类 java.websocket.Endpoint 并实现其方法

第二种注解式:定义要给POJO, 并添加@serverEndpoint 相关注解

方法 描述 注解 onOpen() 当开启一个新的会话时调用,该方法是客户端和服务端连接后调用 @OnOpen onClose() 会话关闭时调用 @OnClose onError() 链接异常时调用 @OnError

4. WebSocket 心跳机制

为什么要心跳机制

在WebSocket使用心跳机制是为了保持 WebSocket 连接的活跃状态而设计的一种机制。在长时间没有数据传输的情况下,WebSocket 连接可能会被中间的网络设备(如路由器或防火墙)认为已经断开,从而导致连接失效。但是服务端和客户端并不知道连接失效了,还在发送数据,但这些数据都接收不到。心跳机制通过定期发送小的数据包来保持连接的活动状态,从而避免这种情况的发生。

心跳机制原理 

 心跳机制原理定期发送小的数据包,保证WebSocket 不被断开。

心跳机制的工作流程

初始化WebSocket 时开启心跳检测,就是定义一个定时器,定时向后端(服务端)发送一次信息,等待后端(服务端)给前端(客户端)一个响应消息,假设数据发到服务端没有响应消息,被视为断开连接,需要重新初始化websocket方法连接;假设数据发到服务端有响应信息,此时认为连接正常,重置定时器,重新开始计时。

  
{{ msg }}
export default { name: \"LayOut\", data() { return { websock: null, //建立的连接 lockReconnect: false, //是否真正建立连接 timeout: 20 * 1000, //20秒一次心跳 timeoutObj: null, //心跳心跳倒计时 serverTimeoutObj: null, //心跳倒计时 timeoutnum: null, //断开 重连倒计时 msg: \"\", //显示的值 sendMsg: \"\", //输入框的值 }; }, created() { // //页面刚进入时开启长连接 this.initWebSocket(); }, destroyed() { //页面销毁时关闭长连接 this.websocketclose(); }, methods: { //建立连接,初始化weosocket initWebSocket() { //后台地址,前面的ws不动,后面是后台地址,我是本地运行的所以填的本地,自行更改。再后面ws是后端的接口地址,admin是参数 const wsuri = \"ws://localhost:8001/ws/admin\"; //建立连接 this.websock = new WebSocket(wsuri); //连接成功 this.websock.onopen = this.websocketonopen; //连接错误 this.websock.onerror = this.websocketonerror; //接收信息 this.websock.onmessage = this.websocketonmessage; //连接关闭 this.websock.onclose = this.websocketclose; }, reconnect() { //重新连接 var that = this; //判断链接状态,true就是链接,false是断开,这里如果是链接状态就不继续执行了,跳出来。 if (that.lockReconnect) { return; } //把链接状态改为true that.lockReconnect = true; //没连接上会一直重连,设置延迟避免请求过多 that.timeoutnum && clearTimeout(that.timeoutnum); that.timeoutnum = setTimeout(function () { //初始化新连接 that.initWebSocket(); //把链接状态改为false that.lockReconnect = false; }, 5000); }, reset() { //重置心跳 var that = this; //清除时间 clearTimeout(that.timeoutObj); clearTimeout(that.serverTimeoutObj); //重启心跳 that.start(); }, start() { //开启心跳 var self = this; //有延迟时间的就清除掉 self.timeoutObj && clearTimeout(self.timeoutObj); self.serverTimeoutObj && clearTimeout(self.serverTimeoutObj); //从新创建计时器 self.timeoutObj = setTimeout(function () { //这里发送一个心跳,后端收到后,返回一个心跳消息 if (self.websock.readyState == 1) { //如果连接正常发送信息到后台 self.websock.send(\"ping\"); } else { //否则重连 self.reconnect(); } self.serverTimeoutObj = setTimeout(function () { //超时关闭 self.websock.close(); }, self.timeout); }, self.timeout); }, //链接成功时执行的方法 websocketonopen() { //连接成功事件 this.websocketsend(\"发送数据\"); //提示成功 console.log(\"连接成功\", 3); //开启心跳 this.start(); }, //连接失败事件 websocketonerror(e) { //错误 console.log(\"WebSocket连接发生错误\"); //重连 this.reconnect(); }, //连接关闭事件 websocketclose(e) { this.websock.close(); //提示关闭 console.log(\"连接已关闭\"); //重连 this.reconnect(); }, //接收服务器推送的信息 websocketonmessage(event) { //打印收到服务器的内容 console.log(\"收到服务器信息\", event.data); this.msg = event.data; //收到服务器信息,心跳重置 this.reset(); }, websocketsend(msg) { //向服务器发送信息 this.websock.send(msg); }, },};.layout { position: relative; width: 100%; height: 100%;}.msgBody { width: 500px; height: 300px; border: 1px solid rgb(95, 79, 79);}

 2. SpringBoot整合WebStocket

1). 使用页面作为WebSocket客户端

2). 导入WebSocket的maven坐标

3). 新建WebSocket服务端组件WebSocketServer,用于和客户端通信

4). 新建配置类WebSocketConfiguration,注册WebSocket的服务端组件

5). 新建定时任务类WebSocketTask,定时向客户端推送数据

6). SpringBoot启动类开启定时任务注解

1. 前端页面

 
{{ msg }}
export default { name: \"LayOut\", data() { return { msg: \"\", sendMsg: \"\", //后台的地址,只需要动localhost:8089部分,改成你后端的地址。 //后面webSocket是后台设定的接口地址,uuid_test001是你前台的客户端唯一id(可以使用uuid生成)。 //用于区分不同的客户端,比如你多个客户端连接后台,后台推送数据的时候需要根据这个id不同,给对应的人推送,不然就推送到所有建立链接的网页上了 path: \"ws://localhost:8089/qf/webSocket/uuid_test001\", //存websocket实例化的 socket: \"\", }; }, methods: { //用于前台发送数据到后台,调用websocket中的send方法把数据发过去。 sendMessage() { this.socket.send(this.sendMsg); }, //初始化建立前后台链接 init() { if (typeof WebSocket === \"undefined\") { alert(\"您的浏览器不支持socket\"); } else { // 实例化socket this.socket = new WebSocket(this.path); // 监听socket连接 this.socket.onopen = this.open; // 监听socket错误信息 this.socket.onerror = this.error; // 监听socket消息 this.socket.onmessage = this.getMessage; this.socket.onclose = this.close; } }, //链接成功时的回调函数 open() { console.log(\"socket连接成功\"); }, //链接错误时的回调 error(err) { console.log(\"连接错误\" + err); }, //后台消息推送过来,接收的函数,参数为后台推过来的数据。 getMessage(msg) { this.msg = msg.data; }, //链接关闭的回调 close(event) { //socket是链接的实例,close就是关闭链接 this.socket.close() console.log(\"断开链接成功\"); }, }, created() { //开局初始化建立链接 this.init(); },};.layout { position: relative; width: 100%; height: 100%;}.msgBody { width: 500px; height: 300px; border: 1px solid rgb(95, 79, 79);}

2. 在pom.xml文件中导入webStocket依赖

 org.springframework.boot spring-boot-starter-websocket

3.  新建一个类名为WebSocketConfig 的配置类,用于注册WebSocket的服务端组件。

package com.example.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.socket.server.standard.ServerEndpointExporter;/** * WebSocket配置类,用于注册WebSocket的Bean, * 确保它们在应用程序启动时被正确初始化和注册,以便能够处理WebSocket连接和通信。 */@Configurationpublic class WebSocketConfig { /** * 该方法用来创建并返回一个ServerEndpointExporter实例。 * 这个实例的作用是扫描并自动配置所有使用@ServerEndpoint注解标记的WebSocket端点 * * @return ServerEndpointExporter:这是一个用于自动检测和管理WebSocket端点的类。 * 通过将其实例化并配置为Spring管理的Bean,可以确保所有WebSocket端点在应用程序启动时被自动初始化和注册。 */ @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); }}

4. WebSocket 服务类如下,与controller同级目录新建一个包,在该包下新建一个WebSocketServer 服务类。用于和客户端通信。

package com.example.websocket;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Component;import javax.websocket.OnClose;import javax.websocket.OnMessage;import javax.websocket.OnOpen;import javax.websocket.Session;import javax.websocket.server.PathParam;import javax.websocket.server.ServerEndpoint;import java.util.Collection;import java.util.HashMap;import java.util.Map;/** * 该类负责监听客户端的连接、断开连接、接收消息、发送消息等操作。 * */@Slf4j@Component@ServerEndpoint(\"/ws/{sid}\")public class WebSocketServer { //存放会话对象 private static Map sessionMap = new HashMap(); /** * 连接建立成功调用的方法 * @param session 第一个参数必须是session * @param sid 代表客户端的唯一标识 */ @OnOpen public void onOpen(Session session, @PathParam(\"sid\") String sid) { log.info(\"客户端:\" + sid + \"建立连接\"); sessionMap.put(sid, session); } /** * 收到客户端消息后调用的方法 * * @param message 客户端发送过来的消息 */ @OnMessage public void onMessage(String message, @PathParam(\"sid\") String sid) { log.info(\"收到来自客户端:\" + sid + \"的信息:\" + message); } /** * 连接关闭调用的方法 * * @param sid */ @OnClose public void onClose(@PathParam(\"sid\") String sid) { log.info(\"连接断开:\" + sid); sessionMap.remove(sid); } /** * 群发 * * @param message */ public void sendToAllClient(String message) { Collection sessions = sessionMap.values(); for (Session session : sessions) { try { //服务器向客户端发送消息 , 发送文本信息有两种方式,一种是getBasicRemote,一种是getAsyncRemote //区别:getAsyncRemote是异步的,发送消息后立即返回,不阻塞当前线程. // 而getBasicRemote是同步的,发送消息时会阻塞当前线程,直到消息发送完成 session.getBasicRemote().sendText(message); } catch (Exception e) { e.printStackTrace(); } } }}

5. 新建一个定时任务类WebSocketTask,将 WebSocketServer 服务类注入进来。

package com.example.task;import com.example.websocket.WebSocketServer;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import java.time.LocalDateTime;import java.time.format.DateTimeFormatter;@Componentpublic class WebSocketTask { @Autowired private WebSocketServer webSocketServer; /** * 通过WebSocket每隔5秒向客户端发送消息 */ //@Scheduled(cron = \"0/5 * * * * ?\") public void sendMessageToClient() { webSocketServer.sendToAllClient(\"这是来自服务端的消息:\" + DateTimeFormatter.ofPattern(\"HH:mm:ss\").format(LocalDateTime.now())); }}

6). 在SpringBoot启动类开启定时任务调度。

@EnableScheduling //开启定时任务调度

登山网成长记录