JAVA整合WebSocket实现服务器消息推送(项目源码)
一.WebSocket简单介绍随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了。近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的全双工通信,扩展了浏览器与服务端的通信功能,使服务端也能主动向客户端发送数据。
我们知道,传统的HTTP协议是无状态的,每次请求(request)都要由客户端(如 浏览器)主动发起,服务端进行处理后返回response结果,而服务端很难主动向客户端发送数据;这种客户端是主动方,服务端是被动方的传统Web模式 对于信息变化不频繁的Web应用来说造成的麻烦较小,而对于涉及实时信息的Web应用却带来了很大的不便,如带有即时通信、实时数据、订阅推送等功能的应 用。在WebSocket规范提出之前,开发人员若要实现这些实时性较强的功能,经常会使用折衷的解决方法:轮询(polling)和Comet技术。其实后者本质上也是一种轮询,只不过有所改进。
轮询是最原始的实现实时Web应用的解决方案。轮询技术要求客户端以设定的时间间隔周期性地向服务端发送请求,频繁地查询是否有新的数据改动。明显地,这种方法会导致过多不必要的请求,浪费流量和服务器资源。
Comet技术又可以分为长轮询和流技术。长轮询改进了上述的轮询技术,减小了无用的请求。它会为某些数据设定过期时间,当数据过期后才会向服务端发送请求;这种机制适合数据的改动不是特别频繁的情况。流技术通常是指客户端使用一个隐藏的窗口与服务端建立一个HTTP长连接,服务端会不断更新连接状态以保持HTTP长连接存活;这样的话,服务端就可以通过这条长连接主动将数据发送给客户端;流技术在大并发环境下,可能会考验到服务端的性能。
这两种技术都是基于请求-应答模式,都不算是真正意义上的实时技术;它们的每一次请求、应答,都浪费了一定流量在相同的头部信息上,并且开发复杂度也较大。
伴随着HTML5推出的WebSocket,真正实现了Web的实时通信,使B/S模式具备了C/S模式的实时通信能力。WebSocket的工作流程是这 样的:浏览器通过JavaScript向服务端发出建立WebSocket连接的请求,在WebSocket连接建立成功后,客户端和服务端就可以通过 TCP连接传输数据。因为WebSocket连接本质上是TCP连接,不需要每次传输都带上重复的头部数据,所以它的数据传输量比轮询和Comet技术小 了很多。本文不详细地介绍WebSocket规范,主要介绍下WebSocket在Java Web中的实现。
JavaEE 7中出了JSR-356:Java API for WebSocket规范。不少Web容器,如Tomcat,Nginx,Jetty等都支持WebSocket。Tomcat从7.0.27开始支持 WebSocket,从7.0.47开始支持JSR-356,下面的Demo代码也是需要部署在Tomcat7.0.47以上的版本才能运行。
二、WebSocket协议介绍
WebSocket协议是一种双向通信协议,它建立在TCP之上,同http一样通过TCP来传输数据,但是它和http最大的不同有两点:1.WebSocket是一种双向通信协议,在建立连接后,WebSocket服务器和Browser/UA都能主动的向对方发送或接收数据,就像Socket一样,不同的是WebSocket是一种建立在Web基础上的一种简单模拟Socket的协议;2.WebSocket需要通过握手连接,类似于TCP它也需要客户端和服务器端进行握手连接,连接成功后才能相互通信。简单的建立握手的时序图如下:
握手过程:
Browser与WebSocket服务器通过TCP三次握手建立连接,如果这个建立连接失败,那么后面的过程就不会执行,Web应用程序将收到错误消息通知。
在TCP建立连接成功后,Browser/UA通过http协议传送WebSocket支持的版本号,协议的字版本号,原始地址,主机地址等等一些列字段给服务器端。
WebSocket服务器收到Browser/UA发送来的握手请求后,如果数据包数据和格式正确,客户端和服务器端的协议版本号匹配等等,就接受本次握手连接,并给出相应的数据回复,同样回复的数据包也是采用http协议传输。
Browser收到服务器回复的数据包后,如果数据包内容、格式都没有问题的话,就表示本次连接成功,触发onopen消息,此时Web开发者就可以在此时通过send接口想服务器发送数据。否则,握手连接失败,Web应用程序会收到onerror消息,并且能知道连接失败的原因。
三、Tomcat 7中的Websocket架构
如图所示,因为Websocket通信分为握手和数据传输两个过程,两个过程中需要用到的处理方式是不一样的,握手过程是基于HTTP 1.1基础上的,而数据传输是直接基于TCP的流传输。
握手过程中,在HttpServletRequest的基础上,封装了WsHttpServletRequest类,添加了对Request的失效操作函数invalidate()。而在数据通信时,接受和处理数据过程中,基于org.apache.coyote.http11.upgrade.UpgradeInbound重新封装了用于处理数据输入流的类StreamInbound,并在StreamInbound的基础上扩展生成了用于消息处理的类MessageInbound。在这两个数据处理类中均留有onData,onTextData/onBinaryData,onOpen,onClose等事件操作函数接口,这些接口将在载入的代码类中实现业务逻辑。在用于数据输出流的类WsOutbound则是封装了UpgradeOutbound对象实例,基于UpgradeOutbound对象的基础上,添加了websocket响应有关的处理逻辑。这里处理函数均为同步调用的函数,保证websocket响应的时序性。
Tomcat中Websocket的处理流程如下:
接收客户端发来的握手请求,Coyote.http11连接器对socket进行解析,形成HttpServletRequest发送给Container。
Container中的相应WebsocketServlet处理请求,如不接受连接请求,则返回,如接受连接请求,则对请求作出响应,建立起客户端和服务器的socket连接。
服务器此时可以通过WsOutbound发送数据给客户端,同时通过StreamInbound监听socket。
如果接收到客户端发来的数据,则将socket数据解析成frame,判断frame类型,通过事件分发数据到不同的逻辑处理流程。
数据返回时调用WsOutbound对返回的数据进行封装处理,发送给客户端。
四、代码实现以及需求
1、项目需要,定时向所有在线用户推送一个广告或是推送一个通知之类的(比如服务器升级,请保存好手头工作之类的)。
2、相关环境 , Nginx、tomcat7、centos 6.5
3、项目框架,springMvc 4.0.6、layer
4、代码实现:
WebSocketConfig:import websocket.handler.SystemWebSocketHandler;
@Configuration
@EnableWebMvc
@EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer{
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(systemWebSocketHandler(),"/webSocketServer");
registry.addHandler(systemWebSocketHandler(),"/sockjs/webSocketServer");
}
@Bean
public WebSocketHandler systemWebSocketHandler(){
return new SystemWebSocketHandler();
}
}SystemWebSocketHandler:
public class SystemWebSocketHandler extendsTextWebSocketHandler {
private static final ArrayList<WebSocketSession> users = new ArrayList<WebSocketSession>();;
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("ConnectionEstablished");
users.add(session);
System.out.println("当前用户"+users.size());
}
/**
* 在UI在用js调用websocket.send()时候,会调用该方法
* @Author 张志朋
* @param session
* @param message
* @throws Exception
* @Date 2016年3月4日
* 更新日志
* 2016年3月4日 张志朋首次创建
*
*/
@Override
protected void handleTextMessage(WebSocketSession session,
TextMessage message) throws Exception {
super.handleTextMessage(session, message);
sendMessageToUsers(session,message);
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws IOException {
if(session.isOpen()){
session.close();
}
users.remove(session);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
users.remove(session);
}
@Override
public boolean supportsPartialMessages() {
return false;
}
/**
* 给所有在线用户发送消息
* @Author 张志朋
* @param messagevoid
* @Date 2016年3月4日
* 更新日志
* 2016年3月4日 张志朋首次创建
*
*/
public void sendMessageToUsers(WebSocketSession session,TextMessage message) {
for (WebSocketSession user : users) {
try {
if (user.isOpen()) {
user.sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
信息输入 index.html:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>请输入任意消息</title>
<script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
<script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>
<script type="text/javascript">
var ws = null;
$(function () {
if ('WebSocket' in window) {
ws = new WebSocket('ws://127.0.0.1:8080/webSocketServer');
}
else if ('MozWebSocket' in window) {
ws = new MozWebSocket("ws://127.0.0.1:8080/webSocketServer");
}
else {
ws = new SockJS("ws://127.0.0.1:8080/webSocketServer");
}
ws.onopen = function () {
};
ws.onmessage = function (event) {
};
ws.onclose = function (event) {
};
});
function stop(){
var message = $("#message").val();
ws.send(message);
}
</script>
</head>
<body class="keBody">
请输入提示信息: <textarea id="message"></textarea><br />
<input type="button"value="开始" />
</body>
</html>
webSocket.js用于导入项目。document.write("<script language=javascript src='http://127.0.0.1:8080/js/jquery-1.10.2.min.js'></script>");
document.write("<script language=javascript src='http://127.0.0.1:8080/layer/layer.js'></script>");
document.write("<script language=javascript src='http://cdn.sockjs.org/sockjs-0.3.min.js'></script>");
var ws = null;
var basePath = "ws://127.0.0.1:8080/";
if ('WebSocket' in window) {
ws = new WebSocket(basePath+'webSocketServer');
}
else if ('MozWebSocket' in window) {
ws = new MozWebSocket(basePath+"webSocketServer");
}
else {
ws = new SockJS(basePath+"sockjs/webSocketServer");
}
ws.onopen = function () {
};
ws.onmessage = function (event) {
pop(event.data);
};
ws.onclose = function (event) {
ws.close();
};
//提示信息
function pop(message){
layer.alert(message);
}
5、在项目头部引入
<script language=javascript src='http://127.0.0.1:8080/webSocket.js '></script>
这时查看后台 会有以下信息 说明 引入成功。
然后在打开页面 index.html 输入以下内容 点击开始即可。
如果在网站出现一下提示说明配置成功,这时候所有网站登录用户都可以收到此信息。
项目下载地址:**** Hidden Message *****
有没有数据库?:o 刘继仆 发表于 2016-4-21 07:20
有没有数据库?
跟 数据库 没有关系 thanks you for share 下载来学习下。 看看新的内容! 谢谢楼主分享,学习!!谢谢楼主分享,学习!!谢谢楼主分享,学习!!谢谢楼主分享,学习!!谢谢楼主分享,学习!! 111111111111111111 beyond 发表于 2016-4-27 03:50
111111111111111111
我去 盗我 头像 还不好好回复 早想学这个了:D