项目需要弹窗告警,原本采用comet4j的方式进行,但是后来发现不支持tomcat8.5,于是打算使用webSocket的方式实现,webSocket是浏览器客户端和服务器后台实现的一种全双工通信方式,许多网页聊天工具都是采用该方式进行。
本地开发的时候都可以正常使用,但是在部署到nginx代理服务器的时候发现报了错误,连不上
Error in connection establishment: net::ERR_NAME_NOT_RESOLVED
后来发现是nginx服务器默认是不打开webSocket的功能的,这需要我们在nginx服务器上配置:
location /test/ {
proxy_pass http://test.com;
proxy_redirect default;
proxy_set_header Upgrade $http_upgrade; # allow websockets
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
}
另外nginx设置了连接超时时间或者读取超时时间的时候,websoket会中断,那么需要我们维护socket连接,断线自动重连,代码如下,
websocket后台:
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import net.sf.json.JSONObject;
@ServerEndpoint("/websocket/{sid}")
@Component
public class WebSocketServer {
static Logger log=LoggerFactory.getLogger(WebSocketServer.class);
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
private static CopyOnWriteArraySet webSocketSet = new CopyOnWriteArraySet();
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
//接收sid
private String sid="";
/**
* 连接建立成功调用的方法*/
@OnOpen
public 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异常");
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this); //从set中删除
subOnlineCount(); //在线数减1
log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("收到来自窗口"+sid+"的信息:"+message);
//群发消息
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
log.error(e.toString());
e.printStackTrace();
}
}
}
/**
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误");
error.printStackTrace();
}
/**
* 实现服务器主动推送
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 发送告警消息
* */
public void sendMessage(Map dataMap)throws IOException{
try{if(dataMap!=null){
JSONObject jsonObject = JSONObject.fromObject(dataMap);
StringBuilder builder = new StringBuilder(jsonObject.toString());
//发送告警到前台
this.session.getBasicRemote().sendText(builder.toString());
log.info("发送成功");
}
}catch(Exception e) {
log.error(e.toString());
}
}
/**
* 群发自定义消息
* */
public static void sendInfo(String message, String sid) throws IOException {
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(message);
}
} catch (IOException e) {
log.error(e.toString());
continue;
}
}
}
/**
* 群发自定义消息
* */
public static void sendInfoMap(Map dataMap, String sid) throws IOException {
log.info("推送消息到窗口"+sid+",推送内容:"+dataMap.toString());
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(dataMap);
//这里可以设定只推送给这个sid的,为null则全部推送暂时全部放开
/*if(sid==null) {
item.sendMessage(dataMap);
}else if(item.sid.equals(sid)){
item.sendMessage(dataMap);
}*/
} catch (IOException e) {
log.error(e.toString());
continue;
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
前台页面以及js:
var websocket_connected_count = 0;
var onclose_connected_count = 0;
/**websocekt*/
function webSocketClient(){
var socket;
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else{
console.log("您的浏览器支持WebSocket");
//httprequest请求id
var sid = "<%=requestId%>";
//实现化WebSocket对象,指定要连接的服务器地址与端口 建立连接
socket =new WebSocket("ws://127.0.0.1:8080/butlerBf/websocket/"+sid);
//打开事件
socket.onopen = function() {
console.log("Socket 已打开");
//socket.send("这是来自客户端的消息" + location.href + new Date());
};
//获得消息事件
socket.onmessage = function(msg) {
if(msg.data!="您的浏览器支持WebSocket"&&msg.data!="Socket 已打开"&&msg.data!="连接成功"&&msg.data!="ping"&&msg.data!=""){
console.log(msg);
checkAuthority(msg.data);
heartCheck.reset().start();
}
};
//关闭事件
socket.onclose = function(e) {
console.log("Socket已关闭");
console.log(e);
};
//发生了错误事件
socket.onerror = function() {
websocket_connected_count++;
if(websocket_connected_count <= 5){
webSocketClient()
}
console.log("Socket发生了错误");
//此时可以尝试刷新页面
}
//离开页面时,关闭socket
//jquery1.8中已经被废弃,3.0中已经移除
$(window).unload(function(){
socket.close();
});
}
// 心跳检测, 每隔一段时间检测连接状态,如果处于连接中,就向server端主动发送消息,来重置server端与客户端的最大连接时间,如果已经断开了,发起重连。
var heartCheck = {
timeout: 60000, // 60s发一次心跳,比server端设置的连接时间稍微小一点,在接近断开的情况下以通信的方式去重置连接时间。
serverTimeoutObj: null,
reset: function(){
clearTimeout(this.timeoutObj);
clearTimeout(this.serverTimeoutObj);
return this;
},
start: function(){
var self = this;
this.serverTimeoutObj = setInterval(function(){
if(socket.readyState == 1){
console.log("连接状态,发送消息保持连接");
socket.send("ping");
heartCheck.reset().start(); // 如果获取到消息,说明连接是正常的,重置心跳检测
}else{
console.log("断开状态,尝试重连");
webSocketClient();
}
}, this.timeout)
}
}
}
此外补充一点,如果nginx没有设置如下读取超时时间,websocket会一直断线重连,比较消耗内存,建议设置长一点:
location /test{
root html;
proxy_pass http://test.com;
proxy_set_header Upgrade $http_upgrade; # allow websockets
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
proxy_connect_timeout 60s;#l连接超时时间,不能设置太长会浪费连接资源
proxy_read_timeout 500s;#读超时时间
proxy_send_timeout 500s;#写超时时间
index index.html index.htm;
}