client心跳 websocket_websocket心跳机制

WebSocket是一种双向通信协议,允许服务器主动向客户端推送信息。本文介绍了WebSocket的特点,并详细讲解了心跳机制,用于判断客户端在线离线状态。通过SpringBoot整合WebSocket,实现了心跳检测和连接管理,确保了服务的稳定性和可靠性。
摘要由CSDN通过智能技术生成

什么是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依赖

org.springframework.boot

spring-boot-starter-websocket

websocket的配置类:

@Configurationpublic classWebSocketConfig {

@BeanpublicServerEndpointExporter serverEndpointExporter() {return newServerEndpointExporter();

}

}

websocket的API

@ServerEndpoint("/websocket/{sid}")

@Component

@Slf4jpublic classWebSocketServer {//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。

private static int onlineCount = 0;//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。

private static CopyOnWriteArraySet webSocketSet = new CopyOnWriteArraySet();//与某个客户端的连接会话,需要通过它来给客户端发送数据

privateSession session;//接收sid

private String sid="";/*** 连接建立成功调用的方法*/@OnOpenpublic 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异常");

}

}/*** 连接关闭调用的方法*/@OnClosepublic voidonClose() {

webSocketSet.remove(this); //从set中删除

subOnlineCount(); //在线数减1

log.info("有一连接关闭!当前在线人数为" +getOnlineCount());

}

@Autowiredprivate RedisUtil redisUtil = (RedisUtil) SpringUtil.getBean("RedisUtil");/*** 收到客户端消息后调用的方法

*

*@parammessage 客户端发送过来的消息*/@OnMessagepublic voidonMessage(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();

}

}

}/***

*@paramsession

*@paramerror*/@OnErrorpublic voidonError(Session session, Throwable error) {

log.error("发生错误");

error.printStackTrace();

}/*** 实现服务器主动推送*/

public void sendMessage(String message) throwsIOException {this.session.getBasicRemote().sendText(message);

}/*** 群发自定义消息

**/

public static void sendInfo(String message,@PathParam("sid") String sid) throwsIOException {

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 intgetOnlineCount() {returnonlineCount;

}public static synchronized voidaddOnlineCount() {

WebSocketServer.onlineCount++;

}public static synchronized voidsubOnlineCount() {

WebSocketServer.onlineCount--;

}

}

HTML页面

Test My WebSocket

TestWebSocket

SEND MESSAGE

CLOSE

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值