使用websocket踩到的坑,贴出来,大家有需要可以借鉴一下,如有问题欢迎指正~~~~
1.websocket主要是服务器主动向客户端推送消息,与客户端保持长连接,当然前提是客户端不刷新页面,否则无意义
2.使用websocket
(1)前端
(2)后台
装信息的并发容器
public class MyWebSocketMap {
private static ConcurrentMap myWebSocketConcurrentHashMap = new ConcurrentHashMap();
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 getValues() {
return myWebSocketConcurrentHashMap.values();
}
public static boolean containsKey(String key) {
return myWebSocketConcurrentHashMap.containsKey(key);
}
public static List getAllKey() {
List allKey = new ArrayList();
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 webSocketSet = new CopyOnWriteArraySet();
ApplicationContext act = SpringUtil.getApplicationContext();
TDmAddressFileService addressFileService = act.getBean(TDmAddressFileService.class);
/**
* 打开列表界面:初始化连接成功调用的方法
*
* @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);
}
/**
* 连接关闭调用的方法
*
* @throws Exception
*/
@OnClose
public void onClose() throws Exception {
// 从map中删除
MyWebSocketMap.remove(session.getQueryString());
subOnlineCount(); // 在线数-1
logger.info("有一个链接关闭,剩余在线人数:" + getOnlineCount());
// webSocketSet.remove(this);
}
/**
* 收到客户端消息后调用的方法
*
* @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);
}
}
//}
}
/**
* 发生错误时调用
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
subOnlineCount(); // 在线数-1
logger.error(String.format("发送错误异常,剩余在线人数:%s,异常:%s", getOnlineCount(), Exceptions.getStackTraceAsString(error)));
}
/**
* 服务器给客户端 发送消息方法。
*
* @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 getBean(Class clazz){
return getApplicationContext().getBean(clazz);
}
//通过name,以及Clazz返回指定的Bean
public static T getBean(String name,Class 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);
}
}