由于HTTP请求的无状态性,且HTTP请求必须由客户端发起,所以在老版的Web开发中都如果要实现服务端向客户端推送消息都是使用轮询的方式。但这种方式会使服务端的压力过大,因为有N个客户端访问就会有N个客户端不停轮询请求。这样的网站性能必然大打折扣。
所以HTML5提出了一个新的协议——WebSocket。WebSocket的原理就是客户端通过与服务端的一次握手就建立长久的连接通道,于是当服务器需要向客户端推送消息的时候只需要通过已经建立的会话通道,向需要发送消息的客户端发送消息即可。同样的对于已经建立会话通道的客户端与服务端,客户端也可以通过会话通道发送消息到服务端,也就是说他们是"全双工"的。
但是由于浏览器的历史遗留性问题,不是所有的浏览器都是支持WebSocket的,尤其是IE10以下,所以才出现了SockJS这样一个框架,它的原理也很简单,就是如果你的浏览器支持WebSocket那么他就使用webSocket协议通信,入股不支持就使用流传输或者轮询的方式,这样也保证了资源的最大利用率。
下面就是WebSocket的请求头:
Accept-Encoding:gzip, deflate, sdch
Accept-Language:zh-CN,zh;q=0.8
Cache-Control:no-cache
Connection:Upgrade
Cookie:JSESSIONID=7F788B04BFFF4186D7387BD9DAA0DDE2
Host:192.168.1.62:8080
Origin:http://192.168.1.62:8080
Pragma:no-cache
Sec-WebSocket-Extensions:permessage-deflate; client_max_window_bits
Sec-WebSocket-Key:8O2SDdMk/08Tfg3SuTjzXA==
Sec-WebSocket-Version:13
Upgrade:websocket
可以看到,红色部分就是WebSocket的请求所特有的。
代码实现:(基于Spring-mvc和sockJS-0.3.4)
Java代码:
1.WebSocketConfig----用于配置WebSocket地址
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebMvc
@EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
public WebSocketConfig() {
}
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
/*
* 将/websck的请求绑定到systemWebSocketHandler处理器
* */
registry.addHandler(systemWebSocketHandler(), "/websck").addInterceptors(new HandshakeInterceptor());
System.out.println("registed!");
registry.addHandler(systemWebSocketHandler(), "/sockjs/websck/info").addInterceptors(new HandshakeInterceptor()).withSockJS();
}
@Bean
public WebSocketHandler systemWebSocketHandler() {
return new SystemWebSocketHandler();
}
}
2.HandshakeInterceptor---握手拦截器,可以再此处限制某些握手请求或生成Log
import java.util.Map;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
/**
*
* Title:HandshakeInterceptor
*
*
* Description
*
*
* @author 陆仁杰
* @date 2015年7月27日
*/
@Component
public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception {
System.out.println("Before Handshake");
return super.beforeHandshake(request, response, wsHandler, attributes);
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {
System.out.println("After Handshake");
super.afterHandshake(request, response, wsHandler, ex);
}
}
3.SystemWebSocketHandler-------核心类,WebSocket消息处理、主动发消息
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.PongMessage;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
import com.yzXJ.MQ.SendMessageToMQ;
/**
*
* Title:SystemWebSocketHandler
*
*
* Description To change this license header, choose License Headers in Project
* Properties. To change this template file, choose Tools | Templates and open
* the template in the editor.
*
*
* @author 陆仁杰
* @date 2015年7月27日
*/
@Component
public class SystemWebSocketHandler implements WebSocketHandler {
private static final Logger logger = Logger.getLogger(SystemWebSocketHandler.class);
private SendMessageToMQ send = null;
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {//连接建立以后自动调用的方法
logger.info("websocket connection success......");
session.sendMessage(new TextMessage("You have connect successfully!"));//向用户主动推送消息
SendMessageIm.addSessions(session);//用于保存所有连接用户信息
}
@Override
public void handleMessage(WebSocketSession wss, WebSocketMessage> wsm) throws Exception {//用户发送消息会调用的方法
String command = "";
try {
if (wsm instanceof PongMessage) {//WebSocket连接会有心跳消息,所以需要过滤
return;
} else {
command = (String) wsm.getPayload();//接收来自用户的消息
logger.info(command);//打印该消息
}
} catch (Exception e) {
logger.error("Error message:",e);
return;
}
}
@Override
public void handleTransportError(WebSocketSession wss, Throwable thrwbl) throws Exception {//异常的处理
if (wss.isOpen()) {
SendMessageIm.removeSession(wss);//移除该用户会话
wss.close();
}
logger.error("websocket connection closed......handleTransportError" ,thrwbl);
}
@Override
public void afterConnectionClosed(WebSocketSession wss, CloseStatus cs) throws Exception {//连接关闭会调用的方法
logger.error("websocket connection closed......afterConnectionClosed" + cs.getCode() + "reson" + cs.getReason());
if (wss.isOpen()) {
SendMessageIm.removeSession(wss);
wss.close();
}
}
@Override
public boolean supportsPartialMessages() {
return false;
}
}
以下是JS代码:
var ws = null;
var url = null;
var transports = [];
function setConnected(connected) {
document.getElementById('connect').disabled = connected;
document.getElementById('disconnect').disabled = !connected;
document.getElementById('echo').disabled = !connected;
}
function connect() {
if (!url) {
alert('Select whether to use W3C WebSocket or SockJS');
return;
}
url="http://192.168.1.62:8080/yzXJ/sockjs/websck/info";
ws= new SockJS(url, undefined, {protocols_whitelist: transports});//建立连接
ws.onopen = function() {//连接建立以后自动调用的方法
alert('open');
log('Info: connection opened.');
};
ws.onmessage = function(event) {//后台有消息自动调用的方法
alert('Received:' + event.data);
log('Received: ' + event.data);
};
ws.onclose = function(event) {//连接关闭自动调用的方法
setConnected(false);
log('Info: connection closed.');
log(event);
};
}
function sayMarco() {
ws.send("aaaa");//由客户端向服务器发消息的方法
}
注意:如果客户端是IE10以下或者某些不支持WebSocket的浏览器,那么就有可能会出现代码2000的客户端错误,解决办法是将服务器换成Tomcat8 ,并对项目的web.xml进行修改:将web.xml的servlet配置和Filter配置都加上同步支持
yzXJ
springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
true
springSecurityFilterChain
/*
contextConfigLocation
/WEB-INF/classes/applicationContext.xml
/WEB-INF/classes/spring-security.xml
log4jConfigLocation
/WEB-INF/classes/log4j.properties
log4jRefreshInterval
6000
org.springframework.web.util.Log4jConfigListener
default
*.html
CharacterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
true
encoding
UTF-8
forceEncoding
true
CharacterEncodingFilter
/*
org.springframework.web.context.ContextLoaderListener
yzXJ
org.springframework.web.servlet.DispatcherServlet
1
true
yzXJ
/
/accident/monitor
404
/WEB-INF/views/error.html
30