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

    由于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.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>

      

       


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值