websocket
一、概念原理
 HTTP1.0 :一个 Request 一个 Response ,这次HTTP请求就结束了;
HTTP1.1中进行了改进,使得有一个keep-alive,也就是说,在一个HTTP连接中,可以发送多个Request,接收多个Response。
Websocket是一个持久化的协议,一次连接,永远有效,服务端就可以主动推送信息给客户端。
二、配置
@Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { System.out.println("开启websocket支持。。。。。"); return new ServerEndpointExporter(); } }
三、服务器类
前端请求URL:
ws://192.22.0.255:9999/person/websocket/alarm
@ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端,
即 @ServerEndpoint 可以把当前类变成websocket服务类
//访问服务端的url地址 @ServerEndpoint(value = "/websocket/alarm") @Component public class SocketServer { private Session session; //与某个客户端的连接会话,需要通过它来给客户端发送数据 private static CopyOnWriteArraySet<WebSocket> webSocketSet = new CopyOnWriteArraySet<>(); private static CopyOnWriteMap<String,CopyOnWriteArraySet<WebSocket>> webSocketMap = new CopyOnWriteMap<>(); RedisUtils redisUtils =SpringInitConfig.getBean("redisUtils"); //注入 /** * 连接成功后调用的方法 * @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据 */ @OnOpen public void onOpen(Session session) { this.session = session; webSocketSet.add(this); log.info("【websocket消息】有新的连接, 总数:{}", webSocketSet.size()); } @OnClose //连接关闭调用方法 public void onClose() { webSocketSet.remove(this); log.info("【websocket消息】连接断开, 总数:{}", webSocketSet.size()); } @OnMessage //接收到客户短消息 public void onMessage(String message) { log.info("【websocket消息】收到客户端发来的消息:{}", message); OperateVo vo = new Gson().fromJson(message, OperateVo.class); if(OperateTypeConstants.AUTH.equals(vo.getOperateType())){ if(webSocketMap.containsKey(vo.getSysid())){ webSocketMap.get(vo.getSysid()).add(this); }else { CopyOnWriteArraySet<WebSocket> socketSessions = new CopyOnWriteArraySet<>(); socketSessions.add(this); webSocketMap.put(vo.getSysid(),socketSessions); } //建立连接成功后,推送初始化告警数据 //卡寻呼之外告警 this.sendPepoleAlarm(this,"init",1); //卡寻呼告警 this.sendCardCallAlarm(this,"init"); } } /** * 人员系统告警推送 * @param alarmMessage */ public void alarmBroadcast(WebSocket webSocket, String alarmMessage) { try { if(webSocket !=null){ if (!webSocket.session.isOpen()) { } else { try { webSocket.session.getBasicRemote().sendText(alarmMessage); } catch (IOException e) { webSocket.session.close(); log.info("关闭websockt链接 wss:"+ session.toString()); } } }else{ for (WebSocket webSockettemp: webSocketMap.get(SysConstants.PEPOLE)) { try { webSockettemp.session.getBasicRemote().sendText(alarmMessage); } catch (Exception e) { webSockettemp.session.close(); } } } }catch (Exception e){ log.info("关闭websockt链接 wss:"+ e.getMessage()); }finally { } } public void sendPepoleAlarm(WebSocket session, String commandType, Integer dataSendType) { List<AlarmVo> result = new ArrayList<>(); Gson gson = new Gson(); List<Object> allAlarmList = redisUtils.hValues(RedisConstants.ALARM); if(allAlarmList != null && allAlarmList.size() >0){ for(Object alarm:allAlarmList){ AlarmListDto alarmListDto = gson.fromJson(alarm.toString(), AlarmListDto.class); AlarmVo vo = new AlarmVo(); BeanUtils.copyProperties(alarmListDto,vo); result.add(vo); } } Map<String,Object> resultMap = new HashMap<>(); resultMap.put("dataSendType",dataSendType); resultMap.put("alarmDataType","alarm"); resultMap.put("commandType",commandType); resultMap.put("alarmdata",result); this.alarmBroadcast(session,JSON.toJSONString(resultMap)); } public void sendCardCallAlarm(WebSocket session, String commandType) { List<AlarmVo> result = new ArrayList<>(); List<Object> allAlarmList = redisUtils.hValues(RedisConstants.CARDCALL_ALARM); if(allAlarmList !=null && allAlarmList.size()>0){ Gson gson = new Gson(); for(Object alram:allAlarmList){ AlarmVo vo = gson.fromJson(alram.toString(),AlarmVo.class); result.add(vo); } } Map<String,Object> resultMap = new HashMap<>(); resultMap.put("alarmDataType","cardCall"); resultMap.put("commandType",commandType); resultMap.put("alarmdata",allAlarmList); resultMap.put("alarmdata",result); this.alarmBroadcast(session, JSON.toJSONString(resultMap)); } }
例二:
@Service
@ServerEndpoint("/websocket/review")
@Slf4j
public class ReviewWebSocket {
    private VehicleApplicationOrderServiceImpl orderService = SpringInitConfig.getBean("vehicleApplicationOrderService");
    private UserSoundServiceImpl soundService = SpringInitConfig.getBean("userSoundService");
    private Session session;
    /**
     * 是调度员登录的所有客户端连接
     */
    private static CopyOnWriteArraySet<ReviewWebSocket> reviewWebSockets = new CopyOnWriteArraySet<>();
    private String empNo;
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        log.info("【websocket消息】有新的连接, 总数:{}", reviewWebSockets.size());
    }
    @OnClose
    public void onClose() {
        reviewWebSockets.remove(this);
        log.info("【websocket消息】连接断开, 总数:{}", reviewWebSockets.size());
    }
    @OnMessage
    public void onMessage(String message) {
        log.info("【websocket消息】收到客户端发来的消息:{}", message);
        OperateVo vo = new Gson().fromJson(message, OperateVo.class);
        if (orderService.isDispatcher(vo.getEmpNo())) { // 是调度员
            if (!reviewWebSockets.contains(this)) {
                this.empNo = vo.getEmpNo();
                reviewWebSockets.add(this);
                log.info("有新的调度员客户端链接加入");
                sendMessage(this, soundService.getSoundStatus(vo.getEmpNo()), 0); // true要播放声音 0不用请求数据
            } else {
                soundService.addSoundOrUpdate(vo.getEmpNo(), vo.isSoundStatus(), vo.getSoundTime()); // 声音新增或更新
                sendMessage(this, soundService.getSoundStatus(vo.getEmpNo()), 0); //返回声音的状态 0不用请求数据
            }
            log.info("调度员客户端链接个数:{}", reviewWebSockets.size());
        }
    }
    /**
     * 发送消息
     *
     * @param webSocket   链接
     * @param soundStatus 声音的状态
     * @param status      是否请求数据 0不用请求数据 1新增数据 2减少数据
     */
    public void sendMessage(ReviewWebSocket webSocket, boolean soundStatus, int status) {
        log.info("存在要审核的订单,给调度员发广播,声音状态:{}", soundStatus);
        try {
            Map<String, Object> resultMap = new HashMap<>();
            resultMap.put("sound", soundStatus); // 声音状态
            resultMap.put("soundTime", soundService.getSoundTime(webSocket.empNo)); // 声音提醒间隔,单位秒
            resultMap.put("status", status); // 前端是否要刷新数据
            resultMap.put("reviewData", orderService.existReviewSendMessage()); // 是否存在要审核的数据
            webSocket.session.getBasicRemote().sendText(JSON.toJSONString(resultMap));
        } catch (Exception e) {
            e.printStackTrace();
            log.info("【websocket消息】广播消息异常:{}", e.getMessage());
        }
    }
    /**
     * 调度员审核派车推送
     *
     * @param status 1增加 2减少
     */
    public void reviewSendBroadcast(int status) {
        log.info("有新的未审核订单,给所有调度员客户端发广播");
        try {
            for (ReviewWebSocket webSocket : reviewWebSockets) {
                log.info("客户端声音状态:{}", soundService.getSoundStatus(webSocket.empNo));
                Map<String, Object> resultMap = new HashMap<>();
                resultMap.put("sound", soundService.getSoundStatus(webSocket.empNo)); // 声音状态
                resultMap.put("soundTime", soundService.getSoundTime(webSocket.empNo)); // 声音提醒间隔,单位秒
                resultMap.put("status", status); // 前端是否要刷新数据
                resultMap.put("reviewData", orderService.existReviewSendMessage()); // 是否存在要审核的数据
                webSocket.session.getBasicRemote().sendText(JSON.toJSONString(resultMap));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
一般收到消息后经处理后要发送消息
收消息:
    @OnMessage
    public void onMessage(String message) { }
发消息:
webSocket.session.getBasicRemote().sendText(message);
四、注意
在@ServerEndpoint注解类中使用@Resource等注入失败。报出空指针异常。
原理是WebSocket是线程安全的,有用户连接时就会创建一个新的端点实例,一个端WebSocket是多对象的,使用的spring却是单例模式。这两者刚好冲突。
@Autowired注解注入对象是在启动的时候就把对象注入,而不是在使用A对象时才把A需要的B对象注入到A中。
而WebSocket在刚刚有说到,有连接时才实例化对象,而且有多个连接就有多个对象。总结就是,WebSocket是多对象的。不管单独使用也好,结合spring也好,或者结合SpringBoot也罢,他都是多对象的。
解决:使用从容器中取对象的工具类
 
                    
                     
                    
                 
                    
                
 
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号