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