websocket心跳機制


什么是websocket?

WebSocket 協議在2008年誕生,2011年成為國際標准。所有瀏覽器都已經支持了。

它的最大特點就是,服務器可以主動向客戶端推送信息,客戶端也可以主動向服務器發送信息,是真正的雙向平等對話,屬於服務器推送技術的一種。

 

 

其他特點包括:

(1)建立在 TCP 協議之上,服務器端的實現比較容易。

(2)與 HTTP 協議有着良好的兼容性。默認端口也是80和443,並且握手階段采用 HTTP 協議,因此握手時不容易屏蔽,能通過各種 HTTP 代理服務器。

(3)數據格式比較輕量,性能開銷小,通信高效。

(4)可以發送文本,也可以發送二進制數據。

(5)沒有同源限制,客戶端可以與任意服務器通信。

(6)協議標識符是ws(如果加密,則為wss),服務器網址就是 URL。

 

一、何為心跳?

心跳就是客戶端定時的給服務端發送消息,證明客戶端是在線的, 如果超過一定的時間沒有發送則就是離線了。

二、如何判斷在線離線?

當客戶端第一次發送請求至服務端時會攜帶唯一標識、以及時間戳,服務端到db或者緩存去查詢改請求的唯一標識,如果不存在就存入db或者緩存中,

第二次客戶端定時再次發送請求依舊攜帶唯一標識、以及時間戳,服務端到db或者緩存去查詢改請求的唯一標識,如果存在就把上次的時間戳拿取出來,使用當前時間戳減去上次的時間,

得出的毫秒秒數判斷是否大於指定的時間,若小於的話就是在線,否則就是離線;

三、若服務端宕機了,客戶端怎么做、服務端再次上線時怎么做?

客戶端則需要斷開連接,通過onclose 關閉連接,服務端再次上線時則需要清除之間存的數據,若不清除 則會造成只要請求到服務端的都會被視為離線。

四、使用springboot整合websocket

添加Pom依賴

  

    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

    websocket的配置類:
    @Configuration 
public class WebSocketConfig {
    
    @Bean  
    public ServerEndpointExporter serverEndpointExporter() {  
        return new ServerEndpointExporter();  
    }  
}

websocket的API
@ServerEndpoint("/websocket/{sid}")
@Component
@Slf4j
public class WebSocketServer {
     
        //靜態變量,用來記錄當前在線連接數。應該把它設計成線程安全的。
        private static int onlineCount = 0;
        //concurrent包的線程安全Set,用來存放每個客戶端對應的MyWebSocket對象。
        private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();

        //與某個客戶端的連接會話,需要通過它來給客戶端發送數據
        private Session session;

        //接收sid
        private String sid="";
        /**
         * 連接建立成功調用的方法*/
        @OnOpen
        public void onOpen(Session session,@PathParam("sid") String sid) {
            this.session = session;
            webSocketSet.add(this);     //加入set中
            addOnlineCount();           //在線數加1
            log.info("有新窗口開始監聽:"+sid+",當前在線人數為" + getOnlineCount());
            this.sid=sid;
            try {
                 sendMessage("連接成功");
            } catch (IOException e) {
                log.error("websocket IO異常");
            }
        }

        /**
         * 連接關閉調用的方法
         */
        @OnClose
        public void onClose() {
            webSocketSet.remove(this);  //從set中刪除
            subOnlineCount();           //在線數減1
            log.info("有一連接關閉!當前在線人數為" + getOnlineCount());
        }

     
        @Autowired
        private RedisUtil<Object> redisUtil = (RedisUtil<Object>) SpringUtil.getBean("RedisUtil"); 
        
        /**
         * 收到客戶端消息后調用的方法
         *
         * @param message 客戶端發送過來的消息*/
        @OnMessage
        public void onMessage(String message, Session session) {
            //清空表
            
            log.info("收到來自窗口"+sid+"的信息:"+message);
            //clinet數據
            DeviceInfo Deviceinfo=FastJsonUtils.toBean(message, DeviceInfo.class);
            Deviceinfo.setBeatTime(System.currentTimeMillis());
            log.info(Deviceinfo.toString());
            String jsonStr=redisUtil.get(Deviceinfo.getDeviceId());
            if(null==jsonStr) {
                redisUtil.set(Deviceinfo.getDeviceId(), Deviceinfo);
            }else {
                //服務端數據
                DeviceInfo redisData= FastJsonUtils.toBean(redisUtil.get(Deviceinfo.getDeviceId()),DeviceInfo.class);
                
                Long redistime= redisData.getBeatTime();
                //當前系統時間
                Long dates=   System.currentTimeMillis();
                Long time=    dates-redistime;
                log.info("endTime="+dates+"startTime="+redistime+"總時長="+time);
                if(time<=15*1000) {
                    log.info(Deviceinfo.getDeviceId()+"在線");
                }else {
                    log.info(Deviceinfo.getDeviceId()+"離線");
                }
                 redisUtil.set(Deviceinfo.getDeviceId(), Deviceinfo);    
            }
           
           
            //群發消息
            for (WebSocketServer item : webSocketSet) {
                try {
                    item.sendMessage(message);
                  //  item.sendInfo(message,sid);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        /**
         * 
         * @param session
         * @param error
         */
        @OnError
        public void onError(Session session, Throwable error) {
            log.error("發生錯誤");
            error.printStackTrace();
        }
        /**
         * 實現服務器主動推送
         */
        public void sendMessage(String message) throws IOException {
            this.session.getBasicRemote().sendText(message);
        }


        /**
         * 群發自定義消息
         * */
        public static void sendInfo(String message,@PathParam("sid") String sid) throws IOException {
            log.info("推送消息到窗口"+sid+",推送內容:"+message);
            for (WebSocketServer item : webSocketSet) {
                try {
                    //這里可以設定只推送給這個sid的,為null則全部推送
                    if(sid==null) {
                        item.sendMessage(message);
                    }else if(item.sid.equals(sid)){
                        item.sendMessage("server:"+message);
                    }
                } catch (IOException e) {
                    continue;
                }
            }
        }

        public static synchronized int getOnlineCount() {
            return onlineCount;
        }

        public static synchronized void addOnlineCount() {
            WebSocketServer.onlineCount++;
        }

        public static synchronized void subOnlineCount() {
            WebSocketServer.onlineCount--;
        }
}        

HTML頁面
<!DOCTYPE HTML>
<html>
<head>
    <title>Test My WebSocket</title>
    <meta charset="utf-8">
    <script type="text/javascript" src="../js/jquery.min.js"></script>
</head>
 
<body>
TestWebSocket
<input  id="texts" value="" type="text" />
<button onclick="send()">SEND MESSAGE</button>
<button οnclick="closeWebSocket()">CLOSE</button>
<div id="message"></div>
 
<script type="text/javascript">
var lockReconnect = false;//避免重復連接
var wsUrl = "ws://localhost:8889/websocket/001";
var ws;
var tt;
function createWebSocket() {
  try {
    ws = new WebSocket(wsUrl);
    init();
  } catch(e) {
    console.log('catch');
    reconnect(wsUrl);
  }
}
function init() {
  ws.onclose = function () {
    console.log('鏈接關閉');
    reconnect(wsUrl);
  };
  ws.onerror = function() {
    console.log('發生異常了');
    reconnect(wsUrl);
  };
  ws.onopen = function () {
    //心跳檢測重置
    heartCheck.start();
  };
  ws.onmessage = function (event) {
    //拿到任何消息都說明當前連接是正常的
    console.log('接收到消息');
    heartCheck.start();
  }
}
function reconnect(url) {
  if(lockReconnect) {
    return;
  };
  lockReconnect = true;
  //沒連接上會一直重連,設置延遲避免請求過多
  tt && clearTimeout(tt);
  tt = setTimeout(function () {
    createWebSocket(url);
    lockReconnect = false;
  }, 4000);
}
//心跳檢測
var heartCheck = {
  timeout: 10000,
  timeoutObj: null,
  serverTimeoutObj: null,
  start: function(){
    console.log('start');
    var self = this;
    this.timeoutObj && clearTimeout(this.timeoutObj);
    this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
    this.timeoutObj = setTimeout(function(){
      //這里發送一個心跳,后端收到后,返回一個心跳消息,
   
      var timestamp = (new Date()).getTime();
      //,"beatTime":timestamp
      var obj={"deviceId":"001","userId":"119"};
      var a = JSON.stringify(obj);
      ws.send(a);
      self.serverTimeoutObj = setTimeout(function() {
       
        console.log(ws);
        ws.close();
       
      }, self.timeout);

    }, this.timeout)
  }
}
createWebSocket(wsUrl);
</script>
</body>

</html>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM