1.背景
应用场景为前端需要实时获取后端数据。之前的做法是JS定时器发送Ajax请求,获取后端数据,这样的话需要一直请求后端,有点浪费资源。
使用websocket可以实现后端向前端推送消息,实现双工通信。
2.实现
2.1 pom依赖
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
2.2 JAVA 服务端
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import com.alibaba.fastjson.JSONObject;
/**
*
* @ClassName: TestSocket
* @Description: Websocket服务,推送信息
* @author: luchenxi
* @date: 2021年4月7日 上午11:57:06
* @Copyright:
*/
@ServerEndpoint("/test/{socketParam}")
public class TestSocket {
private Logger logger = LogManager.getLogger(TestSocket.class);
// 客户端统计
private static int onlineCount = 0;
// 客户端回话缓存
private static Map<String, TestSocket> clients = new ConcurrentHashMap<String, TestSocket>();
// 会话
private Session session;
// uid
private String uid;
/**
*
* @Title: onOpen
* @Description: 有连接时的触发函数
* @param: @param socketParam
* @param: @param session
* @param: @throws IOException
* @return: void
* @throws
*/
@OnOpen
public void onOpen(@PathParam("socketParam")
String socketParam, Session session) throws IOException {
logger.info(socketParam);
// session
this.session = session;
// 解析Json参数
JSONObject param = JSONObject.parseObject(socketParam);
this.uid = param.getString("uid");
// 业务逻辑
// ..............
// ..............
// 客户端数量+1
addOnlineCount();
// 缓存回话
clients.put(this.uid, this);
}
/**
*
* @Title: onClose
* @Description: 连接关闭时的调用方法
* @param: @throws IOException
* @return: void
* @throws
*/
@OnClose
public void onClose() throws IOException {
// 移除回话
clients.remove(this.uid);
// 数量-1
subOnlineCount();
}
/**
*
* @Title: onMessage
* @Description: 收到消息时调用的函数,其中Session是每个websocket特有的数据成员
* @param: @param message
* @param: @throws IOException
* @return: void
* @throws
*/
@OnMessage
public void onMessage(String message) throws IOException {
logger.info("Client : " + message);
// 解析Json参数
JSONObject param = JSONObject.parseObject(message);
// 业务逻辑
// ..............
// ..............
}
/**
*
* @Title: onError
* @Description: 发生意外错误时调用的函数
* @param: @param session
* @param: @param error
* @return: void
* @throws
*/
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
}
/**
*
* @Title: sendMessageTo
* @Description: 向客户端推送消息,可以实现客户端A-》B发消息【类似于聊天室】,回话缓存的key再加上AppId,还能实现同账号多客户端登录收消息等。
* @param: @param message
* @param: @param To
* @param: @throws IOException
* @return: void
* @throws
*/
public void sendMessageTo(String message, String client) throws IOException {
for (TestSocket socket : clients.values()) {
if (socket.getKey().equals(client))
socket.session.getAsyncRemote().sendText(message);
}
}
/**
*
* @Title: getOnlineCount
* @Description: 统计当前连接数
* @param: @return
* @return: int
* @throws
*/
public static synchronized int getOnlineCount() {
return onlineCount;
}
/**
*
* @Title: addOnlineCount
* @Description: 新增客户端数量
* @param:
* @return: void
* @throws
*/
public static synchronized void addOnlineCount() {
onlineCount++;
}
/**
*
* @Title: subOnlineCount
* @Description: 客户端数量减少
* @param:
* @return: void
* @throws
*/
public static synchronized void subOnlineCount() {
onlineCount--;
}
/**
*
* @Title: getClients
* @Description: 获取所有客户端
* @param: @return
* @return: Map<String,WebSocket>
* @throws
*/
public static synchronized Map<String, TestSocket> getClients() {
return clients;
}
}
@ServerEndpoint来进行声明接口:@ServerEndpoint(value="/websocket/{paraName}"),Spring中注意包扫描;
“ { } ”用来表示带参数的连接,如果需要获取{}中的参数在参数列表中增加:@PathParam(“paraName”),要实现多参数的可以就向上面一样用json做。
和tomcat用的同一个端口,TCP通信,无需新开端口!!!!
2.3 HTML 前端
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
websocket Demo <br />
<input id="text" type="text" />
<button onclick="send()"> Send </button>
<button onclick="closeWebSocket()"> Close </button>
<button onclick="recv()"> 后台开始推送 </button>
<br>
</body>
<script type="text/javascript">
//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
// JSON 参数封装
var scoketParam = {
pid:'xx215',
uid:'vis002'
};
websocket = new WebSocket("ws://localhost:8080/fpiiclouddata/test/{"+JSON.stringify(scoketParam)+"}");
}else{
alert('对不起,您的浏览器不支持webSocket')
}
//连接发生错误的回调方法
websocket.onerror = function(){
alert("websocket 连接异常");
};
//连接成功建立的回调方法
websocket.onopen = function(event){
alert("websocket 连接已建立");
}
//接收到消息的回调方法
websocket.onmessage = function(event){
alert(event.data);
}
//连接关闭的回调方法
websocket.onclose = function(){
alert("websocket 连接已关闭");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function(){
websocket.close();
}
//关闭连接
function closeWebSocket(){
websocket.close();
}
//给vis001发送消息
function send(){
var message = document.getElementById('text').value;
var scoketParam = {
target: 'vis001',
message: message
};
websocket.send(JSON.stringify(scoketParam));
}
// 通知后台开始推送消息
function recv(){
var scoketParam = {
target: 'self',
};
websocket.send(JSON.stringify(scoketParam));
}
</script>
</html>
3.总结
实现难度不高,关键在于优化性能。要注意后端多线程下缓存的处理。