# SpringBoot WebSocket
背景:http协议只能由客户端发起请求,服务端被动接收,所以websocket来解决了,它实现了浏览器与服务器全双工通信,服务端可以主动向客户端发送数据
# 使用
# 1. 引入jar包
<!--websocket-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
1
2
3
4
5
2
3
4
5
# 2. 简单配置下,开启支持
@Configuration
public class WebSocketConfig {
/** 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint */
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 3. WebSocketServer
/**
* WebSocketServer 核心类 相当于一个ws协议的Controller
*
* @author wbbaijq
*/
@ServerEndpoint("/websocket/{sid}")
@Component
public class WebSocketServer {
static Logger log = LoggerFactory.getLogger(WebSocketServer.class);
/**
* 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的
*/
private static AtomicInteger onlineNum = new AtomicInteger(0);
/**
* 线程安全,用来存放每个客户端对应的 WebSocketServer 对象。
*/
private static ConcurrentHashMap<String, WebSocketServer> webSocketServerPools = new ConcurrentHashMap<>();
/**
* 某个客户端的连接会话,需要通过它来给客户端发送数据
*/
private Session session;
/**
* 接收sid
*/
private String sid;
/**
* 建立连接成功调用
*/
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
this.session = session;
this.sid = sid;
//放到pools中
if (webSocketServerPools.containsKey(sid)) {
webSocketServerPools.remove(sid);
webSocketServerPools.put(sid, this);
} else {
webSocketServerPools.put(sid, this);
//在线数加一
addOnlineCount();
}
log.info(sid + "加入webSocket!当前人数为" + onlineNum);
try {
sendMessage("欢迎" + sid + "加入连接!");
} catch (IOException e) {
log.error("用户" + sid + "WebScoket IO异常(网络):" + e.getMessage());
}
}
/**
* 关闭连接时调用
*/
@OnClose
public void onClose() {
if (webSocketServerPools.containsKey(this.sid)) {
//从map中移除
webSocketServerPools.remove(this.sid);
//在线数减一
subOnlineCount();
}
log.info(sid + "断开webSocket连接!当前人数为" + onlineNum);
}
/**
* 收到客户端信息
*
* @param message 客户端发过来的消息
*/
@OnMessage
public void onMessage(String message) {
log.info("收到来自" + sid + "的信息:" + message);
//群发消息
for (WebSocketServer item : webSocketServerPools.values()) {
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 错误时调用
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误, 原因:" + error.getMessage());
error.printStackTrace();
}
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 群发自定义消息
*/
public static void sendInfo(String message) {
log.info("推送消息到窗口,内容:" + message);
for (WebSocketServer item : webSocketServerPools.values()) {
try {
item.sendMessage(message);
} catch (IOException e) {
continue;
}
}
}
public static void addOnlineCount() {
onlineNum.incrementAndGet();
}
public static void subOnlineCount() {
onlineNum.decrementAndGet();
}
}
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
119
120
121
122
123
124
125
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
119
120
121
122
123
124
125
# 4. controller
@Controller
public class WebSocketController {
@GetMapping("/socket/{cid}")
public ModelAndView socket(@PathVariable String cid) {
ModelAndView mv = new ModelAndView("/socket");
mv.addObject("cid", cid);
return mv;
}
@ResponseBody
@RequestMapping("/socket/push/{cid}")
public String pushToWeb(@PathVariable String cid, String message) {
try {
WebSocketServer.sendInfo(message);
} catch (Exception e) {
e.printStackTrace();
return "发送失败";
}
return "发送成功";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 5. 页面
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>websocket通讯</title>
</head>
<body>
<p><input id="text" type="text" /></p>
<p><button onclick="send()">Send</button></p>
<p><button onclick="closeWebSocket()">Close</button></p>
<div id="message"></div>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
var websocket;
//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
websocket = new WebSocket("ws://localhost:9001/websocket/9527");
}
else{
alert('Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function(){
setMessageInnerHTML("error");
};
//连接成功建立的回调方法
websocket.onopen = function(event){
setMessageInnerHTML("open");
}
//接收到消息的回调方法
websocket.onmessage = function(event){
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function(){
setMessageInnerHTML("close");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function(){
websocket.close();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//关闭连接
function closeWebSocket(){
websocket.close();
}
//发送消息
function send(){
var message = document.getElementById('text').value;
websocket.send(message);
}
</script>
</body>
</html>
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
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
← 整合定时任务 SpringBoot →