• 基于javax的websocket服务端实现心跳机制


    websocket连接类

    package com.dnn.controller.inter;
    
    import java.io.IOException;
    import java.text.SimpleDateFormat;
    import java.util.Calendar;
    import java.util.Date;
    import java.util.Set;
    import java.util.concurrent.CopyOnWriteArraySet;
    
    import javax.websocket.CloseReason;
    import javax.websocket.CloseReason.CloseCodes;
    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.http.impl.entity.EntitySerializer;
    
    import com.dnn.entity.WebSocketEntity;
    import com.dnn.model.TbAdminMember;
    import com.dnn.service.TbAdminMemberService;
    import com.dnn.service.TbDataGrouprecordService;
    import com.dnn.utils.jfinal.BaseController;
    import com.jfinal.aop.Clear;
    
    import net.sf.json.JSONObject;
    
    @Clear
    @ServerEndpoint("/webSocket/{userId}")
    public class WebSocketController extends BaseController {
        
        protected TbDataGrouprecordService tbDataGrouprecordService=new TbDataGrouprecordService();
        protected TbAdminMemberService tbAdminMemberService=new TbAdminMemberService();
        private static boolean isHeart=false;
        private static final Set<WebSocketEntity> connections = new CopyOnWriteArraySet<WebSocketEntity>();
        
        /**
         * 
         * @Description: 连接方法
         * @param @param userId
         * @param @param session   
         * @return void  
         * @throws IOException 
         * @throws
         * @author 黑暗料理界扛把子
         * @date 2018年5月10日
         */
        @OnOpen
        public synchronized void onOpen(@PathParam("userId") String userId, Session session) throws IOException {
            TbAdminMember member=tbAdminMemberService.findInfo(new TbAdminMember().setId(userId));
            if(null==member){ 
                logger.debug("发现未知生物");
                return;
            }
            addUser(member, session);
            if(connections.size()==1 && !isHeart){
                isHeart=true;
                startHeart();
            }
        }
    
        /**
         * 
         * @Description: 收到消息执行
         * @param @param userId
         * @param @param message
         * @param @param session
         * @param @throws IOException   
         * @return void  
         * @throws
         * @author 黑暗料理界扛把子
         * @date 2018年5月10日
         */
        @OnMessage
        public synchronized void onMessage(@PathParam("userId") String userId, String message, Session session) throws IOException {
            logger.info(message);
            JSONObject jsonObject = JSONObject.fromObject(message);
            if(jsonObject.has("secret") && jsonObject.getString("secret").equals("ping")){//心跳
                logger.info("收到"+userId+"的心跳"+message);
                //如果收到了心跳 这里设置isHeart为true
                WebSocketEntity entity=getUserEntity(userId);
                if(null!=entity){
                    entity.setHeart(true);
                }
            }else{//普通对话
                boolean res=tbDataGrouprecordService.addGroupRecord(jsonObject);
                logger.warn("保存记录:"+res);
                SimpleDateFormat sdfTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                Date date = new Date();
                String datetime = sdfTime.format(date);//获取当前时间
                jsonObject.put("intime", datetime);
                message=tbAdminMemberService.addUserInfo(jsonObject);
                sendMsg(message);
            }
        }
    
        /**
         * 
         * @Description: 链接错误执行
         * @param @param userId
         * @param @param session
         * @param @param error   
         * @return void  
         * @throws IOException 
         * @throws
         * @author 黑暗料理界扛把子
         * @date 2018年5月10日
         */
        @OnError
        public synchronized void onError(@PathParam("userId") String userId, Session session, Throwable error) throws IOException {
            logger.debug(userId+":发生了错误");
            removeUser(userId,new CloseReason(CloseCodes.NO_EXTENSION, "客户端异常"));
            error.printStackTrace();
        }
        
        @OnClose
        public synchronized void onClose(@PathParam("userId") String userId,Session session,CloseReason reason){
            logger.debug(userId+":退出了链接");
            removeUser(userId,reason);
        }
    
        /**
         * 
         * @Description: 获取在线人数
         * @param @return   
         * @return int  
         * @throws
         * @author 黑暗料理界扛把子
         * @date 2018年5月10日
         */
        private synchronized int getUserOnlineNum(){
            return connections.size();
        }
        
        /**
         * 
         * @Description: 获取在线人数列表
         * @param @return   
         * @return Set<String>  
         * @throws
         * @author 黑暗料理界扛把子
         * @date 2018年5月10日
         */
        @SuppressWarnings("unused")
        private synchronized Set<WebSocketEntity> getUserOnline(){
            return connections;
        }
        
        /**
         * 
         * @Description: 用户上线
         * @param @param member
         * @param @param session   
         * @return void  
         * @throws
         * @author 黑暗料理界扛把子
         * @date 2018年5月10日
         */
        private synchronized void addUser(TbAdminMember member, Session session){       
            WebSocketEntity entity=getUserEntity(member.getId());
            if(null==entity){
                connections.add(new WebSocketEntity(member, session));
            }else{
                entity.setSession(session);
                entity.setMemberHead(member.getHead());
                entity.setMemberName(member.getName());
                logger.debug("用户"+entity.getMemberName()+"上线了,当前人数为:"+getUserOnlineNum());
            }
        
        }
        
        /**
         * 
         * @Description: 根据userId获取实体类
         * @param @param userId
         * @param @return   
         * @return WebSocketEntity  
         * @throws
         * @author 黑暗料理界扛把子
         * @date 2018年5月22日
         */
        private static WebSocketEntity getUserEntity(String userId){
            WebSocketEntity entity=null;
            if(connections.size()==0)
                return entity;
            for (WebSocketEntity webSocketEntity : connections) {
                if(webSocketEntity.getUserId().contentEquals(userId)){
                    entity=webSocketEntity;
                    break;
                }
            }
            return entity;
        }
        
        /**
         * 
         * @Description: 用户下线
         * @param @param userId
         * @param @param reason   
         * @return void  
         * @throws
         * @author 黑暗料理界扛把子
         * @date 2018年5月23日
         */
        private void removeUser(String userId, CloseReason reason) {
            WebSocketEntity entity=getUserEntity(userId);
            if(null!=entity){       
                try {
                    if(entity.getSession().isOpen()){
                        entity.getSession().close(reason);
                    }
                    connections.remove(entity);
                } catch (IOException e) {
                    
                    logger.info(e.toString());
                    e.printStackTrace();
                }
            }
            logger.debug("当前人数:"+connections.size());
            
        }
        
    
        /**
         * 
         * @param 发送心跳包
         * @Description: 服务端群发消息
         * @param @param message
         * @param @throws IOException   
         * @return void  
         * @throws
         * @author 黑暗料理界扛把子
         * @date 2018年5月10日
         */
        public synchronized void sendPing(String message) throws IOException{
            if(connections.size()<=0)
                return;
            for (WebSocketEntity webSocketEntity : connections) {   
                synchronized (webSocketEntity) {
                    webSocketEntity.setTimeStr(getTimeInMillis());
                    webSocketEntity.setHeart(false);  
                    ((Session) webSocketEntity.getSession()).getBasicRemote().sendText(message);
                }
            }
        }
        
        /**
         * 
         * @Description: 发消息
         * @param @param message
         * @param @throws IOException   
         * @return void  
         * @throws
         * @author 黑暗料理界扛把子
         * @date 2018年5月11日
         */
        public synchronized void sendMsg(String message) throws IOException{
            if(connections.size()<=0)
                return;
            for (WebSocketEntity entity : connections) {
                synchronized (entity) {
                    ((Session) entity.getSession()).getBasicRemote().sendText(message); // 回复用户
                }
            }
        }
        
        
        
        /**
         * 
         * @Description: 启动心跳包
         * @param    
         * @return void  
         * @throws
         * @author 黑暗料理界扛把子
         * @date 2018年5月10日
         */
        private synchronized void startHeart(){
            ExamineHeartThread examineHeart =new ExamineHeartThread();
            Thread examineThread=new Thread(examineHeart);
            
            KeepHeartThread keepHeart=new KeepHeartThread();
            Thread keepThread=new Thread(keepHeart);
            
            
            keepThread.start(); 
            examineThread.start();
            
        }
        
        /**
         * 
         * @Description: 获取时间戳
         * @param @return   
         * @return long  
         * @throws
         * @author 黑暗料理界扛把子
         * @date 2018年5月22日
         */
        private static long getTimeInMillis(){
            Calendar c = Calendar.getInstance();
            c.set(Calendar.SECOND,c.get(Calendar.SECOND)+8);
            return c.getTimeInMillis();
        }
        
       /**
        * 
        * @author 黑暗料理界扛把子
        *
        * @Description server发送心跳包 10秒一次
        */
        private class KeepHeartThread implements Runnable {
            
            @Override
            public void run() {  
                JSONObject heartJson=new JSONObject();
                heartJson.put("type", "0");
                heartJson.put("secret", "heart_keep");
                while (true) {              
                    try {
                        logger.debug("发送心跳包当前人数为:"+getUserOnlineNum());
                        sendPing(heartJson.toString());
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
                
        }
        
        /**
         * 
         * @author 黑暗料理界扛把子
         *
         * @Description 检测是否收到client心跳 每秒一次
         */
        private class ExamineHeartThread implements Runnable{
    
            @Override
            public void run() {
                while (true) {
                    try {
                        long timeMillins=System.currentTimeMillis();
                        for (WebSocketEntity entity : connections) {
                            logger.debug(timeMillins);
                            logger.info(entity.getTimeStr());
                            logger.debug(timeMillins>entity.getTimeStr());
                            if(!entity.isHeart() && entity.getTimeStr()!=0 && timeMillins>entity.getTimeStr()){
                                logger.debug(entity.getMemberName()+"挂了");
                                onClose(entity.getUserId(),entity.getSession(),new CloseReason(CloseCodes.NORMAL_CLOSURE, "没有收到心跳"));
                            }
                        }
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    } 
       

    WebSocketEntity实体类

    package com.dnn.entity;
    
    import javax.websocket.Session;
    
    import com.dnn.model.TbAdminMember;
    
    public class WebSocketEntity {
    
        private String userId;//用户id
        private Session session;
        private String memberName;//用户姓名
        private String memberHead;//头像
        private long timeStr;//记录下次发送时间的时间戳
        private boolean isHeart=false;//是否收到了心跳
        
        public boolean isHeart() {
            return isHeart;
        }
    
        public void setHeart(boolean isHeart) {
            this.isHeart = isHeart;
        }
    
        public String getMemberName() {
            return memberName;
        }
    
        public void setMemberName(String memberName) {
            this.memberName = memberName;
        }
    
        public String getMemberHead() {
            return memberHead;
        }
    
        public void setMemberHead(String memberHead) {
            this.memberHead = memberHead;
        }   
    
        public long getTimeStr() {
            return timeStr;
        }
    
        public void setTimeStr(long timeStr) {
            this.timeStr = timeStr;
        }
    
        public String getUserId() {
            return userId;
        }
    
        public void setUserId(String userId) {
            this.userId = userId;
        }
    
        public Session getSession() {
            return session;
        }
        
        public void setSession(Session session) {
            this.session = session;
        }
    
    
        public WebSocketEntity(String userId, Session session, String memberName, String memberHead) {
            super();
            this.userId = userId;
            this.session = session;
            this.memberName = memberName;
            this.memberHead = memberHead;
        }
    
        public WebSocketEntity(TbAdminMember member, Session session) {
            super();
            this.userId = member.getId();
            this.session = session;
            this.memberName = member.getName();
            this.memberHead = member.getHead();
        }
    
        @Override
        public String toString() {
            return "WebSocketEntity [userId=" + userId + ", session=" + session + ", memberName=" + memberName
                    + ", memberHead=" + memberHead + ", timeStr=" + timeStr + ", isHeart=" + isHeart + "]";
        }
    
        @Override
        public int hashCode() {
            return this.userId.length();
        }
    
        @Override
        public boolean equals(Object obj) {
            if(!(obj instanceof WebSocketEntity)){
                return false;
            }
            if(obj==this){
                return true;
            }
            return this.userId.equals(((WebSocketEntity)obj).userId);
        }
    }
  • 相关阅读:
    Docker之设置加速器
    Nginx之美多商城前台部署
    Nginx之负载均衡
    Nginx之反向代理
    美多商城后台MIS系统部署之Nginx配置
    Nginx之server和location配置使用
    题解 P4721 【【模板】分治 FFT】
    题解 P3338 【[ZJOI2014]力】
    题解 CHSEQ22 【Chef and Favourite Sequence】
    题解 CF813F 【Bipartite Checking】
  • 原文地址:https://www.cnblogs.com/xiejn/p/15553232.html
Copyright © 2020-2023  润新知