java websocket server implementation, including heartbeat mechanism

Disclaimer: This article is bloggers create their own article, reproduced, please attach Bowen link! https://blog.csdn.net/WXN069/article/details/91369534

websocket connection class

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 xiaoqiang
     * @date2019年6月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 xiaoqiang
     * @date2019年6月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 xiaoqiang
     * @date2019年6月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 xiaoqiang
     * @date2019年6月10日
     */
    private synchronized int getUserOnlineNum(){
        return connections.size();
    }
    
    /**
     * 
     * @Description: 获取在线人数列表
     * @param @return   
     * @return Set<String>  
     * @throws
     * @author xiaoqiang
     * @date2019年6月10日
     */
    @SuppressWarnings("unused")
    private synchronized Set<WebSocketEntity> getUserOnline(){
        return connections;
    }
    
    /**
     * 
     * @Description: 用户上线
     * @param @param member
     * @param @param session   
     * @return void  
     * @throws
     * @author xiaoqiang
     * @date2019年6月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 xiaoqiang
     * @date2019年6月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 xiaoqiang
     * @date2019年6月10日
     */
    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 xiaoqiang
     * @date2019年6月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 entity class

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);
    }
}

Guess you like

Origin blog.csdn.net/WXN069/article/details/91369534