什么是websocket?

WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。

它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

Android socket空心跳包 websocket发送心跳包_客户端

 

 

其他特点包括:

(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>