webSocket如何解决自动关闭的意思
本文主要是解决webSocket自动关闭。
websocket它有一个“心跳”机制,但这个心跳机制是要程序自己去写代码实现的,websocket本身没有给你做这个东西。
它是如何自动关闭的呢?当电脑浏览器发送pong帧的时候,由于内容为空,于是服务器将空内容转发回去,导致客户端浏览器以为是错误的帧类型,发送关闭信息进行error关闭。(服务器返回时,必须要把它原来发来的东西发回去的,这是TCP/IP协议的要求)。
即然服务器接收到浏览器发送的"pong"后如果回复一个“pong”时,它会结束连接,那么为了避免这种事发生,可以在服务器接收到一个“pong”空信息时,不回复它的"pong",也就不关闭连接了。这是一种解决方案。
另一种解决方案是:当发生关闭时,可以主动发送心跳给对方,让连接复活,这样就相当于不断线了。本文就是用这个方案实现的。
第一步:在html文件中加入:
-----------------------------
<script type="text/javascript">
var ws;
//避免重复连接
var lockReconnect = false;
var wsUrl = "ws://localhost:8080/cl-market-camera-web/websocket";
createWebSocket(wsUrl);
function createWebSocket(url) {
try {
ws = new WebSocket(url);
initEventHandle();
} catch (e) {
//重新连接
reconnect(url);
}
}
//封装websocket的那几个接口函数
function initEventHandle() {
ws.onclose = function () {
console.info("连接关闭");
reconnect(wsUrl);
};
ws.onerror = function () {
console.info("传输异常");
reconnect(wsUrl);
};
ws.onopen = function () {
//心跳检测重置
heartCheck.reset().start();
};
websocket.onmessage = function(event) {
//console.info(event.data);
setMessageInnerHTML(event.data);
//如果获取到消息,心跳检测重置
heartCheck.reset().start();
}
}
function reconnect(url) {
if(lockReconnect) return;
lockReconnect = true;
//没连接上会一直重连,设置延迟避免请求过多
setTimeout(function () {
console.info("尝试重连..." + new Date().format("yyyy-MM-dd hh:mm:ss"));
createWebSocket(url);
lockReconnect = false;
}, 5000);
}
//心跳检测,每5s心跳一次
var heartCheck = {
timeout: 5000,
timeoutObj: null,
serverTimeoutObj: null,
reset: function(){
clearTimeout(this.timeoutObj);
clearTimeout(this.serverTimeoutObj);
return this;
},
start: function(){
var self = this;
this.timeoutObj = setTimeout(function(){
//这里发送一个心跳,后端收到后,返回一个心跳消息,
//onmessage拿到返回的心跳就说明连接正常
ws.send("HeartBeat" + new Date().format("yyyy-MM-dd hh:mm:ss"));
console.info("客户端发送心跳:" + new Date().format("yyyy-MM-dd hh:mm:ss"));
self.serverTimeoutObj = setTimeout(function(){
//如果超过一定时间还没重置,说明后端主动断开了
ws.close();
//如果onclose会执行reconnect,我们执行ws.close()就行了.
//如果直接执行reconnect 会触发onclose导致重连两次
}, self.timeout)
}, this.timeout)
}
}
//js中格式化日期,调用的时候直接:new Date().format("yyyy-MM-dd hh:mm:ss")
Date.prototype.format = function(fmt) {
var o = {
"M+" : this.getMonth()+1, //月份
"d+" : this.getDate(), //日
"h+" : this.getHours(), //小时
"m+" : this.getMinutes(), //分
"s+" : this.getSeconds(), //秒
"q+" : Math.floor((this.getMonth()+3)/3), //季度
"S" : this.getMilliseconds() //毫秒
};
if(/(y+)/.test(fmt)) {
fmt=fmt.replace(RegExp.$1, (this.getFullYear()+"").substr(4 - RegExp.$1.length));
}
for(var k in o) {
if(new RegExp("("+ k +")").test(fmt)){
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length)));
}
}
return fmt;
}
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
</script>
第二步:在java的controller包中写一个WebSocketTest类
------------------------------
package com.clmarket.controller;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
@ServerEndpoint("/websocket")
public class WebSocketTest {
//设置连接数
private static int onlineCount = 0;
private static CopyOnWriteArraySet<WebSocketTest> webSocketSet =
new CopyOnWriteArraySet<WebSocketTest>();
private Session session;
public Session getSession(){
return this.session;
}
@OnOpen
public void onOpen(Session session){
this.session = session;
webSocketSet.add(this); //加入set中
addOnlineCount(); //在线数加1
System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
}
@OnClose
public void onClose(){
webSocketSet.remove(this); //从set中删除
subOnlineCount(); //在线数减1
System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
}
/**
* 服务器向浏览器发送消息
* @param message 需要推送到浏览器的消息
* @param session 可选的参数
*/
@OnMessage
public void onMessage(String message, Session session) {
for(WebSocketTest item: webSocketSet){
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
continue;
}
}
}
/**
* 发生错误时调用
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error){
System.out.println("发生错误");
error.printStackTrace();
}
/**
* 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。
* @param message
* @throws IOException
*/
public void sendMessage(String message) throws IOException{
this.session.getBasicRemote().sendText(message);
//this.session.getAsyncRemote().sendText(message);
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketTest.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketTest.onlineCount--;
}
}
第三步:如何把自己的信息借助上面的WebSocketTest类推送出去
-----------------------------
比如服务器有一个字符串“鄂H AAAAA8”这样的字符串,要把它推到浏览器里显示出来,
其实只要三句话搞定:
String license=“鄂H AAAAA8”;
//new一个WebSocketTest对象,表示我要用它来发送
WebSocketTest wst=new WebSocketTest();
//这个session实际上是import javax.websocket.Session;
Session session=wst.getSession();
//调用这个webSocketTest对象的onMessage就可以把license发送出去了。
wst.onMessage(license, session);
-