WebSocket知识分享
前提简介
什么是WebSocket
webSocket是一种网络通讯协议,与Htpp协议的区别是,Htpp通信只能由客户端发起,服务端响应的这种单向通信;而webSocket最大的特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,实现客户端与服务端的双向通讯。
WebSocket API
- WebSocket.onopen 连接成功的回调
- WebSocket.onclose 连接关闭后的回调
- WebSocket.onerror 连接失败后的回调
- WebSocket.onmessage 客户端接收服务端数据的回调
- WebSocket.readyState 当前连接状态
- WebSocket.bufferedAmount 未发送至服务器的二进制字节数
- WebSocket.binaryType 使用二进制的数据类型连接
- WebSocket.protocol 服务器选择的下属协议
- WebSocket.url WebSocket的绝对路径
方法
- WebSocket.close() 关闭当前连接
- WebSocket.send(data) 向服务器发送数据
其余特点
- 握手阶段采用HTTP协议
- 数据格式轻量,性能开销小
- 可以发送文本,和二进制数据
- 没有同源策略的限制
- webSocket协议的标识符是ws,加密的就是wss
http协议的标识符是http,加密的就是https
几种与服务端实时通信的方法
除了WebSocket外,还有其它与服务端建立实时通信的方式
前端代码部分(根据需求没有做太复杂的ws,使用的是原生的ws连接)
var url = 'ws://localhost:8080/rest/openingController/websocket'
var websocket = null
if ('WebSocket' in window) {
websocket = new WebSocket(url);
} else if ('MozWebSocket' in window) {
websocket = new MozWebSocket(url);
} else {
websocket = new SockJS(url);
}
websocket.onopen = onOpen;
websocket.onmessage = onMessage;
websocket.onerror = onError;
websocket.onclose = onClose;
function onOpen(result) {
console.log('连接建立时触发')
heartBeatCheck.start();
}
function onMessage(result) {
console.log('客户端接收服务端数据时触发')
}
function onError(result) {
console.log('通信发生错误时触发');
}
function onClose(result) {
console.log('连接关闭时触发');
}
function doSend(params) {
websocket.send(params);
console.log('客户端向服务端发送数据')
}
var ifCheckHeart = true;
var heartBeatCheck = {
timeout: 5000,
timeoutObj: null,
serverTimeoutObj: null,
start: function start() {
if (!ifCheckHeart) {
return false;
}
clearTimeout(heartBeatCheck.timeoutObj);
clearTimeout(heartBeatCheck.serverTimeoutObj);
heartBeatCheck.timeoutObj = setTimeout(function () {
websocket.send("client heart beat check");
heartBeatCheck.serverTimeoutObj = setTimeout(function () {
websocket.onClose();
}, heartBeatCheck.timeout);
}, heartBeatCheck.timeout);
}
}
window.onbeforeunload = function () {
websocket.onClose();
};
开发中遇到的疑难问题
这里记录开发中遇到的2个问题,大家后面遇到类似的问题,可以参考下,因为我觉得,这种问题算的上是一种共性问题
问题1
问题现象:
进入页面几秒后弹出连接断开
问题原因:
再谷歌、ie、360安全浏览器中建立ws均正常,但再13、14版本的苹果浏览器中,ws已经建立成功,但是因为苹果浏览器不支持ws原有的协议,所以再建立连接成功后又给断开了连接,错误码为1006 (注:此问题不光是苹果浏览器会出现,再一些低版本的浏览器中也会出现)
解决办法:
修改后端代码,代码/注释如下
if(request.getHeaders().containsKey("Sec-WebSocket-Extensions")){
request.getHeaders().set("Sec-WebSocket-Extensions", "permessage-deflate");
}
问题2
问题现象:
进入页面几秒后弹出连接断开,ws可以正常建立连接,但客户端和服务器端再发送或推送消息都失败
问题原因:
客户端发送ws连接请求后,服务器与客户端握手成功(建立成功),握手成功后服务端会有一个拦截器,服务端拦截器的作用是给当前请求过来的客户端打一个标识*(标识可以用token、sessionId或后端生成一个随机数等),以此来识别该用户; 因为每个浏览器的协议不同,所以再世界之窗浏览器上,服务端的拦截器获取请求头中的sessionId为空(后端方法:request.getHeaders() 获取到的 sessionId为空)* ,此时服务端识别不了是哪个用户,所以推送消息失败
解决办法:
即使后端通过request.getHeaders()获取 sessionId为空,但却可以从请求头的cookie中获取到token*(后端方法:request.getHeaders().get(“cookie”))*来标识发请求来的用户是谁,以此来解决该问题
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
System.out.println("Before Handshake");
logger.debug("Before Handshake");
if(request.getHeaders().containsKey("Sec-WebSocket-Extensions")){
request.getHeaders().set("Sec-WebSocket-Extensions", "permessage-deflate");
}
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession(false);
if (session != null) {
TSUser user = (TSUser) session.getAttribute(ResourceUtil.LOCAL_CLINET_USER);
if (user !=null) {
attributes.put(ResourceUtil.WEBSOCKET_USERID,user.getId());
} else {
String cookies = (String)request.getHeaders().get("cookie").get(0);
String token = cookies.substring(cookies.indexOf("token=")+5);
token = token.substring(0, token.indexOf(";"));
attributes.put(ResourceUtil.WEBSOCKET_USERID, token);
}
String wsType = servletRequest.getServletRequest().getParameter("ws_type");
String sectionId = servletRequest.getServletRequest().getParameter("section_id");
String role = servletRequest.getServletRequest().getParameter("role");
System.out.println("#############准备建立连接#############");
if (StringUtils.isNotBlank(wsType) && "openLobby".equals(wsType)) {
System.out.println("#############准备建立连接#############");
if (StringUtils.isNotBlank(sectionId) || StringUtils.isNotBlank(role)) {
System.out.println("ws_type:"+wsType);
System.out.println("section_id:"+sectionId);
System.out.println("role:"+role);
System.out.println("#############准备建立连接,满足连接条件#############");
attributes.put("ws_type",wsType);
attributes.put("section_id",sectionId);
attributes.put("role",role);
} else {
return false;
}
}
} else {
String cookies = (String)request.getHeaders().get("cookie").get(0);
String token = cookies.substring(cookies.indexOf("token=")+5);
token = token.substring(0, token.indexOf(";"));
attributes.put(ResourceUtil.WEBSOCKET_USERID, token);
}
}
return super.beforeHandshake(request, response, wsHandler, attributes);
}
|