使用websocket踩到的坑,贴出来,大家有需要可以借鉴一下,如有问题欢迎指正~~~~
1.websocket主要是服务器主动向客户端推送消息,与客户端保持长连接,当然前提是客户端不刷新页面,否则无意义
2.使用websocket
(1)前端
<script type="text/javascript">
var webSocket = null;
// 创建 webSocket 实例 wss。 http:-ws
var wsProtocol = "wss";
var schema = "<%=scheme%>";
if (schema == "https") {
wsProtocol = "wss";
} else {
wsProtocol = "ws";
}
var url = wsProtocol + "://<%=basePath %>/addressFileWebSocket?addressFile";
if ('WebSocket' in window) {
webSocket = new WebSocket(url);//启动open方法,开始建立连接
} else if ('MozWebSocket' in window) {
webSocket = new MozWebSocket(url);
} else {
}
webSocket.onopen = onOpen;
webSocket.onmessage = onMessage;
webSocket.onerror = onError;
webSocket.onclose = onClose;
function onOpen(openEvt) {
console.log("webSocket连接成功...");
}
function onMessage(evt) {
var data = evt.data;
console.log("webSocket收到消息服务器发送的消息==>" + data);
if(data != ''){
var fileId = data.split("-")[0];
var time = data.split("-")[1];
window.parent.layer.alert('温馨提示:成功处理!', function (index) {
window.parent.layer.close(index);
});
doSend(time, "close");
window.location.reload(true);//刷新页面
}
}
function onError() {
console.log("webSocket连接错误...");
}
function onClose(e) {
console.log("webSocket连接关闭...");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
webSocket.close();
};
// flag=open 请求回调状态,flag=close 请求关闭连接
function doSend(time, flag) {
if (webSocket.readyState == webSocket.OPEN) {
webSocket.send(time + "-" + flag);
console.log("发送" + time + "成功!");
} else {
console.log("连接" + time + "失败!");
}
}
window.close = function (e) {
webSocket.onclose();
}
</script>
(2)后台
装信息的并发容器
public class MyWebSocketMap {
private static ConcurrentMap<String, MyWebSocket> myWebSocketConcurrentHashMap = new ConcurrentHashMap<String, MyWebSocket>();
public static void put(String key, MyWebSocket myWebSocket) {
myWebSocketConcurrentHashMap.put(key, myWebSocket);
}
public static MyWebSocket get(String key) {
return myWebSocketConcurrentHashMap.get(key);
}
public static void remove(String key) {
myWebSocketConcurrentHashMap.remove(key);
}
public static Collection<MyWebSocket> getValues() {
return myWebSocketConcurrentHashMap.values();
}
public static boolean containsKey(String key) {
return myWebSocketConcurrentHashMap.containsKey(key);
}
public static List<String> getAllKey() {
List<String> allKey = new ArrayList<String>();
for (String key : myWebSocketConcurrentHashMap.keySet()) {
allKey.add(key);
}
return allKey;
}
}
MyWebSocket用于通信
@ServerEndpoint(value = "/addressFileWebSocket",configurator = WebSocketConfig.class)
public class MyWebSocket implements Serializable {
protected Logger logger = LoggerFactory.getLogger(getClass());
//private static MemcachedUtils memcachedUtils = MemcachedUtils.UECACHE;
// 与客户端的链接会话,通过它实现定向推送,比如某个用户
private Session session;
//线程安全的静态变量,表示在线连接数
private static volatile int onlineCount = 0;
// 与多个客户端通信,比如聊天室,通知多个用户
//public static CopyOnWriteArraySet<DataPlanWebSocket> webSocketSet = new CopyOnWriteArraySet<DataPlanWebSocket>();
ApplicationContext act = SpringUtil.getApplicationContext();
TDmAddressFileService addressFileService = act.getBean(TDmAddressFileService.class);
/**
* <p>打开列表界面:初始化连接成功调用的方法</p>
*
* @param session 可选的参数
* @throws Exception
*/
@OnOpen
public void onOpen(Session session) throws Exception {
this.session = session;
// 记录页面来源:列表
String query = this.session.getQueryString();
String sessionId = this.session.getId();
MyWebSocketMap.put(query, this);// 放到缓存中
addOnlineCount(); // 线程数+1
logger.info(String.format("%s页面,新连接加入,sessionId:%s,当前在线人数:%s", getPageDesc(query), sessionId, getOnlineCount()));
//webSocketSet.add(this);
}
/**
* <p>连接关闭调用的方法</p>
*
* @throws Exception
*/
@OnClose
public void onClose() throws Exception {
// 从map中删除
MyWebSocketMap.remove(session.getQueryString());
subOnlineCount(); // 在线数-1
logger.info("有一个链接关闭,剩余在线人数:" + getOnlineCount());
// webSocketSet.remove(this);
}
/**
* <p>收到客户端消息后调用的方法</p>
*
* @param message 客户端发送过来的消息,time :20190703
* @param session 可选的参数
* @throws java.io.IOException
*/
@OnMessage
public void onMessage(String message, Session session) throws IOException {
logger.info("收到客户端webSocket请求,time=>" + message);
//synchronized (session) {
if (StringUtils.isNotEmpty(message) && message.indexOf("-") > 0) {
String time = message.split("-")[0];
String flag = message.split("-")[1];
if ("close".equals(flag)) { // 前端关闭时,主动清理连接,不推送消息
System.out.println(" 前端关闭时,主动清理连接,不推送消息");
if (MyWebSocketMap.containsKey(time)) {
MyWebSocketMap.remove(time);
}
} else {
MyWebSocketMap.put(time, this);
//由于使用websocket,需要提前获取登录用户信息,不然再service中获取不到
User user = (User) session.getUserProperties().get("user");
//调用service,进行相应的处理,,,,
addressFileService.createAddressFile(user);
}
}
//}
}
/**
* <p>发生错误时调用</p>
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
subOnlineCount(); // 在线数-1
logger.error(String.format("发送错误异常,剩余在线人数:%s,异常:%s", getOnlineCount(), Exceptions.getStackTraceAsString(error)));
}
/**
* <p>服务器给客户端 发送消息方法。</p>
*
* @param message 消息内容
* @throws java.io.IOException
*/
public void sendMessage(String message) throws IOException {
/**
* getAsyncRemote 非阻塞
* getBasicRemote 阻塞,并且第二个参数是一次发送消息中的部分消息
*/
synchronized (this) {
try {
页面刷新 但是找的不是之前的session
if (this.session.isOpen()) { // 判断链接是否关闭
// 异步时,tomcat可能有bug,引起TEXT_FULL_WRITING
//this.session.getAsyncRemote().sendText(message);
this.session.getBasicRemote().sendText(message);
System.out.println("-------------------------给客户端发送消息--------------");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
MyWebSocket.onlineCount++;
}
public static synchronized void subOnlineCount() {
if (onlineCount != 0) { // 防止减到负数
MyWebSocket.onlineCount--;
}
}
private String getPageDesc(String query) {
if (StringUtils.isEmpty(query)) {
return "未知";
}else {
return "地址库文件列表";
}
}
}
3.下面聊聊遇到的坑儿:
websocket服务器收到客户端的请求后,需要对数据库进行操作,需要获取到service对象(坑一)和shiro用户信息(坑二)
(1)为了获取到service对象,需要定义一个工具类
package com.datang.dmp.modules.passback.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
/**
* Created by on 2019/7/5.
*
*/
@Lazy(false)
@Component
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext = null;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(SpringUtil.applicationContext == null){
SpringUtil.applicationContext = applicationContext;
}
}
//获取applicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
//通过name获取 Bean.
public static Object getBean(String name){
return getApplicationContext().getBean(name);
}
//通过class获取Bean.
public static <T> T getBean(Class<T> clazz){
return getApplicationContext().getBean(clazz);
}
//通过name,以及Clazz返回指定的Bean
public static <T> T getBean(String name,Class<T> clazz){
return getApplicationContext().getBean(name, clazz);
}
}
(2)获取到shiro里面的当前登录用户信息
package com.datang.dmp.modules.passback.web;
import com.datang.dmp.modules.sys.utils.UserUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
/**
*
* Created by on 2019/7/5.
*/
@Configuration
public class WebSocketConfig extends ServerEndpointConfig.Configurator{
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
// 将用户信息存储到socket的配置里
sec.getUserProperties().put("user", UserUtils.getUser());
super.modifyHandshake(sec, request, response);
}
}