来认识一下WebSocket

前言

做测试工具的时候有个需求就是后台一直接收active mq传过来的通告信息,然后前台页面一直唰唰唰的动态显示后台传过来的信息。咋一看很简单,定睛一看,确实很简单,无非是看使用什么方式咯。

那么有那些方式呢?

  1. Ajax轮询,这个。。。不用解释吧。
  2. 长连接。
  3. 压轴出场的主角 - WebSocket

关于这几个的区别,我讲不好,推荐一篇别人转载的博文,正儿八经的是我见过的最通俗易懂的文章。

看完让你彻底搞懂Websocket原理

Java实现

废话不多说,直接上代码。

Js代码:

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
$(function () {
var websocket = null;

//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
var userId = $("#userId").val();
websocket = new WebSocket("ws://localhost:8000/websocket/" + userId);
}
else{
layer.msg("Not support websocket", {icon: 1,timeout:2000});
}

//连接发生错误的回调方法
websocket.onerror = function(){
layer.msg("WebSocket初始化失败,请重新刷新页面!", {icon: 5,timeout:2000});
};

//连接成功建立的回调方法
websocket.onopen = function(event){
layer.msg("WebSocket初始化成功!", {icon: 1,timeout:2000});
};

//接收到消息的回调方法
websocket.onmessage = function(event){
var obj = JSON.parse(event.data);
var messageType = obj.messageType;

// 1系统message,2自定义message
if(messageType == 1){
var onlineNumber = obj.onlineNumber;
$("#onlineNumber").text(onlineNumber);
} else if(messageType == 2){
var html = "";
var fileNames = obj.fileNames;
var arr = fileNames.split(",");
for(var i = 0; i < arr.length; ++i){
if(util.isEmpty(arr[i]))
continue;
html += "<tr>";
html += "<td>" + (i+1) + "</td>";
html += "<td>" + arr[i] + "</td>";
html += "<td><button type=\"button\" class=\"btn btn-success\">success</button></td>";
html += "<td><button type=\"button\" onclick='download(this)' class=\"btn btn-success\">download</button></td>";
html += "</tr>";
}
$("#fileNames").html(html);
} else if(messageType == 3){
var html = "<tr>";
html += "<td>1</td>";
html += "<td>" + obj.error + "</td>";
html += "<td><button type=\"button\" class=\"btn btn-danger\">error</button></td><td></td></tr>";
$("#fileNames").html(html);
}
};

//连接关闭的回调方法
websocket.onclose = function(){
layer.msg("WebSocket通道已关闭!", {icon: 1,timeout:2000});
};

//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
/*window.onbeforeunload = function(){
websocket.close();
};*/

//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onunload = function(){
websocket.close();
};
});

Java代码:(我用的是SpringBoot,所以可能有些注解是SpringBoot独有的)

WebSocket配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.lucent.demo.server;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
* @author zhangxingrui
* @create 2018-10-30 9:59
**/
@Configuration
public class WebSocketConfig {

@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}

}

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
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package com.lucent.demo.server;

import com.alibaba.fastjson.JSON;
import com.lucent.demo.config.MessageTypeEnum;
import com.lucent.demo.exception.MyException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* @author zhangxingrui
* @create 2018-10-30 11:15
**/
@Component
@ServerEndpoint(value = "/websocket/{userId}")
public class WebSocketServer {

private static final Logger logger = LoggerFactory.getLogger(WebSocketServer.class);

/**
* 在线人数。应该把它设计成线程安全的。
*/
public static int onlineNumber = 0;

/**
* 以用户的姓名为key,WebSocket为对象保存起来
*/
private static Map<String, WebSocketServer> clients = new ConcurrentHashMap<>();

/**
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
private Session session;

/**
* 用户ID
*/
private String userId;

@OnOpen
public void onOpen(@PathParam("userId") String userId, Session session) {
addOnlineCount();
logger.info("Current connection's userId: " + userId);
this.userId = userId;
this.session = session;
logger.info("New Connection!Current connections's count: " + onlineNumber);
try {
clients.put(userId, this);
sendOnlineNumber();
}
catch (Exception e){
logger.info(userId+" connection is error!" +
" Error message is: " + e.getMessage());
}
}

private void sendOnlineNumber(){
Map<String, String> map = new HashMap<>();
map.put("messageType", MessageTypeEnum.SYSTEM.getIndex());
map.put("onlineNumber", getOnlineCount());
sendMessageToAll(JSON.toJSONString(map));
}

@OnMessage
public void onMessage(String message, Session session) {
try {
sendMessageToAll(message);
}
catch (Exception e){
logger.info(e.getMessage());
}
}

@OnClose
public void onClose() {
subOnlineCount();
try {
clients.remove(userId);
sendOnlineNumber();
}
catch (Exception e){
logger.info(userId+" connection is error!" +
" Error message is: " + e.getMessage());
}
logger.info("One connection is closed! " +
"Current connections's count: " + getOnlineCount());
}

@OnError
public void onError(Session session, Throwable error) {
logger.info("Server error: "+error.getMessage());
}

public static void sendMessageToOne(String message, String userId) throws MyException {
if(clients.size() == 0){
throw new MyException("current connection's size is zero!");
}

boolean isOk = false;
for ( WebSocketServer item : clients.values()) {
if(item.userId.equalsIgnoreCase(userId)){
item.sendMessage(message);
isOk = true;
break;
}
}

if(!isOk){
throw new MyException("current connection's size is zero!");
}
}

public static void sendMessageToAll(String message) {
for (WebSocketServer item : clients.values()) {
item.sendMessage(message);
}
}

private void sendMessage(String message){
this.session.getAsyncRemote().sendText(message);
}

private static synchronized String getOnlineCount() {
return onlineNumber + "";
}

private static synchronized void addOnlineCount() {
WebSocketServer.onlineNumber++;
}

private static synchronized void subOnlineCount() {
WebSocketServer.onlineNumber--;
}

}

依赖(SpringBoot引用了这个依赖,就不用再引用spring-boot-starter和spring-boot-starter-web等)

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
zhangxingrui wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!