WebScoket定向发送

1. 概述

同样的接着之前的WebScoket,之前实现的是所有的接收者都能接收到,刚好最近写到了点对点通信的功能,就把WebScoket的点对点通信搞懂了。

2. 实现

2.1 关键

1
websocket = new WebSocket("ws://localhost:8080/api/websocket/100");

这里是关键的,这里实现了注册一个websocket的sid为100的,点对点通信主要就是靠这个唯一标识的sid进行通信的,所以我们需要实现不同的多个sid进行发送消息。

2.2 实现

由于需要使用的点对点通信,直接在发送的时候指定发送给谁就行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void sendMessage(String message) throws IOException {
// 构造JSON格式的消息
JSONObject jsonMessage = new JSONObject();
jsonMessage.put("sid", sid); // 包含发送者 ID
jsonMessage.put("msg", message); // 消息内容

String jsonString = jsonMessage.toString();
log.info("推送 {} 的信息: {}", sid, jsonString);
this.session.getBasicRemote().sendText(jsonString); // 推送JSON字符串
}
public static void sendInfo(String message, String targetUserId) throws IOException {
boolean isSent = false;
for (WebSocketServer item : webSocketSet) {
if (item.sid.equals(targetUserId)) {
item.sendMessage(message);
isSent = true;
break;
}
}
if (!isSent) {
log.warn("未找到目标用户 sid: {},消息未送达: {}", targetUserId, message);
}
}

3. 全部代码

WebSocketServer

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package com.example.scoket.util;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import jakarta.websocket.*;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;

@Component
@Slf4j
@ServerEndpoint(value = "/api/websocket/{sid}")
public class WebSocketServer {

private static final AtomicInteger onlineCount = new AtomicInteger(0);
private static final CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();

private Session session;
private String sid = "";

@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
this.session = session;
this.sid = sid.trim(); // 去除多余空格,确保一致性
webSocketSet.add(this);
this.sid = sid;
addOnlineCount();
try {
sendMessage("{\"msg\":\"conn_success\"}");
log.info("有新窗口开始监听: {},当前在线人数为: {}", sid, getOnlineCount());
} catch (IOException e) {
log.error("WebSocket IO Exception", e);
}
}

@OnClose
public void onClose() {
webSocketSet.remove(this);
subOnlineCount();
log.info("释放的 sid 为: {}", sid);
log.info("有一连接关闭!当前在线人数为 {}", getOnlineCount());
}

@OnMessage
public void onMessage(String message, Session session) throws IOException {
log.info("收到来自窗口 {} 的信息: {}", session.getId(), message);
try {
Map<String, String> requestMap = JSONUtil.toBean(message, Map.class);
if (requestMap != null && !requestMap.isEmpty()) {
String targetUserId = requestMap.get("to"); // 接收目标用户
String msg = requestMap.get("msg"); // 消息内容
if (targetUserId != null && msg != null) {
sendInfo(msg, targetUserId); // 发送给指定用户
} else {
log.warn("消息格式错误,缺少 'to' 或 'msg' 字段: {}", message);
}
}
} catch (Exception e) {
log.error("解析或处理消息时出错: {}", e.getMessage(), e);
}
}


@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误: " + error.getMessage(), error);
if (session.isOpen()) {
try {
session.close();
} catch (IOException e) {
log.error("关闭会话时出错: {}", e.getMessage(), e);
}
}
}

public void sendMessage(String message) throws IOException {
// 构造JSON格式的消息
JSONObject jsonMessage = new JSONObject();
jsonMessage.put("sid", sid); // 包含发送者 ID
jsonMessage.put("msg", message); // 消息内容

String jsonString = jsonMessage.toString();
log.info("推送 {} 的信息: {}", sid, jsonString);
this.session.getBasicRemote().sendText(jsonString); // 推送JSON字符串
}
public static void sendInfo(String message, String targetUserId) throws IOException {
boolean isSent = false;
for (WebSocketServer item : webSocketSet) {
if (item.sid.equals(targetUserId)) {
item.sendMessage(message);
isSent = true;
break;
}
}
if (!isSent) {
log.warn("未找到目标用户 sid: {},消息未送达: {}", targetUserId, message);
}
}

public static int getOnlineCount() {
return onlineCount.get();
}

public static void addOnlineCount() {
onlineCount.incrementAndGet();
}

public static void subOnlineCount() {
onlineCount.decrementAndGet();
}
}

WebSocketConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.example.scoket.util;


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();
}

}

5. 项目结构

image-20241125184045949

6. 页面结构

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket Example</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<div id="main" style="width: 1200px;"></div>
Welcome<br/>
<input id="text" type="text" />
<button onclick="send()">发送消息</button>
<hr/>
<button onclick="closeWebSocket()">关闭WebSocket连接</button>
<button onclick="openWebSocket()">开启WebSocket连接</button>
<hr/>
<div id="message"></div>

<script type="text/javascript">
var websocket = null;
var currentUserId = "2237023838";
var targetUserId = "123123";
// 判断当前浏览器是否支持WebSocket
function openWebSocket() {
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/api/websocket/"+currentUserId);

// 连接成功建立的回调方法
websocket.onopen = function() {
setMessageInnerHTML("WebSocket连接成功");
};

// 连接发生错误的回调方法
websocket.onerror = function() {
setMessageInnerHTML("WebSocket连接发生错误");
};

// 接收到消息的回调方法
websocket.onmessage = function(event) {
console.log(event.data);
try {
var data = JSON.parse(event.data); // 解析传入的 JSON
setMessageInnerHTML("接收:"+data.msg); // 显示 msg 字段
} catch (e) {
console.error("解析消息失败:", e);
}
};

// 连接关闭的回调方法
websocket.onclose = function() {
setMessageInnerHTML("WebSocket连接关闭");
};

} else {
alert('当前浏览器不支持 WebSocket');
}
}

// 将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}

// 关闭WebSocket连接
function closeWebSocket() {
if (websocket) {
websocket.close();
websocket = null; // 清空 websocket 变量
}
}

// 发送消息
function send() {
var message = document.getElementById('text').value;
if (message) {
var jsonMessage = JSON.stringify({
to: targetUserId, // 接收者的 ID
msg: message // 消息内容
});
if (websocket && websocket.readyState === WebSocket.OPEN) {
websocket.send(jsonMessage);
setMessageInnerHTML("发送: " + message);
document.getElementById('text').value = ''; // 清空输入框
} else {
alert("WebSocket连接未开启!");
}
} else {
alert("请输入消息!");
}
}
</script>
</body>
</html>

像这样再编写2个sid不一样的页面就可以,这里不做赘述

7. 实现截图

image-20241125184354968

可以看到只有t2作为sid:123123的接收到了消息

image-20241125184438214