专栏首页服务端技术查看Socket断开原因及加入心跳机制防止自动断开连接
原创

查看Socket断开原因及加入心跳机制防止自动断开连接

【转载请注明出处】:https://blog.csdn.net/huahao1989/article/details/107804286

一般情况下,前端页面连接WebSocket服务的时候都是通过Nginx等负载均衡,然后由Nginx去代理连接后端的socket服务。Nginx的配置类似如下:

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}
location / {
    proxy_pass https://socket;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
         
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
}

如果建立连接之后不做一些措施,那么可能会有各种各样的原因会导致socket断开,最好在socket断开时将错误打印出来。

ws.onclose = function (ev) {
  console.log('socket 断开: ' + ev.code + ' ' + ev.reason + ' ' + ev.wasClean)
}

socket断开时,会触发CloseEvent, CloseEvent会在连接关闭时发送给使用 WebSocket 的客户端,它在 WebSocket 对象的 onclose 事件监听器中使用。 CloseEvent有三个字段需要注意, 通过分析这三个字段,一般就可以找到断开原因:

  • CloseEvent.code: code是错误码,是整数类型
  • CloseEvent.reason: reason是断开原因,是字符串
  • CloseEvent.wasClean: wasClean表示是否正常断开,是布尔值。一般异常断开时,该值为false

状态码

名称

描述

0–999

保留段, 未使用.

1000

CLOSE_NORMAL

正常关闭; 无论为何目的而创建, 该链接都已成功完成任务.

1001

CLOSE_GOING_AWAY

终端离开, 可能因为服务端错误, 也可能因为浏览器正从打开连接的页面跳转离开.

1002

CLOSE_PROTOCOL_ERROR

由于协议错误而中断连接.

1003

CLOSE_UNSUPPORTED

由于接收到不允许的数据类型而断开连接 (如仅接收文本数据的终端接收到了二进制数据).

1004

保留. 其意义可能会在未来定义.

1005

CLOSE_NO_STATUS

保留. 表示没有收到预期的状态码.

1006

CLOSE_ABNORMAL

保留. 用于期望收到状态码时连接非正常关闭 (也就是说, 没有发送关闭帧).

1007

Unsupported Data

由于收到了格式不符的数据而断开连接 (如文本消息中包含了非 UTF-8 数据).

1008

Policy Violation

由于收到不符合约定的数据而断开连接. 这是一个通用状态码, 用于不适合使用 1003 和 1009 状态码的场景.

1009

CLOSE_TOO_LARGE

由于收到过大的数据帧而断开连接.

1010

Missing Extension

客户端期望服务器商定一个或多个拓展, 但服务器没有处理, 因此客户端断开连接.

1011

Internal Error

客户端由于遇到没有预料的情况阻止其完成请求, 因此服务端断开连接.

1012

Service Restart

服务器由于重启而断开连接.

1013

Try Again Later

服务器由于临时原因断开连接, 如服务器过载因此断开一部分客户端连接.

1014

由 WebSocket标准保留以便未来使用.

1015

TLS Handshake

保留. 表示连接由于无法完成 TLS 握手而关闭 (例如无法验证服务器证书).

1016–1999

由 WebSocket标准保留以便未来使用.

2000–2999

由 WebSocket拓展保留使用.

3000–3999

可以由库或框架使用.? 不应由应用使用. 可以在 IANA 注册, 先到先得.

4000–4999

可以由应用使用.

为了保证socket稳定,不断开,最好也是最简单的办法是添加一些逻辑,一直保持socket处在连接的状态。常见的做法就是间隔发ping消息给服务端,服务端接收到这个消息之后返回pong消息,以此来保持心跳,以防sock断开。我们常见的ping消息和pong消息实际上是发送了一个文本消息,这个消息的内容是ping或者pong,甚至是heatbeat等等,但是从socket协议来说是有设计ping消息和pong消息的。在socket的数据帧中,有一个opcode,它表明了socket的数据帧是什么类型的:

  • %x0:表示一个延续帧。当Opcode为0时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片。
  • %x1:表示这是一个文本帧(frame)
  • %x2:表示这是一个二进制帧(frame)
  • %x3-7:保留的操作代码,用于后续定义的非控制帧。
  • %x8:表示连接断开。
  • %x9:表示这是一个ping操作。
  • %xA:表示这是一个pong操作。
  • %xB-F:保留的操作代码,用于后续定义的控制帧。

规范的心跳应该是在opcode里定义type:ping(9)才对,消息的内容是null,什么都没有,这才是最轻量级最规范的websocket心跳机制。一般情况下,使用发文本消息的方式也是没啥问题的,无非就是多消耗了一点流量和带宽,调试起来也容易一些,有可能心跳消息本身就会带一些业务数据。

js代码如下:

var lockReconnect = false;  
var ws = null;          
var wsUrl = 'wss://127.0.0.1/socket'
createWebSocket(wsUrl);  

function createWebSocket(url) {
    try{
        if('WebSocket' in window){
            ws = new WebSocket(url);
        }
        initEventHandle();
    }catch(e){
        reconnect(url);
        console.log(e);
    }     
}

function initEventHandle() {
    ws.onclose = function (ev) {
        reconnect(wsUrl);
         console.log('socket 断开: ' + ev.code + ' ' + ev.reason + ' ' + ev.wasClean)
    };
    ws.onerror = function (ev) {
        reconnect(wsUrl);
        console.log("llws连接错误!");
    };
    ws.onopen = function () {
        heartCheck.reset().start();      
        console.log("llws连接成功!"+new Date().toLocaleString());
    };
    ws.onmessage = function (message) {    
        heartCheck.reset().start();      //拿到任何消息都说明当前连接是正常的
        console.log("llws收到消息啦:" +message.data);
        if(message.data!='pong'){
            var msg = JSON.parse(message.data);
        }
    };
}

// 当窗口关闭时,主动去关闭websocket连接
window.onbeforeunload = function() {
    ws.close();
}  

function reconnect(url) {
    if(lockReconnect) return;
    lockReconnect = true;
    setTimeout(function () {     //没连接上会一直重连,设置延迟避免请求过多
        createWebSocket(url);
        lockReconnect = false;
    }, 2000);
}
 
var heartCheck = {
    timeout: 3000,       
    timeoutObj: null,
    serverTimeoutObj: null,
    reset: function(){
        clearTimeout(this.timeoutObj);
        clearTimeout(this.serverTimeoutObj);
        return this;
    },
    start: function(){
        var self = this;
        this.timeoutObj = setTimeout(function(){ 
            ws.send("ping");
            console.log("ping!")
            self.serverTimeoutObj = setTimeout(function(){
              //如果超过一定时间还没重置,说明后端主动断开了
                ws.close();      
            }, self.timeout)
        }, this.timeout)
    }
}

服务端Java代码:

@OnMessage  
public void onMessage(String message, Session session) {  
        if(message.equals("ping")){

        }else{
               
        }
}

欢迎关注 “后端老鸟” 公众号,接下来会发一系列的专题文章,包括Java、Python、Linux、SpringBoot、SpringCloud、Dubbo、算法、技术团队的管理等,还有各种脑图和学习资料,NFC技术、搜索技术、爬虫技术、推荐技术、音视频互动直播等,只要有时间我就会整理分享,敬请期待,现成的笔记、脑图和学习资料如果大家有需求也可以公众号留言提前获取。由于本人在所有团队中基本都处于攻坚和探路的角色,搞过的东西多,遇到的坑多,解决的问题也很多,欢迎大家加公众号进群一起交流学习。

【转载请注明出处】:https://blog.csdn.net/huahao1989/article/details/107804286

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Spring Boot 1.X和2.X优雅重启实战

    项目在重新发布的过程中,如果有的请求时间比较长,还没执行完成,此时重启的话就会导致请求中断,影响业务功能,优雅重启可以保证在停止的时候,不接收外部的新的请求,等...

    后端老鸟
  • 分布式限流之Redis+Lua实现

    【转载请注明出处】:https://cloud.tencent.com/developer/article/1623236

    后端老鸟
  • 使用Nacos做为SpringCloud的注册中心

    【转载请注明出处】:https://cloud.tencent.com/developer/article/1642606

    后端老鸟
  • 基于Node.js的Web Socket

    以前看到过很多关于node.js的文章,但一直没有尝试去搭建node.js的环境。这里有一篇文章叫“websocket与node.js的完美结合”

    meteoric
  • day65_Lucene学习笔记

    注意由于语言不同分析器的切分规则也不同,本例子使用StandardAnalyzer,它可以对用英文进行分词。 如下是org.apache.lucene.ana...

    黑泽君
  • 决策树算法原理及应用(详细版)

    C4.5是一系列用在机器学习和数据挖掘的分类问题中的算法。它的目标是监督学习:给定一个数据集,其中的每一个元组都能用一组属性值来描述,每一个元组属于一个互斥的类...

    智能算法
  • CSS 核心技能点

    Joel
  • 16、axios的使用与数据的mock2(使用proxyTable代理)

    (1)当你请求/api的时候,转发到http://localhost:8080这台端口号为8080的服务器上;

    Ewall
  • python高阶教程-python操作xlsx文件(openpyxl)

    本文由腾讯云+社区自动同步,原文地址 http://blogtest.stackoverflow.club/120/

    羽翰尘
  • Decorator 从原理到实践

    ES6 已经不必在过多介绍,在 ES6 之前,装饰器可能并没有那么重要,因为你只需要加一层 wrapper 就好了,但是现在,由于语法糖 class 的出现,当...

    Nealyang

扫码关注云+社区

领取腾讯云代金券