您的位置:首页 > 编程语言 > Java开发

Spring-mvc+SockJS实现即时通讯教程,并提供各种浏览器版本的支持

2015-12-22 11:27 507 查看
    由于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;

/**
* <p>
* Title:HandshakeInterceptor
* </p>
* <p>
* Description
* <p>
*
* @author 陆仁杰
* @date 2015年7月27日
*/
@Component
public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor {

@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> 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;

/**
* <p>
* Title:SystemWebSocketHandler
* </p>
* <p>
* 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.
* <p>
*
* @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.
4000
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配置都加上同步支持

<async-supported>true</async-supported>,以下是样例配置:

      <?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>yzXJ</display-name>
<!-- SpringSecurity必须的filter -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<async-supported>true</async-supported>
</filter>

<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/classes/applicationContext.xml
/WEB-INF/classes/spring-security.xml
</param-value>
</context-param>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/classes/log4j.properties</param-value>
</context-param>
<context-param>
<param-name>log4jRefreshInterval</param-name>
<param-value>6000</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.util.Log4jConfigListener
</listener-class>
</listener>
<!--容器内重定向的html请求 -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<async-supported>true</async-supported>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
<servlet-name>yzXJ</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>yzXJ</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>/accident/monitor</welcome-file>
</welcome-file-list>
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/views/error.html</location>
</error-page>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
</web-app>

      

       
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息