昨天公司派给我一个任务 让我跟后端联调webSocket。之前没接触过这个东西,就记录下来
用通俗的话来讲的话 传统HTTP协议 如果请求数据或者信息,只能客户端发送数据给服务端。然后服务端接收数据并响应给客户端,说白了就是有缺陷。只能客户端主动。服务端是被动的。然而webSocket出现了。webSocket的出现从而解决了这一现象。不仅客户端主动,服务端也能主动。说白了讲webSocket就是客户端与服务端一种双向的通讯协议。不仅优化传送数据的带宽。而且解决了传统出现的缺陷这一难题。下面放代码
前端代码
<!DOCTYPE HTML>
<html>
<head>
<title>大屏B</title>
</head>
<body>
Welcome<br/>
<input id="text" type="text" /><button onclick="send()">Send</button> <button onclick="closeWebSocket()">Close</button>
<div id="message">
</div>
</body>
<script type="text/javascript">
var websocket = null;
var wsGroupId = "20191009170924631538723061895168";// 部门id,区别分组
var type = "screenB"// 相当于标识,区分屏幕,同一个分组从A-Z代表26个屏幕
var url = "ws://192.168.50.174:8011/ws/" + wsGroupId + "/" + type;
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket(url);
} else {
alert('Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function() {
// setMessageInnerHTML("error");
console.log(getNowTime() + ' 发生异常了');
reconnect(url);
};
//连接成功建立的回调方法
websocket.onopen = function(event) {
//setMessageInnerHTML("open");
console.log(getNowTime() + " Socket 已打开");
send("heartbeat");
//心跳检测重置
heartCheck.start()
}
//接收到消息的回调方法
websocket.onmessage = function(event) {
//websocket.setMessageInnerHTML(event.data);
// 维持心跳
heartCheck.start();
if (event.data != 'heartbeat') {
document.getElementById("text").value = event.data;
console.log(event.data)
}
//window.location.href="http://www.jb51.net";
}
//连接关闭的回调方法
websocket.onclose = function() {
// websocket.setMessageInnerHTML("close");
console.log(getNowTime() + " Socket已关闭");
reconnect(url)
}
// //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function() {
websocket.close();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//关闭连接
function closeWebSocket() {
websocket.close();
lockReconnect = false;
reconnect(url);
}
//发送消息
function send(message) {
// var message = document.getElementById('text').value;
websocket.send(message);
}
var lockReconnect = false; //避免重复连接
//重试连接socket
function reconnect(url) {
if (lockReconnect) {
return;
};
lockReconnect = true;
//没连接上会一直重连,设置延迟避免请求过多
tt && clearTimeout(tt);
tt = setTimeout(function() {
createWebSocket(url);
lockReconnect = false;
}, 10000);
}
//心跳检测
var heartCheck = {
timeout: 5000,
timeoutObj: null,
serverTimeoutObj: null,
start: function() {
console.log(getNowTime() + " Socket 心跳检测");
var self = this;
this.timeoutObj && clearTimeout(this.timeoutObj);
this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
this.timeoutObj = setTimeout(function() {
//这里发送一个心跳,后端收到后,返回一个心跳消息,
//onmessage拿到返回的心跳就说明连接正常
console.log(getNowTime() + ' Socket 连接重试');
send("heartbeat");
self.serverTimeoutObj = setTimeout(function() {
console.log(this);
close();
}, self.timeout);
}, this.timeout)
}
}
/**
* 获取系统当前时间
* @returns
*/
function p(s) {
return s < 10 ? '0' + s : s;
}
function getNowTime() {
var myDate = new Date();
//获取当前年
var year = myDate.getFullYear();
//获取当前月
var month = myDate.getMonth() + 1;
//获取当前日
var date = myDate.getDate();
var h = myDate.getHours(); //获取当前小时数(0-23)
var m = myDate.getMinutes(); //获取当前分钟数(0-59)
var s = myDate.getSeconds();
return year + '-' + p(month) + "-" + p(date) + " " + p(h) + ':' + p(m) + ":" + p(s);
}
</script>
</html>
付java代码
package com.example.springtest01.socket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author feiyang
*/
@ServerEndpoint(value = "/ws/{wsGroupId}/{type}")
@Component
public class WebSocket {
private static final Logger logger = LoggerFactory.getLogger(WebSocket.class);
private static SessionStorage sessionStorage = SessionStorage.getInstance();
private static Map<String, String> sessionGroup = new ConcurrentHashMap<>(16);
/**
* 连接建立成功调用的方法
* @param session
* @param type
* @param wsGroupId
* @throws Exception
*/
@OnOpen
public void onOpen(@PathParam("wsGroupId") String wsGroupId, @PathParam("type") String type, Session session) {
sessionGroup.put(session.getId(), wsGroupId);
sessionStorage.putSession(session, wsGroupId, type);
session.setMaxIdleTimeout(3600000);
logger.info("websocket连接成功 sessionId:" + session.getId());
logger.info("websocket连接成功 type:" + type);
logger.info("websocket连接成功 wsGroupId:" + wsGroupId);
logger.info("websocket链接数量:" + sessionGroup.size());
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(@PathParam("wsGroupId") String wsGroupId, @PathParam("type") String type, Session session) {
logger.info("websocket连接关闭成功 sessionId:" + session.getId());
logger.info(type);
logger.info(sessionGroup.get(session.getId()));
SessionStorage.getInstance().removeSession(sessionGroup.get(session.getId()), type);
}
/**
* 收到客户端消息后调用的方法
* @param message
* @param session
*/
@OnMessage
public void onMessage(@PathParam("wsGroupId") String wsGroupId, @PathParam("type") String type,String message, Session session) {
try {
logger.info(message);
sessionStorage.sendTextSingle( wsGroupId, type, message);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 发生错误时调用
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
logger.info("websocket连接失败 sessionId:" + session.getId());
}
}
package com.example.springtest01.socket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.util.StringUtils;
import javax.websocket.Session;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
*
* 作为对外的工具类
* @author zhoukx
*/
public class SessionStorage {
private Map<String, Map<String, Session>> sessionStore = new ConcurrentHashMap<>();
private static volatile SessionStorage storage;
private static final Logger logger = LoggerFactory.getLogger(SessionStorage.class);
private static final String FLAG_HEART_BEAT = "heartbeat";
private SessionStorage() {
}
/**
* 双端检索机制创建一个 SessionStorage 实例
* @return
*/
public static synchronized SessionStorage getInstance() {
if(storage == null) {
synchronized(SessionStorage.class) {
if(storage == null) {
storage = new SessionStorage();
}
}
}
return storage;
}
/**
* 将ws连接会话分组保存
* @author feiyang
* @param session
* @param wsGroupId
* @param type
* @return void
* @date 2019/9/19
* @throws
*/
public void putSession(Session session, String wsGroupId, String type) {
if(session != null) {
Map<String, Session> sessionGroup = this.sessionStore.get(wsGroupId);
if (sessionGroup == null) {
sessionGroup = new ConcurrentHashMap<>(16);
this.sessionStore.put(wsGroupId, sessionGroup);
}
sessionGroup.put(type, session);
}
}
/**
* ws连接关闭时删除会话
* @author feiyang
* @param wsGroupId
* @param type
* @return void
* @date 2019/9/19
* @throws
*/
public void removeSession(String wsGroupId, String type) {
Map<String, Session> sessionGroup = sessionStore.get(wsGroupId);
sessionGroup.remove(type);
if (sessionGroup.isEmpty()) {
this.sessionStore.remove(wsGroupId);
}
}
/**
* 获取指定ws连接会话
* @author feiyang
* @param wsGroupId
* @param type
* @return javax.websocket.Session
* @date 2019/9/19
* @throws
*/
public Session getSession(String wsGroupId, String type) {
Map<String, Session> sessionGroup = this.sessionStore.get(wsGroupId);
return sessionGroup.get(type);
}
/**
* 获取指定ws连接会话组全部session
* @author feiyang
* @param wsGroupId
* @return java.util.List<javax.websocket.Session>
* @date 2019/9/19
* @throws
*/
public List<Session> getSessions(String wsGroupId) {
Session[] sessions = new Session[]{};
Map<String, Session> sessionGroup = this.sessionStore.get(wsGroupId);
return Arrays.asList(sessionGroup.values().toArray(sessions));
}
/**
* 获取全部的会话
* @author feiyang
* @param
* @return java.util.List<javax.websocket.Session>
* @date 2019/9/19
* @throws
*/
public List<Session> getAllSession() {
List<Session> sessionList = new ArrayList<>();
for (Map<String, Session> sessionGroup : sessionStore.values()) {
Session[] sessions = new Session[]{};
sessionList.addAll(Arrays.asList(sessionGroup.values().toArray(sessions)));
}
return sessionList;
}
/**
* 针对单个会话发送文本消息
* @author feiyang
* @param wsGroupId
* @param type
* @param text
* @return void
* @date 2019/9/19
* @throws
*/
@Async
public void sendTextSingle(String wsGroupId, String type, String text){
try {
Session session = getSession(wsGroupId, type);
if (session.isOpen()) {
session.getBasicRemote().sendText(text);
if (text.equals(FLAG_HEART_BEAT)) {
logger.info("socket维持链接状态:" + text);
} else {
logger.info("已发送》》》》》" + text);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 针对会话组发送文本信息
* @author feiyang
* @param wsGroupId
* @param text
* @return void
* @date 2019/9/19
* @throws
*/
@Async
public void sendTextGroup(String wsGroupId, String text) {
try {
List<Session> sessions = getSessions(wsGroupId);
for(Session session : sessions) {
if (session.isOpen()) {
session.getBasicRemote().sendText(text);
logger.info("已发送》》》》》" + text);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
// /**
// * 针对全部会话发送消息
// * @author feiyang
// * @param text
// * @return void
// * @date 2019/9/19
// * @throws
// */
// public void sendTextAll(String text) {
// try {
// List<Session> sessions = getAllSession();
// for(Session session : sessions) {
// if (session.isOpen()) {
// session.getBasicRemote().sendText(text);
// logger.info("已发送》》》》》" + text);
// }
// }
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
}
package com.example.springtest01.socket;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @Author :feiyang
* @Date :Created in 4:03 PM 2019/9/18
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter (){
return new ServerEndpointExporter();
}
}