【转载请注明出处】: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