zoukankan      html  css  js  c++  java
  • WebSocket 学习教程(二):Spring websocket实现消息推送

    ===============================================

    环境介绍:

    Jdk 1.7 (1.6不支持)

    Tomcat7.0.52 (支持Websocket协议)

    Spring4.0.26 (支持Websocket)

    web.xml(配置了前端自动优化HtmlCompressor和Druid监控),自动优化会影响Websocket js脚本,后面会讲

    =================================================

    配置步骤:

    1. 引入Spring相关Jar,特别需要下面这两个

            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-websocket</artifactId>
                <version>4.0.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-messaging</artifactId>
                <version>4.0.6.RELEASE</version>
            </dependency>

    2. 编写WebSocketConfig implements WebSocketConfigurer

    WebSocketConfig.java

    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.config.annotation.EnableWebSocket;
    import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
    import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
    import org.springframework.web.socket.handler.TextWebSocketHandler;
    
    import cn.com.ship.message.handler.ChatMessageHandler;
    import cn.com.ship.message.handler.TextMessageHandler;
    
    @Configuration
    //@EnableWebMvc//这个标注可以不加,如果有加,要extends WebMvcConfigurerAdapter
    @EnableWebSocket
    public class WebSocketConfig implements WebSocketConfigurer {
        @Override
        public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
            registry.addHandler(chatMessageHandler(),"/websocket/chatMessageServer.do").addInterceptors(new ChatHandshakeInterceptor());
            registry.addHandler(chatMessageHandler(), "/sockjs/chatMessageServer.do").addInterceptors(new ChatHandshakeInterceptor()).withSockJS();
        }
     
        @Bean
        public TextWebSocketHandler chatMessageHandler(){
            return new ChatMessageHandler();
        }
    
    }

     3. 编写ChatMessageHandler extends TextWebSocketHandler

    ChatMessageHandler.java

    import java.io.IOException;
    import java.util.ArrayList;
    
    import org.apache.log4j.Logger;
    import org.springframework.web.socket.CloseStatus;
    import org.springframework.web.socket.TextMessage;
    import org.springframework.web.socket.WebSocketSession;
    import org.springframework.web.socket.handler.TextWebSocketHandler;
    
    import cn.com.ship.message.common.Constants;
    import cn.com.ship.message.common.MessageCriteria;
    
    public class ChatMessageHandler extends TextWebSocketHandler{
    
    	private static final ArrayList<WebSocketSession> users;//这个会出现性能问题,最好用Map来存储,key用userid
    	private static Logger logger = Logger.getLogger(ChatMessageHandler.class);
    	static {
    		users = new ArrayList<WebSocketSession>();
    	}
    	
    	public ChatMessageHandler() {
    		// TODO Auto-generated constructor stub
    	}
    		
    	/**
    	 * 连接成功时候,会触发UI上onopen方法
    	 */
    	@Override
        public void afterConnectionEstablished(WebSocketSession session) throws Exception {
    		System.out.println("connect to the websocket success......");
    	users.add(session);
    	//这块会实现自己业务,比如,当用户登录后,会把离线消息推送给用户
            //TextMessage returnMessage = new TextMessage("你将收到的离线");
    	//session.sendMessage(returnMessage);
        }
    
    	/**
    	 * 在UI在用js调用websocket.send()时候,会调用该方法
    	 */
    	@Override
    	protected void handleTextMessage(WebSocketSession session,
    			TextMessage message) throws Exception {
    		super.handleTextMessage(session, message);
    				
    	}
    
    	/**
         * 给某个用户发送消息
         *
         * @param userName
         * @param message
         */
        public void sendMessageToUser(String userName, TextMessage message) {
            for (WebSocketSession user : users) {
                if (user.getAttributes().get(Constants.WEBSOCKET_USERNAME).equals(userName)) {
                    try {
                        if (user.isOpen()) {
                            user.sendMessage(message);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    break;
                }
            }
        }
        
        /**
         * 给所有在线用户发送消息
         *
         * @param message
         */
        public void sendMessageToUsers(TextMessage message) {
            for (WebSocketSession user : users) {
                try {
                    if (user.isOpen()) {
                        user.sendMessage(message);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        
        @Override
        public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
            if(session.isOpen()){
                session.close();
            }
            logger.debug("websocket connection closed......");
            users.remove(session);
        }
        
        @Override
        public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
            logger.debug("websocket connection closed......");
            users.remove(session);
        }
        
        @Override
        public boolean supportsPartialMessages() {
            return false;
        }
    
    }
    

    4. 编写websocket握手拦截器ChatHandshakeInterceptor extends HttpSessionHandshakeInterceptor

    ChatHandshakeInterceptor.java

    package cn.com.ship.message.websocket;
    
    import java.util.Map;
    
    import javax.servlet.http.HttpSession;
    
    import org.springframework.http.server.ServerHttpRequest;
    import org.springframework.http.server.ServerHttpResponse;
    import org.springframework.http.server.ServletServerHttpRequest;
    import org.springframework.web.socket.WebSocketHandler;
    import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
    
    import cn.com.ship.message.common.Constants;
    
    public class ChatHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
    
    	@Override
    	public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
    		System.out.println("Before Handshake");
    		if (request instanceof ServletServerHttpRequest) {
                ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
                HttpSession session = servletRequest.getServletRequest().getSession(false);
                if (session != null) {
                    //使用userName区分WebSocketHandler,以便定向发送消息
                    String userName = (String) session.getAttribute(Constants.SESSION_USERNAME);
                    if (userName==null) {
                    	userName="default-system";
                    }
                    attributes.put(Constants.WEBSOCKET_USERNAME,userName);
                    
                }
            }
            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);
    	}
    	
    
    }
    

    4. 重点在Spring mvc相关配置(经常出现问题就是:中文乱码,如果是用ajax交互)

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jee="http://www.springframework.org/schema/jee"
    	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
    	xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util"
    	xmlns:tool="http://www.springframework.org/schema/tool" xmlns:context="http://www.springframework.org/schema/context"
    	xmlns:task="http://www.springframework.org/schema/task"
    	xmlns:mvc="http://www.springframework.org/schema/mvc"
    	xmlns:websocket="http://www.springframework.org/schema/websocket"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans  
    	http://www.springframework.org/schema/beans/spring-beans.xsd  
    	http://www.springframework.org/schema/tx  
    	http://www.springframework.org/schema/tx/spring-tx.xsd  
    	http://www.springframework.org/schema/aop  
    	http://www.springframework.org/schema/aop/spring-aop.xsd  
    	http://www.springframework.org/schema/jee  
    	http://www.springframework.org/schema/jee/spring-jee.xsd  
    	http://www.springframework.org/schema/context  
    	http://www.springframework.org/schema/context/spring-context.xsd  
    	http://www.springframework.org/schema/util  
    	http://www.springframework.org/schema/util/spring-util.xsd  
    	http://www.springframework.org/schema/tool  
    	http://www.springframework.org/schema/tool/spring-tool.xsd
    	http://www.springframework.org/schema/task 
    	http://www.springframework.org/schema/task/spring-task.xsd
    	http://www.springframework.org/schema/websocket 
    	http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd
    	http://www.springframework.org/schema/mvc 
    	http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd">
    	
    	<!-- 这个bean要放在context:component-scan这个前面,不然会出现中文乱码 -->
    	<bean
    		class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    		<property name="messageConverters">
    			<list>
    				<ref bean="stringHttpMessageConverter" />
    				<ref bean="byteArrayHttpMessageConverter" />
    				<ref bean="jsonHttpMessageConverter" />
    				<ref bean="jsonHttpMessageConverter4JS" />
    			</list>
    		</property>
    	</bean>
    	
    	<!-- 启动SpringMVC Controller的注解功能,完成请求和注解POJO的映射 -->
    	<context:component-scan base-package="cn.com.ship.*.**.controller" />
    	
    	<!-- websocket相关扫描,主要扫描:WebSocketConfig.java 这个类路径 -->
    	<context:component-scan base-package="cn.com.ship.message.websocket"/>
    	
    	<!-- 下面标签可以不加 等价于所有component-scan-->
    	<context:annotation-config />
    	
    	<!-- 这个重点,标注必须加,websocket用到-->
    	<mvc:annotation-driven/>
    	
    	<bean id="byteArrayHttpMessageConverter"
    		class="org.springframework.http.converter.ByteArrayHttpMessageConverter" />
    		
    	<bean id="stringHttpMessageConverter"
    		class="org.springframework.http.converter.StringHttpMessageConverter">
    		<property name="supportedMediaTypes">
    			<list>
    				<value>text/plain;charset=UTF-8</value>
    			</list>
    		</property>
    	</bean>
    	
    	<bean id="jsonHttpMessageConverter"
    		class="cn.com.ship.external.spring.converter.json.MappingJackson2HttpMessageConverter">
    		<property name="objectMapper">
    			<bean class="com.fasterxml.jackson.databind.ObjectMapper">
    				<!--对属性值为null的不序列化反序列化-->
    				<property name="serializationInclusion">
    					<util:constant static-field="com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL"/>
    				</property>
    			</bean>
    		</property>
    		<property name="supportedMediaTypes">
    			<list>
    				<value>application/json</value>
    			</list>
    		</property>
    	</bean>
    	
    	<bean id="jsonHttpMessageConverter4JS"
    		class="cn.com.ship.external.spring.converter.json.MappingJackson2HttpMessageConverter">
    		<property name="objectMapper">
    			<bean class="com.fasterxml.jackson.databind.ObjectMapper">
    				<property name="dateFormat">
    					<bean class="java.text.SimpleDateFormat">
    						<constructor-arg type="java.lang.String" value="yyyy-MM-dd HH:mm:ss" />
    					</bean>
    				</property>
    				<!--对属性值为null的不序列化反序列化-->
    				<property name="serializationInclusion">
    					<util:constant static-field="com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL"/>
    				</property>
    			</bean>
    		</property>
    		<property name="supportedMediaTypes">
    			<list>
    				<value>text/json</value>
    			</list>
    		</property>
    	</bean>
    	
    	<bean id="viewResolver"
    		class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    		<property name="viewClass"
    			value="org.springframework.web.servlet.view.JstlView" />
    		<property name="prefix" value="/" />
    		<property name="suffix" value=".jsp" />
    	</bean>
    
    	<!-- 文件上传配置 -->
    	<bean id="multipartResolver"
    		class="org.springframework.web.multipart.commons.CommonsMultipartResolver"
    		p:defaultEncoding="utf-8">
    		<property name="maxUploadSize" value="1024000000" />
    		<property name="resolveLazily" value="true" />
    	</bean>
    </beans>

     注意:MappingJackson2HttpMessageConverter.java,来自Spring代码,并且修改了一点点,这个找到附件位置下载

    5. jsp相关Websocket脚本编写

         ws.jsp

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
    
    <html>
    <head>
    	<title>Java API for WebSocket (JSR-356)</title>  
    </head>
    <body>
    <script type="text/javascript" src="http://localhost:8080/ship/js/jquery/jquery.min.js"></script>
    <script type="text/javascript" src="http://localhost:8080/ship/js/sockjs-0.3.4.min.js"></script>
    <script type="text/javascript">
    	var websocket = null;
    	if ('WebSocket' in window) {
    		websocket = new WebSocket("ws://localhost:8080/ship/webSocketServer.do");
    	} 
    	else if ('MozWebSocket' in window) {
    		websocket = new MozWebSocket("ws://localhost:8080/ship/webSocketServer.do");
    	} 
    	else {
    		websocket = new SockJS("http://localhost:8080/ship/sockjs/webSocketServer.do");
    	}
    	websocket.onopen = onOpen;
    	websocket.onmessage = onMessage;
    	websocket.onerror = onError;
    	websocket.onclose = onClose;
    	      	
    	function onOpen(openEvt) {
    		//alert(openEvt.Data);
    	}
    	
    	function onMessage(evt) {
    		alert(evt.data);
    	}
    	function onError() {}
    	function onClose() {}
    	
    	function doSend() {
    		if (websocket.readyState == websocket.OPEN) {  		
                var msg = document.getElementById("inputMsg").value;  
                websocket.send(msg);//调用后台handleTextMessage方法
                alert("发送成功!");  
            } else {  
            	alert("连接失败!");  
            }  
    	}
    </script>
    请输入:<textarea rows="5" cols="10" id="inputMsg" name="inputMsg"></textarea>
    <button onclick="doSend();">发送</button>
    </body>
    </html>

        login.jsp

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Java API for WebSocket (JSR-356)</title> 
    </head>
    <body>
            <!-ship是我的项目名-->
    	<form action="/ship/websocket/login.do">
    		登录名:<input type="text" name="username"/>
    		<input type="submit" value="登录"/>
    	</form>
    
    </body>
    </html>

    5. 调用端Controller编写 WebsocketController.java

    package cn.com.ship.websocket.controller;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    @Controller
    public class WebsocketController {
    
    	@Bean//这个注解会从Spring容器拿出Bean
    	public InfoHandler infoHandler() {
    		return new InfoHandler();
    	}
    	
    	@RequestMapping("/websocket/login")
    	public void login(HttpServletRequest request, HttpServletResponse response) throws Exception {
    		String username = request.getParameter("username");
    		HttpSession session = request.getSession(false);
    		session.setAttribute(Constants.SESSION_USERNAME, username);
    		
    		response.sendRedirect("/ship/websocket/ws.jsp");
    	}
    
    	@RequestMapping("/websocket/send")
    	@ResponseBody
    	public String send(HttpServletRequest request) {
    		
    		String username = request.getParameter("username");
    		infoHandler().sendMessageToUser(username, new TextMessage("你好,测试!!!!"));
    		
    		return null;
    	}
    
    }
    

    6. 测试Websocket是否连接成功

       先在login.jsp上随便输入用户名,然后WebsocketController处理完请求,会重定向到ws.jsp

       如果后台打印出现消息,证明Websocket连接成功(前两名消息是在ChatHandshakeInterceptor.java,最后一句是在ChatMessageHandler.java)

    Before Handshake
    After Handshake
    connect to the websocket success......

  • 相关阅读:
    AR路由器web界面每IP限速配置方法
    传输层:TCP 协议
    从需求的角度去理解Linux系列:总线、设备和驱动
    京东的个性化推荐系统
    数据挖掘-MovieLens数据集_电影推荐_亲和性分析_Aprioro算法
    Linux时间子系统之七:定时器的应用--msleep(),hrtimer_nanosleep()
    Linux SPI总线和设备驱动架构之四:SPI数据传输的队列化
    拦截器及 Spring MVC 整合
    表现层 JSP 页面实现
    Controller 层实现
  • 原文地址:https://www.cnblogs.com/xiaohuizhenyoucai/p/10671290.html
Copyright © 2011-2022 走看看