前端和后台建立websocket长连接,并添加心跳检测

Nginx代理服务器默认1分钟会导致websocket长连接断开,所以服务端与客户端需要添加心跳检测

1.前端代码Vue


  data () {
    return {
      showDeviceDialog: true,
      banner: [],
      deviceId: '',
      timeout: 55000, // 55秒发一次心跳,比nginx代理端默认连接超时时间(1分钟)稍微小一点,在接近断开的情况下以通信的方式去重置连接时间
      serverTimeoutObj: null,
      websocket_connected_count: 0, //已尝试连接次数
    }
  },
  watch: {
    deviceId (newVal, OldVal) {
      if (newVal) {
        const routerUrl = this.$router.resolve({
          name: 'detail',
          params: { id: newVal }
        })
        window.open(routerUrl.href, '_blank')
      }
    }
  },
  components: {
    selectDevice
  },
  methods: {
    seeMore () {
      const routerUrl = this.$router.resolve({
        name: 'list'
      })
      window.open(routerUrl.href, '_blank')
      // this.$router.push({ name: 'list' })
    },
    confirmSelectDevice (active) {
      this.showDeviceDialog = false
      this.createWebsocket(active)
    },
    createWebsocket(active) {
      let port;
      switch(active) {
        case 1:
            port = 8092;
            break;
        case 2:
            port = 8093;
            break;
        case 3:
            port = 8094;
            break;
      }
      const clientId = port + '@' + new Date().getTime();
      const httpURL = process.env.NODE_ENV === 'production' ? 'wss://henyouliao.cn' : 'ws://192.168.2.135:8005';
      this.websocket = new WebSocket(`${httpURL}/websocket/${clientId}`);
      // 连接发生错误的回调方法,连接错误时会继续尝试发起连接(尝试5次)
      this.websocket.onerror = () => {
        console.log("websocket发生了错误" + (this.websocket_connected_count > 0 ? ",已重连" + this.websocket_connected_count + "次" : ""));
        if( ++this.websocket_connected_count <= 5) {
          this.createWebsocket(active);
        }
      };
      // 连接成功建立的回调方法
      this.websocket.onopen = () => {
        console.log("websocket已打开(" + new Date().format('yyyy-MM-dd hh:mm:ss') + ")");
        //成功建立连接后,重置心跳检测
        this.heartCheckReset();
        this.heartCheckStart(active);
      };
      // 接收到消息的回调方法
      this.websocket.onmessage = (msg) => {
        //如果获取到消息,说明连接是正常的,重置心跳检测
        this.heartCheckReset();
        this.heartCheckStart(active);
        //拿到后端返回的心跳pong说明连接正常
        if(msg.data && String(msg.data).toLowerCase().trim() !== "pong") {
          console.log('websocket接收到消息', msg.data);
          this.deviceId = msg.data
        }
      };
      // 连接关闭的回调方法
      this.websocket.onclose = () => {
        console.log("websocket已关闭(" + new Date().format('yyyy-MM-dd hh:mm:ss') + ")");
      };
      // 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
      this.websocket.onbeforeunload = () => {
        console.log('监听到窗口关闭事件,主动关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常');
        this.websocket.close();
        this.heartCheckReset();
      };
    },
    // 心跳检测, 每隔一段时间检测连接状态,如果处于连接中,就向server端主动发送消息,来重置server端与客户端的最大连接时间,如果已经断开了,发起重连。
    heartCheckReset() {
      clearTimeout(this.serverTimeoutObj);
    },
    heartCheckStart(active) {
      this.serverTimeoutObj = setTimeout(() =>{
        if(this.websocket.readyState == 1){
          console.log("websocket连接状态,发送心跳检测ping维持心跳连接...");
          this.websocket.send("ping");
          //如果获取到消息,说明连接是正常的,重置心跳检测
          this.heartCheckReset();
          this.heartCheckStart(active);
        } else {
          console.log("websocket断开状态,尝试重连");
          this.createWebsocket(active);
        }
      }, this.timeout)
    }
}

2.后台代码SpringBoot


/**
 * @author Kisen
 * @email liuqs@jaid.cn
 * @date 2022/11/16 11:28
 * @detail websocket server端
 * clientId格式: 机器socket端口@时间戳
 * 8092,8093,8094端口分别对应1号机、2号机、3号机
 */
@Slf4j
@Component
@ServerEndpoint("/websocket/{clientId}")
public class WebSocketServer {

    //用来存放每个客户端对应的WebSocket对象
    public static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();

    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;

    //当前连接客户端的用户唯一标识
    private String clientId;

    /**
     * 连接建立成功调用的方法
     *
     * @param session
     * @param clientId
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("clientId") String clientId) {
        this.session = session;
        this.clientId = clientId;
        // clientId是用来表示唯一客户端,如果需要指定发送,需要指定发送通过clientId来区分
        webSocketMap.put(clientId, this);
        log.info("[WebSocket]客户端{}连接成功,客户端标识:{},当前在线连接数为:{}", session.getId(), clientId, webSocketMap.size());
    }

    /**
     * 连接关闭调用的方法
     *
     * @param session
     */
    @OnClose
    public void onClose(Session session) {
        webSocketMap.remove(this.clientId);
        log.info("[WebSocket]客户端{}连接断开,客户端标识:{},当前在线连接数为:{}", session.getId(), clientId, webSocketMap.size());
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("收到客户端{} 的消息:{}", session.getId(), message);
        //心跳检测响应
        if (StringUtils.equalsIgnoreCase("ping", message)) {
            sendMessage("pong");
            log.info("[WebSocket]服务端 已回复客户端{} 的心跳检测: pong", session.getId());
        }
    }

    @OnError
    public void onError(Session session, Throwable error) {
        log.error("客户端{} 发生错误:{}", session.getId(), error);
    }

    /**
     * 实现服务器主动推送
     *
     * @param message
     * @throws IOException
     */
    public void sendMessage(String message) {
        try {
            this.session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            log.error("客户端{} 发生消息({})失败:{}", session.getId(), message, e);
            throw new RuntimeException(e);
        }
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值