Skip to content

Commit b124dec

Browse files
committed
feat: SpringBoot 整合 WebSocket 实现实时聊天演示。
1 parent d449b10 commit b124dec

File tree

10 files changed

+745
-0
lines changed

10 files changed

+745
-0
lines changed

springboot-websocket/.gitignore

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
HELP.md
2+
target/
3+
!.mvn/wrapper/maven-wrapper.jar
4+
!**/src/main/**
5+
!**/src/test/**
6+
7+
### STS ###
8+
.apt_generated
9+
.classpath
10+
.factorypath
11+
.project
12+
.settings
13+
.springBeans
14+
.sts4-cache
15+
16+
### IntelliJ IDEA ###
17+
.idea
18+
*.iws
19+
*.iml
20+
*.ipr
21+
22+
### NetBeans ###
23+
/nbproject/private/
24+
/nbbuild/
25+
/dist/
26+
/nbdist/
27+
/.nb-gradle/
28+
build/
29+
30+
### VS Code ###
31+
.vscode/

springboot-websocket/README.md

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
# 工程简介
2+
1. websocket 原理图<br/>
3+
![websocket实时聊天原理](https://img2020.cnblogs.com/blog/1721320/202003/1721320-20200319084859071-1530751486.png)
4+
5+
6+
# 搭建步骤
7+
## 一、maven 依赖
8+
```xml
9+
<dependencies>
10+
<dependency>
11+
<groupId>org.springframework.boot</groupId>
12+
<artifactId>spring-boot-starter-web</artifactId>
13+
</dependency>
14+
15+
<dependency>
16+
<groupId>org.springframework.boot</groupId>
17+
<artifactId>spring-boot-starter-test</artifactId>
18+
<scope>test</scope>
19+
</dependency>
20+
<dependency>
21+
<groupId>org.springframework.boot</groupId>
22+
<artifactId>spring-boot-starter-thymeleaf</artifactId>
23+
</dependency>
24+
<!-- 核心依赖 springboot-websocket -->
25+
<dependency>
26+
<groupId>org.springframework.boot</groupId>
27+
<artifactId>spring-boot-starter-websocket</artifactId>
28+
</dependency>
29+
<dependency>
30+
<groupId>com.alibaba</groupId>
31+
<artifactId>fastjson</artifactId>
32+
<version>1.2.46</version>
33+
</dependency>
34+
</dependencies>
35+
```
36+
37+
## 二、application.properties 配置
38+
```properties
39+
# 应用名称
40+
spring.application.name=springboot-websocket
41+
# 应用服务 WEB 访问端口
42+
server.port=8080
43+
# THYMELEAF (ThymeleafAutoConfiguration)
44+
# 开启模板缓存(默认值: true )
45+
spring.thymeleaf.cache=true
46+
# 检查模板是否存在,然后再呈现
47+
spring.thymeleaf.check-template=true
48+
# 检查模板位置是否正确(默认值 :true )
49+
spring.thymeleaf.check-template-location=true
50+
#Content-Type 的值(默认值: text/html )
51+
spring.thymeleaf.content-type=text/html
52+
# 开启 MVC Thymeleaf 视图解析(默认值: true )
53+
spring.thymeleaf.enabled=true
54+
# 模板编码
55+
spring.thymeleaf.encoding=UTF-8
56+
# 要被排除在解析之外的视图名称列表,⽤逗号分隔
57+
spring.thymeleaf.excluded-view-names=
58+
# 要运⽤于模板之上的模板模式。另⻅ StandardTemplate-ModeHandlers( 默认值: HTML5)
59+
spring.thymeleaf.mode=HTML5
60+
# 在构建 URL 时添加到视图名称前的前缀(默认值: classpath:/templates/ )
61+
spring.thymeleaf.prefix=classpath:/templates/
62+
# 在构建 URL 时添加到视图名称后的后缀(默认值: .html )
63+
spring.thymeleaf.suffix=.html
64+
```
65+
66+
## 三、websocket 配置(`config/WebSocketConfig.java`
67+
```java
68+
import org.springframework.context.annotation.Bean;
69+
import org.springframework.context.annotation.Configuration;
70+
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
71+
72+
@Configuration
73+
public class WebSocketConfig {
74+
75+
/**
76+
* ServerEndpointExporter 作用 <p>
77+
* 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
78+
*
79+
* @return 服务轮询
80+
*/
81+
@Bean
82+
public ServerEndpointExporter serverEndpointExporter() {
83+
return new ServerEndpointExporter();
84+
}
85+
}
86+
```
87+
88+
## 四、核心:`WebSocketServer.java`
89+
```java
90+
import javax.websocket.*;
91+
import javax.websocket.server.PathParam;
92+
import javax.websocket.server.ServerEndpoint;
93+
import java.io.IOException;
94+
import java.util.concurrent.ConcurrentHashMap;
95+
import java.util.concurrent.ConcurrentMap;
96+
import java.util.concurrent.atomic.AtomicInteger;
97+
98+
@Component
99+
@ServerEndpoint(value = "/webSocket/{sid}")
100+
public class WebSocketServer {
101+
102+
private static AtomicInteger onlineNum = new AtomicInteger();
103+
104+
private static ConcurrentMap<String, Session> sessionPools = new ConcurrentHashMap<>();
105+
106+
/**
107+
* 发送消息主题
108+
*
109+
* @param session 会话对象
110+
* @param message 消息内容
111+
* @throws IOException
112+
*/
113+
public void sendMessage(Session session, String message) throws IOException {
114+
if (session != null) {
115+
synchronized (session) {
116+
System.out.println("发送数据:" + message);
117+
session.getBasicRemote().sendText(message);
118+
}
119+
}
120+
}
121+
122+
/**
123+
* 给指定用户发送信息
124+
*
125+
* @param userName 用户名
126+
* @param message 信息内容
127+
*/
128+
public void sendInfo(String userName, String message) {
129+
Session session = sessionPools.get(userName);
130+
try {
131+
sendMessage(session, message);
132+
} catch (Exception e) {
133+
e.printStackTrace();
134+
}
135+
}
136+
137+
/**
138+
* 建立连接成功调用
139+
*
140+
* @param session 会话对象
141+
* @param userName 消息内容
142+
*/
143+
@OnOpen
144+
public void onOpen(Session session, @PathParam(value = "sid") String userName) {
145+
sessionPools.put(userName, session);
146+
addOnlineCount();
147+
System.out.println(userName + "加入webSocket!当前人数为" + onlineNum);
148+
try {
149+
sendMessage(session, "欢迎" + userName + "加入连接!");
150+
} catch (IOException e) {
151+
e.printStackTrace();
152+
}
153+
}
154+
155+
/**
156+
* 关闭连接时调用
157+
*
158+
* @param userName 用户名
159+
*/
160+
@OnClose
161+
public void onClose(@PathParam(value = "sid") String userName) {
162+
sessionPools.remove(userName);
163+
subOnlineCount();
164+
System.out.println(userName + "断开webSocket连接!当前人数为" + onlineNum);
165+
}
166+
167+
/**
168+
* 收到客户端信息
169+
*
170+
* @param message 消息内容
171+
* @throws IOException
172+
*/
173+
@OnMessage
174+
public void onMessage(String message) throws IOException {
175+
message = "客户端:" + message + ",已收到";
176+
System.out.println(message);
177+
for (Session session : sessionPools.values()) {
178+
try {
179+
sendMessage(session, message);
180+
} catch (Exception e) {
181+
e.printStackTrace();
182+
continue;
183+
}
184+
}
185+
}
186+
187+
/**
188+
* 错误时调用
189+
*
190+
* @param session 会话对象
191+
* @param throwable 异常信息
192+
*/
193+
@OnError
194+
public void onError(Session session, Throwable throwable) {
195+
System.out.println("发生错误");
196+
throwable.printStackTrace();
197+
}
198+
199+
public static void addOnlineCount() {
200+
onlineNum.incrementAndGet();
201+
}
202+
203+
public static void subOnlineCount() {
204+
onlineNum.decrementAndGet();
205+
}
206+
}
207+
```
208+
209+
## 五、实时聊天页面:`webSocket.html`
210+
```html
211+
<!DOCTYPE html>
212+
<html lang="en">
213+
<head>
214+
<meta charset="UTF-8">
215+
<title>WebSocket</title>
216+
</head>
217+
<body>
218+
<h3>hello socket</h3>
219+
<p>【userId】:<input id="userId" name="userId" type="text" value="10">
220+
<p>【toUserId】:<input id="toUserId" name="toUserId" type="text" value="20">
221+
<p>【toUserId】:<input id="contentText" name="contentText" type="text" value="hello websocket">
222+
<p>【操作】:<button onclick="openSocket()">开启socket</button><br><button onclick="sendMessage()">发送消息</button>
223+
</body>
224+
<script>
225+
var socket;
226+
227+
/**
228+
* 打开 socket 来接
229+
*/
230+
function openSocket() {
231+
if (typeof (WebSocket) == "undefined") {
232+
console.log("您的浏览器不支持WebSocket");
233+
} else {
234+
console.log("您的浏览器支持WebSocket");
235+
//实现化WebSocket对象,指定要连接的服务器地址与端口 建立连接
236+
var userId = document.getElementById('userId').value;
237+
var socketUrl = "ws://localhost:8080/webSocket/" + userId;
238+
console.log(socketUrl);
239+
if (socket != null) {
240+
socket.close();
241+
socket = null;
242+
}
243+
socket = new WebSocket(socketUrl);
244+
//打开事件
245+
socket.onopen = function () {
246+
console.log("websocket已打开");
247+
//socket.send("这是来自客户端的消息" + location.href + new Date());
248+
};
249+
//获得消息事件
250+
socket.onmessage = function (msg) {
251+
var serverMsg = "收到服务端信息:" + msg.data;
252+
console.log(serverMsg);
253+
//发现消息进入 开始处理前端触发逻辑
254+
};
255+
//关闭事件
256+
socket.onclose = function () {
257+
console.log("websocket已关闭");
258+
};
259+
//发生了错误事件
260+
socket.onerror = function () {
261+
console.log("websocket发生了错误");
262+
}
263+
}
264+
}
265+
266+
/**
267+
* 发送消息主题
268+
*/
269+
function sendMessage() {
270+
if (typeof (WebSocket) == "undefined") {
271+
console.log("您的浏览器不支持WebSocket");
272+
} else {
273+
// console.log("您的浏览器支持WebSocket");
274+
var toUserId = document.getElementById('toUserId').value;
275+
var contentText = document.getElementById('contentText').value;
276+
var msg = '{"toUserId":"' + toUserId + '","contentText":"' + contentText + '"}';
277+
console.log(msg);
278+
socket.send(msg);
279+
}
280+
}
281+
</script>
282+
</html>
283+
```
284+
285+
## 六、测试
286+
1. 打开浏览器,输入地址:`http://localhost:8080/webSocket`;
287+
2. 点击 `开启socket`;控制台得到如下信息:
288+
```text
289+
10加入webSocket!当前人数为1
290+
发送数据:欢迎10加入连接!
291+
```
292+
3. 点击 `发送消息`,返回到服务控制台,得到用户输入的信息;
293+
```text
294+
客户端:{"toUserId":"20","contentText":"hello websocket"},已收到
295+
发送数据:客户端:{"toUserId":"20","contentText":"hello websocket"},已收到
296+
```
297+
4. 再次打开一个浏览器标签页,`重复前三步`操作,即可再添加一个用户进入到 `websocket` 并独立与`websocket服务器`消息互动;
298+
```text
299+
10加入webSocket!当前人数为2
300+
发送数据:欢迎10加入连接!
301+
```
302+
5. 关闭连接(即 关闭浏览器当前标签页);服务器将得到如下信息:
303+
```text
304+
10断开webSocket连接!当前人数为1
305+
```
306+
6. 关闭服务器;(即到此整个所有的实时聊天窗口将会结束。)
307+
308+
# 延伸阅读
309+

0 commit comments

Comments
 (0)