使用spring-websocket包搭建websocket服务

来源:互联网 发布:2016安卓翻墙软件 编辑:程序博客网 时间:2024/05/22 03:24

websocket是Html5新增加特性之一,目的是浏览器与服务端建立全双工的通信方式,解决http请求-响应带来过多的资源消耗,同时对特殊场景应用提供了全新的实现方式,比如聊天、股票交易、游戏等对对实时性要求较高的行业领域。

Spring4开始支持WebSocket,也支持通过STOMP协议连接JMS消息服务器如ActiveMQ等。WebSocket是目前唯一真正实现全双工通信的服务器向客户端推的互联网技术,与长连接和轮询技术相比,WebSocket的优越性不言自明,长连接的连接资源(线程资源)随着连接数量的增多必会耗尽,客户端轮询会给服务器造成很大的压力,而WebSocket是在物理层非网络层建立一条客户端至服务器的长连接,以此来保证服务器向客户端的即时推送,既不耗费线程资源,又不会不断向服务器轮询请求。

1.spring 4.0及以上增加了WebSocket的支持(这里使用4.3.8.RELEASE) 
2.spring 支持STOMP协议的WebSocket通信 
3.应对不支持 WebSocket 的场景,许多浏览器不支持 WebSocket 协议;SockJS 是 WebSocket 技术的一种模拟。SockJS 会 尽可能对应 WebSocket API,但如果 WebSocket 技术 不可用的话,会从如下 方案中挑选最优可行方案:

XHR streamingXDR streamingiFrame event sourceiFrame HTML fileXHR pollingXDR pollingiFrame XHR pollingJSONP polling

4.WebSocket 是发送和接收消息的 底层API,而SockJS 是在 WebSocket 之上的 API;最后 STOMP(面向消息的简单文本协议)是基于 SockJS 的高级API 
5.SockJS 所处理的URL 是 “http:” 或 “https:” 模式 
6.WebSocket 所处理的URL 是“ws:” or “wss:” 模式


下面我们来建立一个websocket实例


一。 建立maven工程

dom.xml 加入websocket所依赖的包

<dependency>      <groupId>javax.servlet</groupId>      <artifactId>javax.servlet-api</artifactId>      <version>3.1.0</version>  </dependency>  <dependency>      <groupId>com.fasterxml.jackson.core</groupId>      <artifactId>jackson-core</artifactId>      <version>2.8.0</version>  </dependency>  <dependency>      <groupId>com.fasterxml.jackson.core</groupId>      <artifactId>jackson-databind</artifactId>      <version>2.8.0</version>  </dependency> <dependency>     <groupId>org.springframework</groupId>     <artifactId>spring-websocket</artifactId>     <version>4.3.7.RELEASE</version>  </dependency>  <dependency>     <groupId>org.springframework</groupId>     <artifactId>spring-messaging</artifactId>     <version>4.3.7.RELEASE</version>  </dependency>  

spring框架加入 spring-core, spring-context, spring-web ,spring-webmvc 类库,贴出完整的pom.xml

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <groupId>org.jstudioframework</groupId>    <artifactId>jstudio-webstocket1</artifactId>    <version>1.0-SNAPSHOT</version>    <packaging>war</packaging>    <properties>        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>        <!-- 使用最近较新的spring版本 -->        <spring.version>4.3.7.RELEASE</spring.version>        <slf4j.version>1.6.6</slf4j.version>        <jackson.version>2.8.4</jackson.version>        <servlet-api.version>3.1.0</servlet-api.version>    </properties>    <dependencies>        <!-- 日志处理 -->        <dependency>            <groupId>org.slf4j</groupId>            <artifactId>slf4j-log4j12</artifactId>            <version>${slf4j.version}</version>        </dependency>        <!-- spring相关 -->        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-core</artifactId>            <version>${spring.version}</version>        </dependency>        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-beans</artifactId>            <version>${spring.version}</version>        </dependency>        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-context</artifactId>            <version>${spring.version}</version>        </dependency>        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-web</artifactId>            <version>${spring.version}</version>        </dependency>        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-webmvc</artifactId>            <version>${spring.version}</version>        </dependency>        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-aop</artifactId>            <version>${spring.version}</version>        </dependency>        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-context-support</artifactId>            <version>${spring.version}</version>        </dependency>        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-test</artifactId>            <version>${spring.version}</version>        </dependency>        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-websocket</artifactId>            <version>${spring.version}</version>        </dependency>        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-messaging</artifactId>            <version>${spring.version}</version>        </dependency>        <dependency>            <groupId>javax.servlet</groupId>            <artifactId>javax.servlet-api</artifactId>            <version>${servlet-api.version}</version>            <scope>provided</scope>        </dependency>        <dependency>            <groupId>com.fasterxml.jackson.core</groupId>            <artifactId>jackson-core</artifactId>            <version>${jackson.version}</version>        </dependency>        <dependency>            <groupId>com.fasterxml.jackson.core</groupId>            <artifactId>jackson-databind</artifactId>            <version>${jackson.version}</version>        </dependency>    </dependencies>    <build>        <plugins>            <!-- 配置Tomcat插件 -->            <plugin>                <groupId>org.apache.tomcat.maven</groupId>                <artifactId>tomcat7-maven-plugin</artifactId>                <configuration>                    <port>8080</port>                    <path>/ws</path>                </configuration>            </plugin>        </plugins>    </build></project>


二。创建一个Websocket处理器  

可以扩展 AbstractWebSocketHandler ,也可以扩展 TextWebSocketHandler(文本处理器),TextWebSocketHandler 继承 AbstractWebSocketHandler

package org.jstudioframework.websoket.handler;import org.slf4j.Logger;import org.slf4j.LoggerFactory;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 java.io.IOException;import java.util.ArrayList;/** * Websocket处理器 */public class WebSocketHandler extends TextWebSocketHandler {    private final static Logger LOGGER = LoggerFactory.getLogger(WebSocketHandler.class);    //已建立连接的用户    private static final ArrayList<WebSocketSession> users = new ArrayList<WebSocketSession>();    /**     * 处理前端发送的文本信息     * js调用websocket.send时候,会调用该方法     *     * @param session     * @param message     * @throws Exception     */    @Override    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {        String username = (String) session.getAttributes().get("WEBSOCKET_USERNAME");        // 获取提交过来的消息详情        LOGGER.debug("收到用户 " + username + "的消息:" + message.toString());        //回复一条信息,        session.sendMessage(new TextMessage("reply msg:" + message.getPayload()));    }    /**     * 当新连接建立的时候,被调用     * 连接成功时候,会触发页面上onOpen方法     *     * @param session     * @throws Exception     */    @Override    public void afterConnectionEstablished(WebSocketSession session) throws Exception {        users.add(session);        String username = (String) session.getAttributes().get("WEBSOCKET_USERNAME");        LOGGER.info("用户 " + username + " Connection Established");        session.sendMessage(new TextMessage(username + " connect"));        session.sendMessage(new TextMessage("hello wellcome"));    }    /**     * 当连接关闭时被调用     *     * @param session     * @param status     * @throws Exception     */    @Override    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {        String username = (String) session.getAttributes().get("WEBSOCKET_USERNAME");        LOGGER.info("用户 " + username + " Connection closed. Status: " + status);        users.remove(session);    }    /**     * 传输错误时调用     *     * @param session     * @param exception     * @throws Exception     */    @Override    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {        String username = (String) session.getAttributes().get("WEBSOCKET_USERNAME");        if (session.isOpen()) {            session.close();        }        LOGGER.debug("用户: " + username + " websocket connection closed......");        users.remove(session);    }    /**     * 给所有在线用户发送消息     *     * @param message     */    public void sendMessageToUsers(TextMessage message) {        for (WebSocketSession user : users) {            try {                if (user.isOpen()) {                    user.sendMessage(message);                }            } catch (IOException e) {                e.printStackTrace();            }        }    }    /**     * 给某个用户发送消息     *     * @param userName     * @param message     */    public void sendMessageToUser(String userName, TextMessage message) {        for (WebSocketSession user : users) {            if (user.getAttributes().get("WEBSOCKET_USERNAME").equals(userName)) {                try {                    if (user.isOpen()) {                        user.sendMessage(message);                    }                } catch (IOException e) {                    e.printStackTrace();                }                break;            }        }    }}


三。创建一个WebSocket握手拦截器

package org.jstudioframework.websoket.interceptor;import org.slf4j.Logger;import org.slf4j.LoggerFactory;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.HandshakeInterceptor;import javax.servlet.http.HttpSession;import java.util.Map;/** * WebSocket握手拦截器 */public class WebSocketHandshakeInterceptor implements HandshakeInterceptor {    private final static Logger LOGGER = LoggerFactory.getLogger(WebSocketHandshakeInterceptor.class);    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> attributes) throws Exception {        if (request instanceof ServletServerHttpRequest) {            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;            HttpSession session = servletRequest.getServletRequest().getSession(false);            if (session != null) {                //使用userName区分WebSocketHandler,以便定向发送消息                String userName = (String) session.getAttribute("SESSION_USERNAME");                if (userName == null) {                    userName = "system-" + session.getId();                }                attributes.put("WEBSOCKET_USERNAME", userName);            }        }        return true;    }    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {        System.out.println("After Handshake");    }}


四。 Spring WebSocket的配置文件,采用的是注解的方式

package org.jstudioframework.websoket.config;import org.jstudioframework.websoket.handler.WebSocketHandler;import org.jstudioframework.websoket.interceptor.WebSocketHandshakeInterceptor;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;import org.springframework.web.socket.config.annotation.*;import org.springframework.web.socket.handler.TextWebSocketHandler;/** * Spring WebSocket的配置,这里采用的是注解的方式 */@Configuration//@EnableWebMvc//这个标注可以不加,如果有加,要extends WebMvcConfigurerAdapter@EnableWebSocketpublic class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {        //1.注册WebSocket        String websocket_url = "/websocket/socketServer.do";                        //设置websocket的地址        registry.addHandler(webSocketHandler(), websocket_url).                          //注册Handler                addInterceptors(new WebSocketHandshakeInterceptor());                   //注册Interceptor        //2.注册SockJS,提供SockJS支持(主要是兼容ie8)        String sockjs_url = "/sockjs/socketServer.do";                              //设置sockjs的地址        registry.addHandler(webSocketHandler(), sockjs_url).                            //注册Handler                addInterceptors(new WebSocketHandshakeInterceptor()).                   //注册Interceptor                withSockJS();                                                           //支持sockjs协议    }    @Bean    public TextWebSocketHandler webSocketHandler() {        return new WebSocketHandler();    }}


五。创建Controller测试类

package org.jstudioframework.websoket.controller;import org.jstudioframework.websoket.handler.WebSocketHandler;import org.springframework.context.annotation.Bean;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.socket.TextMessage;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;/** * 测试类 */@Controllerpublic class PageController {    @Bean//这个注解会从Spring容器拿出Bean    public WebSocketHandler infoHandler() {        return new WebSocketHandler();    }    @RequestMapping("/login")    public void login(HttpServletRequest request, HttpServletResponse response) throws Exception {        String username = request.getParameter("username");        System.out.println(username + "登录");        HttpSession session = request.getSession();        session.setAttribute("SESSION_USERNAME", username);        response.sendRedirect("websocket.jsp");    }    @RequestMapping("/send")    @ResponseBody    public String send(HttpServletRequest request) {        String username = request.getParameter("username");        infoHandler().sendMessageToUser(username, new TextMessage("你好,欢迎测试!!!!"));        return null;    }}


六。测试页面

1.登录页面,输入用户名称  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><body><h2>Wellcome</h2><body><form action="login">    登录名:<input type="text" name="username"/>    <input type="submit" value="登录"/></form></body></body></html>

2.消息发送接收页面 websocket.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><script type="text/javascript" src="http://cdn.bootcss.com/jquery/3.1.0/jquery.min.js"></script><script type="text/javascript" src="http://cdn.bootcss.com/sockjs-client/1.1.1/sockjs.js"></script><script type="text/javascript">    var websocket = null;    if ('WebSocket' in window) {        //Websocket的连接        websocket = new WebSocket("ws://localhost:8080/ws/websocket/socketServer.do");//WebSocket对应的地址    }    else if ('MozWebSocket' in window) {        //Websocket的连接        websocket = new MozWebSocket("ws://localhost:8080/ws/websocket/socketServer.do");//SockJS对应的地址    }    else {        //SockJS的连接        websocket = new SockJS("http://localhost:8080/ws/sockjs/socketServer.do");    //SockJS对应的地址    }    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("连接失败!");        }    }    window.close = function () {        websocket.onclose();    }</script>请输入:<textarea rows="3" cols="100" id="inputMsg" name="inputMsg"></textarea><button onclick="doSend();">发送</button></body></html>


七。spring的配置

<?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:context="http://www.springframework.org/schema/context"       xmlns:mvc="http://www.springframework.org/schema/mvc"       xsi:schemaLocation="http://www.springframework.org/schema/beans                          http://www.springframework.org/schema/beans/spring-beans-3.1.xsd                          http://www.springframework.org/schema/context                          http://www.springframework.org/schema/context/spring-context-3.1.xsd                          http://www.springframework.org/schema/mvc                          http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">        <context:annotation-config />        <mvc:annotation-driven />        <context:component-scan base-package="org.jstudioframework.websoket" />    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">        <property name="prefix" value="/WEB-INF/views/" />        <property name="suffix" value=".jsp" />    </bean></beans> 


八。web.xml配置

<?xml version="1.0" encoding="UTF-8"?><web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"         version="3.1">    <welcome-file-list>        <welcome-file>index.jsp</welcome-file>    </welcome-file-list>    <context-param>        <param-name>contextConfigLocation</param-name>        <param-value>classpath:spring/*.xml</param-value>    </context-param>    <listener>        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>    </listener>    <servlet>        <servlet-name>dispatcherServlet</servlet-name>        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>        <init-param>            <param-name>contextConfigLocation</param-name>            <param-value>classpath:spring/*.xml</param-value>        </init-param>        <load-on-startup>1</load-on-startup>        <async-supported>true</async-supported>    </servlet>    <servlet-mapping>        <servlet-name>dispatcherServlet</servlet-name>        <url-pattern>/</url-pattern>    </servlet-mapping>    <filter>        <filter-name>encodingFilter</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>encodingFilter</filter-name>        <url-pattern>/*</url-pattern>    </filter-mapping></web-app>


所有的servlet和filter都要加<async-supported>true</async-supported>


九。测试




工程代码实例下载地址

http://download.csdn.net/download/zsg88/9933921