A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 鱼丸儿 于 2018-3-30 15:32 编辑

WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。 WebSocket通信协议于2011年被IETF定为标准RFC 6455,WebSocketAPI被W3C定为标准。 在WebSocket API中,浏览器和服务器只需要要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
以上摘自Wikipedia.
本文将说明在IntellijIDEA下使用Gradle构建SpringMVC+WebSocket实现消息推送.为了更好的阅读体验,请点击阅读原文
导入依赖
在build.gradle中导入denpendies
[Java] 纯文本查看 复制代码
 // ------------------------   Spring SpringMVC  start -------------------------[/align][align=left]compile group: 'org.springframework', name: 'spring-webmvc', version: '4.2.4.RELEASE'
compile group: 'org.springframework', name: 'spring-context-support', version: '4.2.4.RELEASE'
// ------------------------   Spring SpringMVC  end -------------------------

// ------------------------   WebSocket  start -------------------------
compile group: 'org.springframework', name: 'spring-websocket', version: '4.2.4.RELEASE'
compile group: 'org.springframework', name: 'spring-messaging', version: '4.2.4.RELEASE'
compile group: 'javax.servlet', name: 'javax.servlet-api', version: '3.1.0'
// ------------------------   WebSocket  end -------------------------

compile group: 'log4j', name: 'log4j', version: '1.2.17'
compile group: 'com.google.code.gson', name: 'gson', version: '2.8.2'
此处需要注意的是Spring从4.0+版本开始支持WebSocket,而servlet-api需要为3.0+版本
[XML] 纯文本查看 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                  [url=http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd]http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd[/url]"
         version="3.0">

    <!-- 初始化spring 容器 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:config/spring/applicationContext-*.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- 配置前端控制器 -->
    <servlet>
        <servlet-name>index-dispather</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:config/spring/spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>

    <servlet-mapping>
        <servlet-name>index-dispather</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!-- 解决post乱码问题 -->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
</web-app>
此处需要给servlet及filter添加异步<async-supported>true</async-supported>.
[XML] 纯文本查看 复制代码
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       [url=http://www.springframework.org/schema/beans/spring-beans.xsd]http://www.springframework.org/schema/beans/spring-beans.xsd[/url]
       [url=http://www.springframework.org/schema/context]http://www.springframework.org/schema/context[/url]
       [url=http://www.springframework.org/schema/context/spring-context.xsd]http://www.springframework.org/schema/context/spring-context.xsd[/url]
       [url=http://www.springframework.org/schema/mvc]http://www.springframework.org/schema/mvc[/url]
       [url=http://www.springframework.org/schema/mvc/spring-mvc.xsd]http://www.springframework.org/schema/mvc/spring-mvc.xsd[/url]">

    <!-- 自动扫描控制器,webSocket -->
    <context:component-scan base-package="com.lhalcyon.king.controller,com.lhalcyon.king.socket"/>
    <!-- 视图渲染 -->
    <bean id="internalResourceViewResolver"
          class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <!-- 控制器映射器和控制器适配器 -->
    <mvc:annotation-driven>

    </mvc:annotation-driven>

    <!-- 静态资源映射器 -->
    <mvc:resources mapping="/statics/**" location="/WEB-INF/statics/" />
</beans>
此处需配置扫描controller和websocket所在包
还有一个applicationContext-websocket.xml配置文件我们在代码实现中说明
代码实现握手拦截器
HandshakeInterceptor
拦截器说明
An interceptorto copy information from the HTTP session to the “handshake
attributes” map to made available via WebSocketSession.getAttributes()
Copies a subset or all HTTP session attributes and/or the HTTP session id
拦截器主要用于用户登录标识的记录,便于后面获取指定用户的会话标识并向指定用户发送消息,
这里我们继承HttpSessionHandshakeInterceptor
[Java] 纯文本查看 复制代码
[/align][align=left] public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor {

    private Logger logger = Logger.getLogger(HandshakeInterceptor.class);

    // 握手前
    @Override
    public boolean beforeHandshake(ServerHttpRequest request,
                                   ServerHttpResponse response, WebSocketHandler wsHandler,
                                   Map<String, Object> attributes) throws Exception {

        logger.info("++ HandshakeInterceptor: beforeHandshake ++"+attributes);
        return super.beforeHandshake(request, response, wsHandler, attributes);
    }

    // 握手后
    @Override
    public void afterHandshake(ServerHttpRequest request,
                               ServerHttpResponse response, WebSocketHandler wsHandler,
                               Exception ex) {

        logger.info("++ HandshakeInterceptor: afterHandshake ++");
        super.afterHandshake(request, response, wsHandler, ex);
    }
}
beforeHandshake(..)
Invoked before the handshake is processed.
afterHandshake(..)
Invoked after the handshake is done. The response status and headers indicate the results of the handshake, i.e. whether it was successful or not.
两个方法调用时机均为字面所述,握手前后分别调用.主要是在握手前后去做一些事,比如将需要的数据设置到attributes里,之后在WebSocketHandler的session中获取这些数据.
处理类
WebSocketHandler
[Java] 纯文本查看 复制代码
public class MyWebSocketHandler implements WebSocketHandler {


    private static final Logger log = Logger.getLogger(MyWebSocketHandler.class);

    // 保存所有的用户session
    private static final ArrayList<WebSocketSession> users = new ArrayList<WebSocketSession>();


    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        log.info("connect websocket success.......");

        users.add(session);
    }

    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
        Gson gson = new Gson();
        // 将消息JSON格式通过Gson转换成Map
        // message.getPayload().toString() 获取消息具体内容
        Map<String, Object> msg = gson.fromJson(message.getPayload().toString(),
                new TypeToken<Map<String, Object>>() {}.getType());
        log.info("handleMessage......."+message.getPayload()+"..........."+msg);
        String content = message.getPayload().toString();
        // 处理消息 msgContent消息内容
        TextMessage textMessage = new TextMessage(content, true);
        // 调用方法(发送消息给所有人)
        sendMsgToAllUsers(textMessage);
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        log.warn("handleTransportError");
        users.remove(session);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
        log.info("connect websocket closed.......");
        users.remove(session);

    }

    // 给所有用户发送 信息
    public void sendMsgToAllUsers(WebSocketMessage<?> message) throws Exception{
        for (WebSocketSession user : users) {
            user.sendMessage(message);
        }
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }
}
以下对主要方法进行说明:
afterConnectionEstablished(..)
连接建立后调用,常用于记录用户的连接标识,便于后面信息发送.
handleTextMessage(..)
对消息进行处理.
handleTransportError(..)
连接异常处理.需要关闭出错会话连接
afterConnectionClosed(..)
连接关闭处理
此处我们对消息的处理很简单,即在接受消息后发送给所有连接的用户,类似一个匿名群聊室.
注册
完成了WebSocket处理类,还需要对其进行注册生效.这里有两种方式,择其一即可.

创建配置类,并通过注解注册
[Java] 纯文本查看 复制代码
@Configuration
@EnableWebMvc
@EnableWebSocket
public class MyWebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        //前台 可以使用websocket环境
        registry.addHandler(myWebSocketHandler(),"/websocket").addInterceptors(new HandshakeInterceptor()).setAllowedOrigins("*");

        //前台 不可以使用websocket环境,则使用sockjs进行模拟连接
        registry.addHandler(myWebSocketHandler(), "/sockjs/websocket").addInterceptors(new HandshakeInterceptor())
                .withSockJS();
    }
    
    // websocket 处理类
    @Bean
    public WebSocketHandler myWebSocketHandler(){
        return new MyWebSocketHandler();
    }
}
同时还需要配置上文提到过的Spring扫描配置类.
[XML] 纯文本查看 复制代码
!-- 自动扫描控制器,webSocket -->
<context:component-scan base-package="com.lhalcyon.king.controller,com.lhalcyon.king.socket"/>
通过xml配置注册
applicationContext-websocket.xml
[XML] 纯文本查看 复制代码
<?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:websocket="http://www.springframework.org/schema/websocket"
       xsi:schemaLocation="
            [url=http://www.springframework.org/schema/beans]http://www.springframework.org/schema/beans[/url]
            [url=http://www.springframework.org/schema/beans/spring-beans-4.0.xsd]http://www.springframework.org/schema/beans/spring-beans-4.0.xsd[/url]
            [url=http://www.springframework.org/schema/websocket]http://www.springframework.org/schema/websocket[/url]
            [url=http://www.springframework.org/schema/websocket/spring-websocket-4.0.xsd]http://www.springframework.org/s ... g-websocket-4.0.xsd[/url]">



    <!-- websocket处理类 -->
    <bean id="myHandler" class="com.lhalcyon.king.socket.MyWebSocketHandler"/>

    <!-- 握手接口/拦截器 -->
    <bean id="myInterceptor" class="com.lhalcyon.king.socket.HandshakeInterceptor"/>

    <websocket:handlers>
        <websocket:mapping path="/websocket" handler="myHandler"/>
        <websocket:handshake-interceptors>
            <ref bean="myInterceptor"/>
        </websocket:handshake-interceptors>
    </websocket:handlers>

    <!--  注册 sockJS -->
    <websocket:handlers>
        <websocket:mapping path="/websocket" handler="myHandler"/>
        <websocket:handshake-interceptors>
            <ref bean="myInterceptor"/>
        </websocket:handshake-interceptors>
        <websocket:sockjs />
    </websocket:handlers>

</beans>
以上服务端代码实现基本完成,接下来对客户端测试页面做一个简单的实现.
客户端页面
index.jsp
[HTML] 纯文本查看 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE HTML>
<html>
<head>
    <title>首页</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="renderer" content="webkit">

    <!-- 引入 JQuery  -->
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>

    <!-- 引入 sockJS  -->
    <script type="text/javascript" src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js" ></script>
    
    <script type="text/javascript">
        $(function() {
            var websocket;

            // 首先判断是否 支持 WebSocket

            var url = 'ws://' + window.location.host + '/word-king/websocket';

            if('WebSocket' in window) {
                websocket = new WebSocket(url);
            } else if('MozWebSocket' in window) {
                websocket = new MozWebSocket(url);
            } else {
                url = "http://"+ window.location.host +"/word-king/sockjs/websocket";
                websocket = new SockJS(url);
            }
            // 打开时
            websocket.onopen = function(evnt) {
                console.log("  websocket.onopen  ");
            };
            // 处理消息时
            websocket.onmessage = function(evnt) {
                $("#msg").append("<p>(<font color='red'>" + evnt.data + "</font>)</p>");
                console.log("  websocket.onmessage   ");
            };
            websocket.onerror = function(evnt) {
                alert("onerror");
                console.log("  websocket.onerror  ");
            };
            websocket.onclose = function(evnt) {
                console.log("  websocket.onclose  ");
                alert("onclose");
            };
            // 点击了发送消息按钮的响应事件
            $("#TXBTN").click(function(){
                // 获取消息内容
                var text = $("#tx").val();
                // 判断
                if(text == null || text == ""){
                    alert(" content  can not empty!!");
                    return false;
                }
                var msg = {
                    msgContent: text,
                    postsId: 1
                };
                // 发送消息
                websocket.send(JSON.stringify(msg));
            });
        });
    </script>
</head>
<body>
<!-- 最外边框 -->
<div style="margin: 20px auto; border: 1px solid blue; width: 300px; height: 500px;">

    <!-- 消息展示框 -->
    <div id="msg" style="width: 100%; height: 70%; border: 1px solid yellow;overflow: auto;"></div>

    <!-- 消息编辑框 -->
    <textarea id="tx" style="width: 100%; height: 20%;"></textarea>

    <!-- 消息发送按钮 -->
    <button id="TXBTN" style="width: 100%; height: 8%;">发送数据</button>
</div>
</body>
</html>
需要注意的是此处引入JQuery时,如果是本地的文件,可能存在无效的情况,需要去设置静态资源映射路径,可自行
本文采用的是引入在线地址
1
2
<!-- 引入 JQuery  -->
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.5/jquery.min.js"></script>
地址写入后需Download下来
代码不高亮警告后即能生效.


0 个回复

您需要登录后才可以回帖 登录 | 加入黑马