Spring Boot中的WebSocket

在Spring Boot中使用WebSocket的示例

Posted by Jeremy Song on 2023-03-03
Estimated Reading Time 5 Minutes
Words 1.2k In Total
Viewed Times

很多网站为了实现推送技术,所用的技术都是轮询。轮询是在特定的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。

在这种情况下,HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

简介

WebSocket是一种在单个TCP连接上进行全双工通信的协议。

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

特点

  • 较少的控制开销。
    相对于HTTP请求每次都要携带完整的头部,开销显著减少了。
  • 更强的实时性。
    由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。
  • 保持连接状态。
    Websocket需要先创建连接,是一种有状态的协议,之后通信时可以省略部分状态信息。而HTTP请求可能需要在每个请求都携带状态信息(如身份认证等)。
  • 更好的二进制支持。
    Websocket定义了二进制帧,相对HTTP,可以更轻松地处理二进制内容。
  • 可以支持扩展。
    Websocket定义了扩展,用户可以扩展协议、实现部分自定义的子协议。如部分浏览器支持压缩等。
  • 更好的压缩效果。
    相对于HTTP压缩,Websocket在适当的扩展支持下,可以沿用之前内容的上下文,在传递类似的数据时,可以显著地提高压缩率。

在SpringBoot项目中创建WebSocket Server

项目依赖(Maven)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.jeremysong</groupId>
<artifactId>demo</artifactId>
<version>1.0</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.3</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>3.0.3</version>
</dependency>
</dependencies>
</project>

WebSocket服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
* ServerEndpointExporter 作用
*
* 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
*
*/
@Component
public class WebSocketConfig {

@Bean
public ServerEndPointExporter serverEndPointExporter() {
return new ServerEndPointExporter();
}
}
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
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Component
@ServerEndpoint("/websocket/{name}")
public class WebSocket {

private Session session;

private String name;

private static ConcurrentHashMap<String, WebSocket> webSocketSet = new ConcurrentHashMap<>();

@OnOpen
public void onOpen(Session session, @PathParam(value = "name") String name) {
this.session = session;
this.name = name;
webSocketSet.put(name, this);
}

@OnClose
public void onClose() {
webSocketSet.remove(this.name);
}

@OnMessage
public void onMessage(String message) {
log.info("{} send {}", this.name, message);
}

/**
* 群发
* @param message 消息内容
*/
public void groupSending(String message) {
for (String name : webSocketSet.keySet()) {
try {
webSocketSet.get(name).session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}

/**
* 指定发动消息
* @param name 指定的客户端名
* @param message 消息内容
*/
public void appointSending(String name, String message) {
try {
webSocketSet.get(name).session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}

使用JavaScript创建WebSocket Client

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
let websocket = null;

if ('WebSocket' in window) {
websocket = new WebSocket('ws://localhost:8888/websocket/cli-1');

websocket.onopen = function () {
console.log('连接成功');
};
websocket.onclose = function () {
console.log('退出连接');
};
websocket.onmessage = function (event) {
console.log('收到消息:' + event.data);
};
websocket.onerror = function () {
console.log('连接出错');
};

// MDN Example
websocket.addEventListener('open', function (event) {
websocket.send('Hello Server!');
});
websocket.addEventListener('message', function (event) {
console.log('Message from server ', event.data);
});
websocket.addEventListener('error', function (event) {
console.log('WebScoket error: ', event);
});
}

window.onbeforeunload = function () {
// 页面关闭时关闭WebSocket连接
websocket.close(1000);
};

在Chrome console中测试

执行如下命令时可以在Server端添加日志输出和debug观察交互现象。

1
2
3
4
5
6
7
8
> ws1 = new WebSocket('ws://localhost:8888/websocket/name1');
> ws1.send('Send message to server! I am name1');

> ws2 = new WebSocket('ws://localhost:8888/websocket/name2');
> ws2.send('Send message to server! I am name2');

> ws1.close(1000);
> ws2.close(1000);

参考


欢迎关注我的公众号 须弥零一,跟我一起学习IT知识。


如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !