什么是WebSocket
1、WebSocket是一种在单个TCP连接上进行全双工通信的协议。
2、WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。
3、在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
1、依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2、application.properties
# 端口号
server.port=8080
# 项目路径
server.servlet.context-path=/chat
3、配置类
package com.study.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
//开启WebSocket的支持,并把该类注入到spring容器中
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
4、后端
猜测:不同的userId会创建不同的实例,WebSocket后端是多例,但是:
相同的userId请求,后端接收到消息时,使用的同一个实例接收。
package com.study;
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.concurrent.ConcurrentHashMap;
/**
* 不同的userId会创建不同的实例
* 相同的userId在@OnMessage每次请求接收到消息时,使用的同一个实例接收
*/
@ServerEndpoint(value = "/websocket/{userId}")
@Component
public class WebSocket {
/**
* 存放所有在线的客户端
*/
private static ConcurrentHashMap<String, WebSocket> webSocketMap = new ConcurrentHashMap<>();
/**
* 连接uid和连接会话
*/
private String userId;
private Session session;
//新增一个方法用于主动向客户端发送消息
public static void sendMessage(String message, String userId) {
WebSocket webSocket = webSocketMap.get(userId);
if (webSocket != null) {
try {
webSocket.session.getBasicRemote().sendText(message);
System.out.println("【websocket消息】发送消息成功,用户id=" + userId + ",消息内容:" + message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
//前端请求时一个websocket时
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
this.userId = userId;
this.session = session;
webSocketMap.put(userId, this);
System.out.println("【websocket消息】有新的连接,连接id=" + userId + ":" + this);
}
//前端关闭时一个websocket时
@OnClose
public void onClose(@PathParam("userId") String userId) {
webSocketMap.remove(userId);
System.out.println("【websocket消息】连接断开:" + userId);
}
@OnError
public void onError(Session session, Throwable error) {
System.out.println("【websocket消息】WebSocket发生错误,错误信息为:" + error.getMessage());
error.printStackTrace();
}
//前端向后端发送消息
@OnMessage
public void onMessage(String message) {
System.out.println("【websocket实例】" + this);
if ("ping".equals(message)) {
sendMessage("pong", userId);
} else {
System.out.println("【websocket消息】收到客户端发来的消息:" + message);
sendMessage(this + "=" + message, userId);
}
}
}
5、前端页面
3个页面同时打开,验证结果:
猜测:不同的userId会创建不同的实例,WebSocket后端是多例,但是:
相同的userId请求,后端接收到消息时,使用的同一个实例接收。
前端页面1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket</title>
</head>
<body>
<h1>WebSocket1</h1>
<input type="text" name="message">
<button onclick="sendMessage()">发送</button>
<div>
<textarea name="reply" rows="20" cols="50"></textarea>
</div>
<script>
const socket = new WebSocket('ws://127.0.0.1:8080/chat/websocket/1001');
// 成功连接 WebSocket 服务器的回调函数
socket.onopen = function () {
console.log('WebSocket 连接成功');
// 向服务器发送消息
socket.send('Hello Server');
// 心跳
setInterval(() => {
if (socket.readyState === socket.OPEN) {
// socket.send('{"type":"ping"}')
socket.send('ping')
}
}, 5000)
};
// 接收到来自 WebSocket 服务器的消息时的回调函数
socket.onmessage = function (event) {
console.info(event)
const message = event.data;
console.log('接收到服务器的消息:', message);
document.getElementsByName("reply")[0].value += message + '\n';
};
// WebSocket 连接关闭时的回调函数
socket.onclose = function (event) {
console.log('WebSocket 连接关闭');
};
// WebSocket 连接发生错误时的回调函数
socket.onerror = function (error) {
console.error('WebSocket 发生错误:', error);
};
function sendMessage() {
let value = document.getElementsByName("message")[0].value;
socket.send(value);
}
</script>
</body>
</html>
前端页面2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket</title>
</head>
<body>
<h1>WebSocket2</h1>
<input type="text" name="message">
<button onclick="sendMessage()">发送</button>
<div>
<textarea name="reply" rows="20" cols="50"></textarea>
</div>
<script>
const socket = new WebSocket('ws://127.0.0.1:8080/chat/websocket/1002');
// 成功连接 WebSocket 服务器的回调函数
socket.onopen = function () {
console.log('WebSocket 连接成功');
// 向服务器发送消息
socket.send('Hello Server');
// 心跳
setInterval(() => {
if (socket.readyState === socket.OPEN) {
// socket.send('{"type":"ping"}')
socket.send('ping')
}
}, 5000)
};
// 接收到来自 WebSocket 服务器的消息时的回调函数
socket.onmessage = function (event) {
console.info(event)
const message = event.data;
console.log('接收到服务器的消息:', message);
document.getElementsByName("reply")[0].value += message + '\n';
};
// WebSocket 连接关闭时的回调函数
socket.onclose = function (event) {
console.log('WebSocket 连接关闭');
};
// WebSocket 连接发生错误时的回调函数
socket.onerror = function (error) {
console.error('WebSocket 发生错误:', error);
};
function sendMessage() {
let value = document.getElementsByName("message")[0].value;
socket.send(value);
}
</script>
</body>
</html>
前端页面3
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket3</title>
</head>
<body>
<h1>WebSocket2</h1>
<input type="text" name="message">
<button onclick="sendMessage()">发送</button>
<div>
<textarea name="reply" rows="20" cols="50"></textarea>
</div>
<script>
const socket = new WebSocket('ws://127.0.0.1:8080/chat/websocket/1003');
// 成功连接 WebSocket 服务器的回调函数
socket.onopen = function () {
console.log('WebSocket 连接成功');
// 向服务器发送消息
socket.send('Hello Server');
// 心跳
setInterval(() => {
if (socket.readyState === socket.OPEN) {
// socket.send('{"type":"ping"}')
socket.send('ping')
}
}, 5000)
};
// 接收到来自 WebSocket 服务器的消息时的回调函数
socket.onmessage = function (event) {
console.info(event)
const message = event.data;
console.log('接收到服务器的消息:', message);
document.getElementsByName("reply")[0].value += message + '\n';
};
// WebSocket 连接关闭时的回调函数
socket.onclose = function (event) {
console.log('WebSocket 连接关闭');
};
// WebSocket 连接发生错误时的回调函数
socket.onerror = function (error) {
console.error('WebSocket 发生错误:', error);
};
function sendMessage() {
let value = document.getElementsByName("message")[0].value;
socket.send(value);
}
</script>
</body>
</html>
6、验证结果
3个页面同时打开,验证结果:
猜测:不同的userId会创建不同的实例,WebSocket后端是多例,但是:
相同的userId请求,后端接收到消息时,使用的同一个实例接收。
同时打开2个前端页面1 :
发现先打开那个页面不能收到websocket消息,但是还能发送消息,
后打开的那个页面,能接收到2个页面发送的消息,也能发送消息。
原因:
代码逻辑问题,因为使用webSocketMap存放所有的客户端信息,
打开两个前端页面1,导致后打开那个webSocketMap的相同key把前面那个覆盖了,
但是,实际上两个页面的websocket都是正常的,只是因为存放客户端webSocketMap相同key导致。
所以,相同的userId请求,后端接收到消息时,使用的不一定是同一个实例接收。
在WebSocket的上下文中,@ServerEndpoint注解的类通常不会为具有相同属性的不同连接共享同一个实例。每个连接都是独立的,只要客户端通过new WebSocket连接服务端,后端就会产生一个实例,并且会创建新的@ServerEndpoint实例来处理。