项目场景:
WebSocket 是一种在客户端和服务器之间进行全双工通信的网络协议。它允许在一个单独的 TCP 连接上进行实时、双向的数据传输。
在实际项目中遇到了特定的需求,用到了WebSocket 网络协议。我的需求是这样的,需要持续调用第三方接口,并且调用的频率速度由前端控制,比如50ms调用一次,这个50速度参数由前端控制。可以理解为加速减速的效果。
问题描述:
如果由前端使用定时器等玩意频繁的向后端发送请求,这显然不合理。所有这个场景下,就可以使用WebSocket 在客户端和服务器之间进行全双工通信。说白了就是前后端建立连接,请求不在由前端频繁发起。而是由后端控制请求。简单理解就是后端定义一个WebSocket 的 ws 的url路径,前端根据这个专属url路径,向后端发起创建连接。连接成功后,前端向后端发送一条消息,后端收到后回应给前端。
前端代码:
提示:如下是一个简单的dome案列,有详细的注释,通过这几个按钮,打开控制台可以看到具体的效果体现,可以作为参考。
<template>
<div class="diagram">
<button @click="webSocket()">建立socket连接</button>
<button @click="websocketclose()">连接关闭</button><br>
<button @click="websocketsend(num2)">数据发送更改150</button>
<button @click="websocketsend(num1)">数据发送更改100</button>
<button @click="websocketsend(num)">数据发送50</button><br>
</div>
</template>
<script>
export default {
name: "test",
data() {
return {
num : 50,
num1 : "updateVelocityValue:100",
num2 : "updateVelocityValue:150",
};
},
created() {
//this.webSocket(); // 连接WebSocket
},
destroyed :function() {
this.websocketclose();
},
methods: {
say() {
if (this.websock == null) {
alert("连接异常:null");
return 'null'
}
if(this.userInput===""){
this.tips('请输入聊天内容');
}else{
this.tips('');
}
this.websocketsend(this.userInput);
this.userInput = '';
},
//WebSocket连接
webSocket :function() {
// 建立socket连接
if ('WebSocket' in window) {//判断当前浏览器是否支持webSocket
this.websock = new WebSocket("ws://localhost:8010/websocket/343369");//与后端服务建立连接
} else {
alert('你的浏览器暂不支持websocket :(');
}
console.log(this.websock);
this.websock.onopen = this.websocketonopen;
this.websock.onerror = this.websocketonerror;
this.websock.onmessage = this.websocketonmessage;
this.websock.onclose = this.websocketclose;
},
// 数据发送
websocketsend :function (data) {
this.websock.send(data)
},
// 数据接收
websocketonmessage :function(e) {
console.log(e.data);
},
//WebSocket连接关闭
websocketclose :function(e) {
this.websock.close();
console.log("连接关闭");
},
//WebSocket连接发生错误
websocketonerror :function(e) {
console.log("WebSocket连接发生错误");
},
}
};
</script>
<style lang="less" scoped>
</style>
后端代码(三步):
提示:后端使用的SpringBoot ,分为3个步骤。如下所示:
第一步:Maven依赖
<!--websocket-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>1.3.5.RELEASE</version>
</dependency>
第二步:配置类
package com.it.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();
}
}
第三步:具体实现(可以看看注释,很清晰,很无脑)
代码说明:前端点击socket连接按钮,会和后端建立连接,进入到后端onOpen()方法中,ws 路径需要注意,注释有描述。前端再次点击数据发送50,向后端发送消息,后端进入onMessage()方法中,接收消息并处理业务逻辑,我这里判断一下是否是默认的50参数还是修改的参数,参数为50开一个线程去执行业务逻辑。处理完事后,前端点击连接关闭。这个通道将被关闭。
package com.it.websocket; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.CopyOnWriteArraySet; import com.alibaba.fastjson.JSON; import com.it.domain.User; import com.it.dto.MyResponseDto; import com.it.service.impl.EmulationOutcomeServiceImpl; import com.it.service.impl.UserServiceImpl; import com.it.util.DateUtil; import com.it.util.SpringContextUtils; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import lombok.extern.slf4j.Slf4j; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; @Slf4j @ServerEndpoint("/websocket/{userName}") //是你连接时的url,如果后端为127.0.0.1:8080/websocket/张三,那么前端websocket连接url写 @Component // 此注解千万千万不要忘记,它的主要作用就是将这个监听器纳入到Spring容器中进行管理 public class WebSocketServer { //用来记录当前在线连接数。应该把它设计成线程安全的 private static int onlineCount = 0; //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象 private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>(); //与某个客户端的连接会话,需要通过它来给客户端发送数据 private Session session; //持续仿真的参数 private Integer velocityValue = 50; //用来执行持续仿真的线程 private Thread simulationThread; //用户名称 private String userName; /** * 连接成功时调用的方法(可以接收一个参数噢) */ @OnOpen public void onOpen(@PathParam("userName") String userName,Session session) { this.userName =userName; this.session = session; webSocketSet.add(this); addOnlineCount(); log.info("连接建立成功:当前在线人数为(" + getOnlineCount() + "人)"); } /** * 收到客户端消息后处理逻辑 * @param message 客户端发送过来的消息 */ @OnMessage public void onMessage(String message, Session session) { log.info("收到来自客户端的消息:" + message); if (message.startsWith("updateVelocityValue:")) { int newVelocityValue = Integer.parseInt(message.substring("updateVelocityValue:".length())); this.velocityValue = newVelocityValue; } else { startSimulation(); } } /** * 执行持续仿真的线程 */ private void startSimulation() { stopSimulation(); // 先停止已有的仿真线程 simulationThread = new Thread(() -> { while (simulationThread != null) { try { ApplicationContext context = SpringContextUtils.getApplicationContext(); MyResponseDto myResponseDto =null; if (context == null) { System.out.println("ApplicationContext is null"); } else { //获取流程仿真的执行状态信息 EmulationOutcomeServiceImpl emulationOutcomeService = context.getBean(EmulationOutcomeServiceImpl.class); if (emulationOutcomeService == null) { System.out.println("emulationOutcomeService is null"); } else { myResponseDto = emulationOutcomeService.sustainEmulation(velocityValue); } //根据用户名称获取用户信息(设置当前仿真时间与百分比进度) UserServiceImpl userService = context.getBean(UserServiceImpl.class); if (userService == null) { System.out.println("userService is null"); } else { User user = userService.getUserByUserName(userName); if (user != null){ //引擎返回的时间 long time = Long.parseLong(myResponseDto.getTime()); //获取用户输入的开始时间 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date startDate = null; try { startDate = sdf.parse(DateUtil.forString(user.getStartEmulationTime(), "yyyy-MM-dd HH:mm:ss")); } catch (ParseException e) { throw new RuntimeException(e); } long startTime = startDate.getTime() / 1000; // 将毫秒转换为秒 //将合并的当前时间挫转为日期字符串,并赋值给myResponseDto对象,返回前端 Date date = new Date(time+startTime); String formattedDate = sdf.format(date); myResponseDto.setTime(formattedDate); //设置百分比进度算法 Date startDateTime = user.getStartEmulationTime(); Date endDateTime = user.getEndEmulationTime(); Date currentDateTime = sdf.parse(formattedDate); long totalTime = endDateTime.getTime() - startDateTime.getTime(); long elapsedTime = currentDateTime.getTime() - startDateTime.getTime(); double progress = (double) elapsedTime / totalTime; myResponseDto.setProgress((int) (progress * 100) + "%"); } } } String jsonStr = JSON.toJSONString(myResponseDto); sendInfo(jsonStr); Thread.sleep(velocityValue); } catch (Exception e) { log.info("连接断开"); throw new RuntimeException(e); } } }); //开启线程 simulationThread.start(); } //关闭持续仿真的线程(方案二:给线程打个中断标记) private void stopSimulation() { System.out.println("---"); if (simulationThread != null && simulationThread.isAlive()) { simulationThread.interrupt(); try { simulationThread.join(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } simulationThread = null; } } /** * 给前端发送消息。 */ public static void sendInfo(String message) throws IOException { log.info(message); for (WebSocketServer item : webSocketSet) { try { item.sendMessage(message); } catch (IOException e) { continue; } } } //发送消息 public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); } /** * 关闭连接时调用 */ @OnClose public void onClose() { webSocketSet.remove(this); subOnlineCount(); this.stopSimulation(); log.info("有一连接关闭!当前在线人数为" + getOnlineCount()); } /** * 发生错误时调用 */ @OnError public void onError(Session session, Throwable error) { log.error("发生错误"); error.printStackTrace(); } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { WebSocketServer.onlineCount++; } public static synchronized void subOnlineCount() { WebSocketServer.onlineCount--; } }
如上只是很浅的使用,如果对你有帮助,三连支持哈,真的不能再细了!!!