websocket技术分享

时间:2022-02-06 01:12:17

开发环境

spring3+tomcat7+spring-websocket4 + spring security3

运行环境

windows、Linux

一、背景:

产品将要发布的消息或其他需要让客户提前知道的消息,在客户端和服务端建立连接后推送

给客户端。

二、WebSocket是什么

WebSocket协议是基于TCP的一种新的网络协议。

三、WebSocket身世挖掘

1、WebSocket是HTML5出的东西(协议),跟HTTP协议基本没有关系,只是为了兼容现有浏览器的握手规范而已

websocket技术分享

2、HTTP有1.1和1.0之说,也就是所谓的keep-alive,把多个HTTP请求合并为一个

3、Websocket是一个持久化的协议,相对于HTTP这种非持久的协议来说

4、典型的Websocket握手

Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Version: 13
Origin: http://example.com
5、实现了websocket的浏览器
websocket技术分享
Chrome Supported in version 4+
 Firefox  Supported in version 4+
 Opera  Supported in version 10+
 Safari  Supported in version 5+
 IE(Internet Explorer)  Supported in version 10+

四、WebSocket的特点

1、长连接,实现双向通信, 具有底层socket的特点,实现真正意义上的推送功能

2、是HTML5的技术之一,具有巨大的应用前景

3、节约带宽,节省服务器资源

缺点:

少部分浏览器不支持,浏览器支持的程度与方式有区别

五、WebSocket如何用

前端代码:

 var DJCW_webSocket = (function(){
var webSocket = null,
tryTime = 0,
initSocket; initSocket = function(){
debugger
var _marquee = "<marquee behavior='scroll' direction='left' behavior='scroll'>"; if (!window.WebSocket) {
alert("您的浏览器不支持websocket!");
return false;
} webSocket = new WebSocket('ws://127.0.0.1:8082/projectname/websocket'); // 收到服务端消息
webSocket.onmessage = function(msg) {
DJCW.messagesScroll(msg.data);
}; // 异常
webSocket.onerror = function(event) {}; // 建立连接
webSocket.onopen = function(event) {}; // 断线重连
webSocket.onclose = function() {
// 重试10次,每次之间间隔10秒
if (tryTime < 10) {
setTimeout(function() {
webSocket = null;
tryTime++;
initSocket();
}, 500);
} else {
tryTime = 0;
}
};
}; initModule = function() {
initSocket();
};
return {
initSocket:initSocket,
initModule : initModule
};
})(); $(function() {
DJCW_webSocket.initModule();
window.onbeforeunload = function() {
// 离开页面时的其他操作
};
});

后端代码:

 import java.io.IOException;
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.ServerEndpoint; import org.springframework.stereotype.Service; /**
* 功能说明:websocket处理类, 使用J2EE7的标准 切忌直接在该连接处理类中加入业务处理代码 作者:ydd(2017-04-12 15:29)
*/
// relationId和userCode是我的业务标识参数,websocket是连接的路径,可以自行定义
@ServerEndpoint("/websocket")
@Service
public class WebsocketEndPoint { private static final Log LOG = Log.getLogger(WebsocketEndPoint.class); private static CopyOnWriteArraySet<WebsocketEndPoint> sessions = new CopyOnWriteArraySet<WebsocketEndPoint>();
private Session session; public WebsocketEndPoint() {
} /**
* @Title: onOpen
* @Description: (打开连接时触发)
* @param @param session 设定文件
* @return void 返回类型
* @throws
*/
@OnOpen
public void onOpen(Session session) {
this.session = session;
sessions.add(this);
} /**
* @Title: onMessage
* @Description: (收到客户端消息时触发)
* @param @param message
* @param @return 设定文件
* @return String 返回类型
* @throws
*/
@OnMessage
public String onMessage(String message) {
return "Got your message (" + message + ").Thanks !";
} /**
* @Title: onError
* @Description: (异常时触发)
* @param @param throwable
* @param @param session 设定文件
* @return void 返回类型
* @throws
*/
@OnError
public void onError(Throwable throwable, Session session) {
LOG.e("Chat Error: " + throwable.toString());
} /**
* @Title: onClose
* @Description: (关闭连接时触发)
* @param @param session 设定文件
* @return void 返回类型
* @throws
*/
@OnClose
public void onClose(Session session) {
sessions.remove(this);
} /**
* 向所有用户发送消息
*
* @param msg
*/
public void sendUser(String msg) {
try {
if (sessions.size() != 0) {
for (WebsocketEndPoint s : sessions) {
if (s != null) {
s.session.getBasicRemote().sendText(msg);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
} }

六、WebSocket应用场景

在线聊天室、在线客服系统、评论系统、WebIM等

七、WebSocket的实现原理

在实现websocket连线过程中,需要通过浏览器发出websocket连线请求,然后服务器发出回应,这个过程通常称为“握手” (handshaking)。
websocket技术分享
在 WebSocket API,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数
据互相传送。在此WebSocket 协议中,为我们实现即时服务带来了两大好处:
1. Header
互相沟通的Header是很小的-大概只有 2 Bytes
2. Server Push
服务器的推送,服务器不再被动的接收到浏览器的request之后才返回数据,而是在有新数据时就主动推送给浏览器。

WebSocket和传统的HTTP交互方式的区别如下图:

websocket技术分享

八、遇到的错误

2、Message will not be sent because the WebSocket session has been closed
服务端不正常关闭后,在后台的OnError的方法中抛出的异常,可以不抛出异常
3、spring security3与WebSocket结合使用时报302循环重定向错误
原因
springsecurity的过滤器DelegatingFilterProxy和springMVC的过滤器DispatcherServlet
冲突所导致的。
解决方案(只将spring security的url-pattern中的/*改为具体的对指定的相关请求进行拦截就可以了)
     <!-- spring security -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<!-- 由于加入了websocket,不能写成/*,不然会出现302循环重定向,因为和 DispatcherServlet相冲突-->
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>*.js</url-pattern>
<url-pattern>*.jsp</url-pattern>
<!--通过SecurityContextHolder.getContext().getAuthentication().getPrincipal()可以获取到用户名 -->
<url-pattern>/login/</url-pattern>
<!-- 登录时springsecurity自带的类进行用户密码认证以及赋予相关权限 -->
<url-pattern>/j_spring_security_check</url-pattern>
<!-- 退出时通过springsecurity自带的url进行退出并清除session -->
<url-pattern>/j_spring_security_logout</url-pattern>
</filter-mapping>

九、头脑风暴

1、在同一浏览器同时打开同一产品带有ws协议请求的多个标签页,是否都会走onClose方法(即删除相应的session)

经测试是会的

2、static CopyOnWriteArraySet<WebsocketEndPoint> sessions = new CopyOnWriteArraySet<WebsocketEndPoint>();
每次加载类的时候不会new出一个新的实例吗,这样的话怎么实现的将消息推送给所有用户呢

不会每次都new出一个新的实例

static变量

  按照是否静态的对类成员变量进行分类可分两种:一种是被static修饰的变量,叫静态变量或类变量;另一种是没有被static修饰的变量,叫实例变量。

  两者的区别是:

  对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),
当然也可以通过对象来访问(但是这是不推荐的)。

  对于实例变量,没创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响(灵活)。

CopyOnWriteArraySet介绍:
属于java.util.concurrent包下的,java.util.concurrent 包添加了多个新的线程安全集合类(ConcurrentHashMap、CopyOnWriteArrayList 和

CopyOnWriteArraySet)这些类的目的是提供高性能、高度可伸缩性、线程安全的基本集合类型版本,通过同步的封装工厂(Collections.synchronizedMap()、

synchronizedList() 和 synchronizedSet()),非线程安全集合均可表现为线程安全的