依赖
<!--webSocket-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
配置类
开启websocket
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
业务实现
@ServerEndpoint(value = "/websocket/{userId}")
@Component
public class WebSocketServer {
// 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
private static ConcurrentHashMap<String, WebSocket> webSocketMap = new ConcurrentHashMap<>();
// 与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
// 接收参数
private String userId;
// 查询框数据
private String queryMsg;
// 获取在线数
public static synchronized int getOnlineCount() {
return onlineCount;
}
// 在线数加1
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
// 在线数减1
public static synchronized void subOnlineCount() {
WSVehicleRtdServer.onlineCount--;
}
public String getQueryMsg() {
return queryMsg;
}
public void setQueryMsg(String queryMsg) {
this.queryMsg = queryMsg;
}
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
this.session = session;
if (StringUtils.isBlank(userId)) {
sendMessage("userId不可以为空!");
close();
return;
}
if (this.isExist(userId)) {
sendMessage("用户:" + userId + ",已存在!");
log.error("用户{}已存在!", userId);
close();
return;
}
this.userId = userId;
//加入map
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
webSocketMap.put(userId, this);
} else {
webSocketMap.put(userId, this);
addOnlineCount();
}
log.info("用户{}连接成功,当前在线人数为:{}", userId, getOnlineCount());
sendMessage("连接成功!");
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
subOnlineCount();
}
log.info("用户{}退出,当前在线人数为:{}", this.userId, getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
logger.info("来自客户端用户:{} 消息:{}",userId, message);
if (StringUtils.isNotBlank(message)) {
this.session = session;
// 解析发送的报文
JSONObject jsonObject = JSON.parseObject(message);
String queryMsg = jsonObject.getString("queryMsg");
this.queryMsg = queryMsg;
if (webSocketMap.containsKey(this.userId)) {
webSocketMap.put(this.userId, this);
}
}
}
/**
* 发生错误时调用
*
* @OnError
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("用户{}错误,原因:{}", this.userId, error);
}
/**
* 服务器主动发送消息
*/
public void sendMessage(String message) throws IOException {
try {
if (this.session.isOpen()) {
synchronized (this.session) {
this.session.getBasicRemote().sendText(message);
}
}
} catch (IOException e) {
log.error("用户{}发送数据失败:{}", this.userId, e);
try {
this.session.close();
log.info("用户{}关闭连接成功", this.userId);
} catch (IOException e1) {
log.error("用户{}关闭连接失败:{}", this.userId, e);
}
}
}
/**
* 判断该连接是否存在
*
* @param userId
* @return
*/
public boolean isExist(String userId) {
if (webSocketMap.containsKey(userId))
return true;
return false;
}
/**
* 关闭当前连接
*/
public void close() {
try {
sendMessage("连接关闭!");
this.session.close();
} catch (IOException e) {
log.error("当前连接关闭失败: {}", e);
}
}
/**
* 通过userId向客户端发送消息
*/
public void sendMessageByUserId(String userId, String message) throws IOException {
log.info("发送消息到:" + userId + ",报文:" + message);
if (StringUtils.isNotBlank(userId) && webSocketMap.containsKey(userId)) {
webSocketMap.get(userId).sendMessage(message);
}else{
log.error("用户" + userId + ",不在线!");
}
}
/**
* 群发自定义消息
*/
public static void sendInfo(String message) throws IOException {
for (String item : webSocketMap.keySet()) {
try {
webSocketMap.get(item).sendMessage(message);
} catch (IOException e) {
continue;
}
}
}
}
vue调用
/**
* websocket封装方法
*/
let global_callback = null
let websock = null
// 初始化websocket
function initWebSocket(websocketUrl) {
// 申请一个WebSocket对象,参数是服务端地址,同http协议使用http://开头一样,WebSocket协议的url使用ws://开头,另外安全的WebSocket协议使用wss://开头
const wsuri = `ws://${process.env.WEBSOCKET_URL}${websocketUrl}`
websock = new WebSocket(wsuri)
// 当客户端收到服务端发来的消息时,触发onmessage事件,参数e.data包含server传递过来的数据
websock.onmessage = function(e) {
websocketonmessage(e)
}
// 当客户端收到服务端发送的关闭连接请求时,触发onclose事件
websock.onclose = function(e) {
websocketclose(e)
}
// 当WebSocket创建成功时,触发onopen事件
websock.onopen = function() {
websocketOpen()
}
// 连接发生错误的回调方法
websock.onerror = function() {
console.log('WebSocket连接发生错误')
}
}
// 实际调用的方法
function sendSock(agentData, callback) {
global_callback = callback
if (websock.readyState === websock.OPEN) {
// 若是ws开启状态
websocketsend(agentData)
} else if (websock.readyState === websock.CONNECTING) {
// 若是 正在开启状态,则等待1s后重新调用
setTimeout(function() {
sendSock(agentData, callback)
}, 1000)
} else {
// 若未开启 ,则等待1s后重新调用
setTimeout(function() {
sendSock(agentData, callback)
}, 1000)
}
}
// 数据接收
function websocketonmessage(e) {
if (isJSON(e.data)) {
global_callback && global_callback(JSON.parse(e.data))
window.dispatchEvent(new CustomEvent('onmessageWS', {
detail: { data: JSON.parse(e.data) }
}))
}
}
// 数据发送
function websocketsend(agentData) {
websock.send(JSON.stringify(agentData))
}
// 关闭
function websocketclose(e) {
console.log('connection closed (' + e.code + ')')
}
function websocketOpen(e) {
console.log('ws连接成功')
}
// 判断是否为json
function isJSON(str) {
if (typeof str === 'string') {
try {
const obj = JSON.parse(str)
return !!(typeof obj === 'object' && obj)
} catch (e) {
console.log('error:' + str + '!!!' + e)
return false
}
}
console.log('It is not a string!')
}
function closeSock() {
if (websock) {
websock.close()
}
}
export { sendSock, initWebSocket, closeSock }
常见异常
Caused by: java.lang.IllegalStateException: The remote endpoint was in state [TEXT_PARTIAL_WRITING] which is an invalid state for called method
是多个线程同时使用同一session发送导致的,也就是在高并发的情况下使用以下调用方法,没有加锁的情况下,会导致session在使用的情况下,被另一个线程调用。
解决方法:是在发送消息的时候加上一把锁,保证一个session在某个时刻不会被调用多次。
synchronized (session) {
this.session.getBasicRemote().sendText(message);
}