websocket 简绍

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

WebSocket 的优势包括:

全双工通信:允许服务器主动向客户端发送信息,同时也能够提供客户端到服务器的低延迟通信。
减少带宽消耗:一旦建立连接之后,通信开销很小,因为不需要HTTP头部。
创建更丰富的交互体验:实时更新数据的能力,使得应用能够更加流畅地响应用户的动作

可以通过 JavaScript 的 WebSocket 对象来创建一个 WebSocket 连接。

1
var ws = new WebSocket('ws://localhost:8080');

JavaScript 设置

处理事件

WebSocket 对象提供了几个事件处理器来处理连接的状态变化:
open:连接成功建立时触发。
message:接收到消息时触发。
error:发生错误时触发。
close:连接关闭时触发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket Example</title>
<script>
document.addEventListener('DOMContentLoaded', function () {
const socket = new WebSocket('ws://localhost:8080/websocket');

socket.onopen = function (event) {
console.log('WebSocket connection established.');
};

socket.onmessage = function (event) {
console.log('Received from server:', event.data);
};

socket.onclose = function (event) {
console.log('WebSocket connection closed.');
};

socket.onerror = function (error) {
console.error('WebSocket error:', error);
};

function sendMessage() {
let message = document.getElementById('message').value;
socket.send(message);
console.log('Sent message:', message);
}
});
</script>
</head>
<body>
<input type="text" id="message" placeholder="Type your message here">
<button onclick="sendMessage()">Send Message</button>
</body>
</html>

确保你的服务器端已正确配置,并且能够接收来自客户端的连接请求。
使用HTTPS和WSS(加密的WebSocket)可以提高安全性,特别是在生产环境中。
如果你的服务器部署在不同的域上,请确保服务器配置允许跨域访问。

Java 服务端设置

导jar包

通过 maven 导入 spring boot 中 集成的 websocket jar 包

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

创建WebSocket端点

@EnableWebSocket

@EnableWebSocket 是 Spring 框架中的一个注解,用于启用 WebSocket 支持。当一个类被标记为此注解时,Spring 会自动配置必要的组件来处理 WebSocket 请求。这个注解通常应用在一个配置类上,该类负责设置 WebSocket 的处理逻辑。

registerWebSocketHandlers
  • registerWebSocketHandlers 是 Spring WebSocket 框架中的一个重要方法,它用来注册 WebSocket 处理器(WebSocketHandler)。这个方法是 WebSocketConfigurer 接口中定义的一个抽象方法,需要在你的配置类中实现。通过这个方法,你可以指定哪些路径映射到特定的 WebSocket 处理器,并可以配置其他相关选项。
  • addHandler 方法:接受两个参数,一个是 WebSocketHandler 的实例,另一个是 WebSocket 终结点的 URL 映射。
  • setAllowedOrigins 方法:指定哪些源(域名)可以访问你的 WebSocket 终结点。如果设置为 “*”,则表示任何源都可以访问。
  • withSockJS 方法:这是一个可选的配置项,用于启用 SockJS,它是一种兼容性更好的 WebSocket 实现,能够在不支持原生 WebSocket 的浏览器上使用其他传输方式(如长轮询)。

接下来,定义一个WebSocket处理器类,并配置一个端点来接受WebSocket连接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

private final WebSocketHandler webSocketHandler;

public WebSocketConfig(WebSocketHandler webSocketHandler) {
this.webSocketHandler = webSocketHandler;
}

@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketHandler, "/websocket").setAllowedOrigins("*");
}
}

这里我们定义了一个WebSocket配置类,并注册了WebSocket处理器。/websocket 是WebSocket的端点路径。

实现WebSocket处理器

afterConnectionEstablished
1
void afterConnectionEstablished(WebSocketSession session) throws Exception;
  • afterConnectionEstablished 是 WebSocketHandler 接口中定义的一个方法,它会在 WebSocket 连接成功建立之后被调用。这个方法为你提供了一个机会来执行一些初始化操作,例如记录连接信息、将连接加入到一个管理连接的集合中,或者发送初始消息给客户端。
  • WebSocketSession session —— 表示新建立的 WebSocket 会话。
  • Exception —— 如果处理过程中出现任何异常,可以抛出
afterConnectionClosed
1
void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception;
  • afterConnectionClosed 是 WebSocketHandler 接口中定义的一个方法,它会在 WebSocket 连接关闭时被调用。这个方法允许你在连接关闭后执行一些清理操作,例如从会话管理器中移除会话、记录关闭事件或者发送通知给其他客户端。
  • WebSocketSession session:表示已关闭的 WebSocket 会话。
  • CloseStatus status:表示关闭状态的原因。CloseStatus 是一个枚举类型,提供了标准的关闭码和关闭原因,例如 NORMAL_CLOSURE(正常关闭)、GOING_AWAY(离开)、PROTOCOL_ERROR(协议错误)等。
  • 抛出:Exception —— 如果处理过程中出现任何异常,可以抛出。
handleTextMessage
1
void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception;
  • handleTextMessage 是 WebSocketHandler 接口中定义的一个方法,用于处理从客户端接收到的文本消息。当客户端发送文本消息时,这个方法会被调用。对于二进制消息,则会调用 handleBinaryMessage 方法。
  • WebSocketSession session:表示当前的 WebSocket 会话。
  • TextMessage message:包含从客户端接收到的文本消息。
  • 抛出:Exception —— 如果处理过程中出现任何异常,可以抛出
    现在我们需要实现一个WebSocket处理器类来处理连接事件:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    import org.springframework.web.socket.CloseStatus;
    import org.springframework.web.socket.TextMessage;
    import org.springframework.web.socket.WebSocketSession;
    import org.springframework.web.socket.handler.TextWebSocketHandler;

    import java.util.concurrent.ConcurrentHashMap;

    import org.springframework.web.socket.CloseStatus;
    import org.springframework.web.socket.TextMessage;
    import org.springframework.web.socket.WebSocketSession;
    import org.springframework.web.socket.handler.TextWebSocketHandler;
    import java.util.concurrent.ConcurrentHashMap;

    public class WebSocketHandler extends TextWebSocketHandler {

    private static final ConcurrentHashMap<String, WebSocketSession> sessions = new ConcurrentHashMap<>();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
    super.afterConnectionEstablished(session);
    String id = session.getId();
    sessions.put(id, session);
    System.out.println("WebSocket connection established with session ID: " + id);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
    super.afterConnectionClosed(session, status);
    String id = session.getId();
    sessions.remove(id);
    System.out.println("WebSocket connection closed with session ID: " + id);
    }

    public void sendMessageToSession(String sessionId, String message) {
    WebSocketSession session = sessions.get(sessionId);
    if (session != null && session.isOpen()) {
    try {
    session.sendMessage(new TextMessage(message));
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }

    public void broadcastMessage(String message) {
    for (WebSocketSession session : sessions.values()) {
    if (session.isOpen()) {
    try {
    session.sendMessage(new TextMessage(message));
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }
    }
    }

现在我们可以使用WebSocketHandler类中的sendMessageToSession或broadcastMessage方法来发送数据给前端。
发送给特定会话:如果想要向特定的客户端发送消息,可以使用sendMessageToSession方法。你需要知道客户端的会话ID。

1
webSocketHandler.sendMessageToSession(sessionId, "Hello, client!");

广播消息:如果你想向所有已连接的客户端广播消息,可以使用broadcastMessage方法

1
webSocketHandler.broadcastMessage("Hello, everyone!")

注销WebSocket连接

要在Java后端注销WebSocket连接,可以通过调用 WebSocketHandler 类中的 removeSession 方法来实现。例如,你可以从HTTP请求或其他服务中调用此方法:

1
2
3
4
5
6
@GetMapping("/logout/{sessionId}")
public ResponseEntity<String> logout(@PathVariable String sessionId) {
WebSocketHandler handler = new WebSocketHandler(); // 或者通过依赖注入获取
handler.removeSession(sessionId);
return ResponseEntity.ok("Session " + sessionId + " has been closed.");
}

自己定义一个 固定id 方便使用

  • 前端 JavaScript 代码
    • 创建WebSocket连接:在DOM加载完成后,创建一个WebSocket连接。
      
    • 发送握手消息:在连接建立后,发送一个包含固定ID的握手消息。
      
    • 处理接收到的消息:设置消息接收处理函数,用于接收后端发送的数据。
      
  • 后端 Java 代码
    • 配置WebSocket处理器:配置WebSocket处理器,注册WebSocket路径。
    • 处理握手消息:接收前端发送的握手消息,并根据消息内容保存客户端ID与WebSocket会话的映射。
    • 发送消息:提供方法根据客户端ID发送消息。

前端 javascript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<!DOCTYPE html>
<html l ang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket Client Example</title>
</head>
<body>
<input type="text" id="messageInput" placeholder="Enter your message here">
<button id="sendButton">Send Message</button>
<div id="log"></div>

<script>
document.addEventListener('DOMContentLoaded', function () {
const socket = new WebSocket('ws://example.com/websocket');

socket.onopen = function (event) {
console.log('WebSocket connection established.');

// 固定的客户端ID
const clientId = 'client-123';

// 发送握手消息
socket.send(JSON.stringify({
type: 'handshake',
clientId: clientId,
message: 'Hello, server!'
}));
};

socket.onmessage = function (event) {
console.log('Received from server:', event.data);
document.getElementById('log').textContent += 'Received from server: ' + event.data + '\n';
};

socket.onclose = function (event) {
console.log('WebSocket connection closed.');
document.getElementById('log').textContent += 'WebSocket connection closed.\n';
};

socket.onerror = function (error) {
console.error('WebSocket error:', error);
document.getElementById('log').textContent += 'WebSocket error: ' + error + '\n';
};

document.getElementById('sendButton').addEventListener('click', function () {
let message = document.getElementById('messageInput').value;
if (socket.readyState === WebSocket.OPEN) {
socket.send(message);
document.getElementById('log').textContent += 'Sent message: ' + message + '\n';
} else {
alert('WebSocket connection is not open.');
}
});
});
</script>
</body>
</html>

java 服务器端配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.utils.socket.HandshakeMessage;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class WebSocketHandler extends TextWebSocketHandler {

private static final Map<String, WebSocketSession> clientSessions = new ConcurrentHashMap<>();
private static final ObjectMapper objectMapper = new ObjectMapper();

@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
super.afterConnectionEstablished(session);
String sessionId = session.getId();
System.out.println("WebSocket connection established with session ID: " + sessionId);
}

@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String payload = message.getPayload();
HandshakeMessage handshakeMessage = objectMapper.readValue(payload, HandshakeMessage.class);

if ("handshake".equals(handshakeMessage.getType())) {
String clientId = handshakeMessage.getClientId();
String sessionId = session.getId();

// 存储clientId与sessionId的映射关系
clientSessions.put(clientId, session);

// 可以选择回复客户端确认握手成功的消息
session.sendMessage(new TextMessage("Handshake successful"));
}
}

@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
super.afterConnectionClosed(session, status);
String sessionId = session.getId();
System.out.println("WebSocket connection closed with session ID: " + sessionId);

// 移除会话
clientSessions.values().removeIf(s -> s.getId().equals(sessionId));
}

public void sendMessageToClient(String clientId, String message) {
WebSocketSession session = clientSessions.get(clientId);
if (session != null && session.isOpen()) {
try {
session.sendMessage(new TextMessage(message));
} catch (Exception e) {
e.printStackTrace();
}
}
}

// 定义握手消息模型
private static class HandshakeMessage {
private String type;
private String clientId;
private String message;

// Getters and setters
}
}