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

spring4.1.6 WebSocket实例

2015-07-14 21:33 633 查看
WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。

在 WebSocket API,浏览器和服务器只需要要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。在此WebSocket 协议中,为我们实现即时服务带来了两大好处:

1. Header

互相沟通的Header是很小的-大概只有 2 Bytes

2. Server Push

目前支持websocket的浏览器有:

Chrome
Supported in version 4+
Firefox
Supported in version 4+
Internet Explorer
Supported in version 10+
Opera
Supported in version 10+
Safari
Supported in version 5+
对于那些不支持websocket的浏览器,可以使用SockJS来协调支持,该库目前提供的模拟方式已经支持绝大多是浏览器了。具体情况如下:

BrowserWebsocketsStreamingPolling
IE 6, 7nonojsonp-polling
IE 8, 9 (cookies=no)noxdr-streaming †xdr-polling †
IE 8, 9 (cookies=yes)noiframe-htmlfileiframe-xhr-polling
IE 10rfc6455xhr-streamingxhr-polling
Chrome 6-13hixie-76xhr-streamingxhr-polling
Chrome 14+hybi-10 / rfc6455xhr-streamingxhr-polling
Firefox <10no ‡xhr-streamingxhr-polling
Firefox 10+hybi-10 / rfc6455xhr-streamingxhr-polling
Safari 5hixie-76xhr-streamingxhr-polling
Opera 10.70+no ‡iframe-eventsourceiframe-xhr-polling
Opera 12.10+rfc6455xhr-streamingxhr-polling
Konquerornonojsonp-polling
从spring4开始,加入了对websocket的支持,同时也加入了对SockJS、STOMP的支持,现在通过Spring可以非常方便的创建我们的WebSocket应用服务。

进入正题,使用maven搭建spring的webmvc环境。pom.xml的内容如下:

<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/maven-v4_0_0.xsd">   <modelVersion>4.0.0</modelVersion>
  <groupId>com.zhm.spring</groupId>
  <artifactId>ws_test</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>ws_test Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring.version>4.1.6.RELEASE</spring.version>
    <jackson.version>2.5.4</jackson.version>
    <jetty.version>6.1.23</jetty.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</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-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>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet.jsp.jstl</groupId>
      <artifactId>jstl-api</artifactId>
      <version>1.2</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>javax.servlet.jsp-api</artifactId>
      <version>2.3.1</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.dataformat</groupId>
      <artifactId>jackson-dataformat-xml</artifactId>
      <version>${jackson.version}</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>${jackson.version}</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>${jackson.version}</version>
    </dependency>
  </dependencies>
  <build>
    <finalName>ROOT</finalName>
    <plugins>
      <plugin>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.6</version>
        <configuration>
          <packagingExcludes>
            %regex[WEB-INF/lib/.*]
          </packagingExcludes>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.2</version>
        <configuration>
          <port>9090</port>
          <path>/</path>
          <uriEncoding>UTF-8</uriEncoding>
          <finalName>ROOT</finalName>
          <server>tomcat7</server>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>


web.xml的配置(其中<async-supported>true</async-supported>配置在使用非W3C WebSocket连接的时候需要加上):

<web-app version="3.0" 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 http://java.sun.com/xml/ns/javaee/web-app_3_1.xsd"> 
<filter>
        <filter-name>encodingFilter</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>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
        <async-supported>true</async-supported>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/dispatcher-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
  <display-name>Archetype Created Web Application</display-name>
</web-app>


dispatcher-server.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:context="http://www.springframework.org/schema/context"
      xmlns:websocket="http://www.springframework.org/schema/websocket"
      xmlns:mvc="http://www.springframework.org/schema/mvc"
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
       http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
   <!--  websocket消息接收与处理类 -->
   <bean id="websocket" class="com.zhm.spring.ws.websocket.WebSocketEndPoint"/>
   <!-- 定义客户端与服务器握手的拦截器,可以做一些预处理 -->
   <!-- 该拦截器专门为SockJS客户端服务的 -->
   <websocket:handlers>
      <websocket:mapping path="/sockjs/websocket" handler="websocket"/>
      <websocket:handshake-interceptors>
         <bean class="com.zhm.spring.ws.websocket.HandshakeInterceptor"/>
      </websocket:handshake-interceptors>
      <!-- 开启sockjs支持 -->
      <websocket:sockjs />
   </websocket:handlers>
   <!-- 定义客户端与服务器握手的拦截器,可以做一些预处理 -->
   <!-- 该拦截器专门为WebSocket客户端服务的 -->
   <websocket:handlers>
      <websocket:mapping path="/websocket" handler="websocket"/>
      <websocket:handshake-interceptors>
         <bean class="com.zhm.spring.ws.websocket.HandshakeInterceptor"/>
      </websocket:handshake-interceptors>
   </websocket:handlers>

   <mvc:view-resolvers>
      <mvc:jsp />
   </mvc:view-resolvers>
   <mvc:annotation-driven />
   <context:component-scan base-package="com.zhm.spring" />

</beans>


WebSocketEndPoint代码:

package com.zhm.spring.ws.websocket;

import com.zhm.spring.ws.utils.Constants;
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.util.ArrayList;

/**
 * Created by zhm on 2015/7/14.
 */
public class WebSocketEndPoint extends TextWebSocketHandler{
    private static final ArrayList<WebSocketSession> users;

    static {
        users = new ArrayList<WebSocketSession>();
    }

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        users.add(session);
        super.afterConnectionEstablished(session);
    }

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

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        users.remove(session);
        super.afterConnectionClosed(session, status);
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        super.handleTextMessage(session, message);
        TextMessage returnMessage = new TextMessage(session.getAttributes().get(Constants.SESSION_USERNAME.value())+" : "+message.getPayload());
//        session.sendMessage(returnMessage);
        sendToAllClients(returnMessage, session);
    }

    private void sendToAllClients(TextMessage msg,WebSocketSession curSession) {
        try
        {
            for(WebSocketSession user : users)
            {
                if(user.isOpen()) {
                    if (!user.getId().equals(curSession.getId())){
                        user.sendMessage(msg);
                    }
                }
                else
                {
                    users.remove(user.getId());
                }
            }
        }catch(Exception e)
        {
            e.printStackTrace();
        }
    }
}


继承TextWebSocketHandler,并复写一些方法。很好理解。

HandshakeInterceptor的源码如下:

package com.zhm.spring.ws.websocket;

import com.zhm.spring.ws.utils.Constants;
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 javax.servlet.http.HttpSession;
import java.util.Map;

/**
 * Created by zhm on 2015/7/14.
 */
public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor {
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, 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(Constants.SESSION_USERNAME.value());
                attributes.put(Constants.SESSION_USERNAME.value(),userName);
            }
        }
        return super.beforeHandshake(request, response, wsHandler, attributes);
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {
        super.afterHandshake(request, response, wsHandler, ex);
    }

}


主要做一些握手的之前的预处理工作。

最后就是写个HomeController加载客户端页面了。

package com.zhm.spring.ws.controller;

import com.zhm.spring.ws.utils.Constants;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpSession;

/**
 * Created by zhm on 2015/7/14.
 */
@Controller
public class HomeController {
    @RequestMapping(value="/webchat/{username}")
    public String webchat(@PathVariable String username,HttpSession session){
        session.setAttribute(Constants.SESSION_USERNAME.value(),username);
        return "websocket";
    }
}


websocket.jsp的代码如下:

<%@ page import="com.zhm.spring.ws.utils.Constants" %>
<%--
  Created by IntelliJ IDEA.
  User: zhm
  Date: 2015/7/14
  Time: 10:35
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
  <title>Spring4  websocket实例</title>
  <meta charset="utf-8">
  <style type="text/css">
    #connect-container {
      float: left;
      width: 400px
    }

    #connect-container div {
      padding: 5px;
    }

    #console-container {
      float: left;
      margin-left: 15px;
      width: 400px;
    }

    #console {
      border: 1px solid #CCCCCC;
      border-right-color: #999999;
      border-bottom-color: #999999;
      height: 170px;
      overflow-y: scroll;
      padding: 5px;
      width: 100%;
    }

    #console p {
      padding: 0;
      margin: 0;
    }
  </style>

  <script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>

  <script type="text/javascript">
    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('请选择使用W3C的websocket还是SockJS');
        return;
      }

      ws = (url.indexOf('sockjs') != -1) ?
              new SockJS(url, undefined, {protocols_whitelist: transports}) : new WebSocket(url);

      ws.onopen = function () {
        setConnected(true);
        log('Info: 连接成功.');
      };
      ws.onmessage = function (event) {
        log(event.data);
      };
      ws.onclose = function (event) {
        setConnected(false);
        log('Info: 断开连接.');
        log(event);
      };
    }

    function disconnect() {
      if (ws != null) {
        ws.close();
        ws = null;
      }
      setConnected(false);
    }

    function echo() {
      if (ws != null) {
        var message = document.getElementById('message').value;
        log('<%=session.getAttribute(Constants.SESSION_USERNAME.value())%>:' + message);
        ws.send(message);
      } else {
        alert('没有建立连接,请连接服务!');
      }
    }

    function updateUrl(urlPath) {
      if (urlPath.indexOf('sockjs') != -1) {
        url = urlPath;
        document.getElementById('sockJsTransportSelect').style.visibility = 'visible';
      }
      else {
        if (window.location.protocol == 'http:') {
          url = 'ws://' + window.location.host + urlPath;
        } else {
          url = 'wss://' + window.location.host + urlPath;
        }
        document.getElementById('sockJsTransportSelect').style.visibility = 'hidden';
      }
    }

    function updateTransport(transport) {
      alert(transport);
      transports = (transport == 'all') ?  [] : [transport];
    }

    function log(message) {
      var console = document.getElementById('console');
      var p = document.createElement('p');
      p.style.wordWrap = 'break-word';
      p.appendChild(document.createTextNode(message));
      console.appendChild(p);
      while (console.childNodes.length > 25) {
        console.removeChild(console.firstChild);
      }
      console.scrollTop = console.scrollHeight;
    }
  </script>
</head>
<body>
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websockets
  rely on Javascript being enabled. Please enable
  Javascript and reload this page!</h2></noscript>
<div>
  <div id="connect-container">
    <input id="radio1" type="radio" name="group1" onclick="updateUrl('/websocket');">
    <label for="radio1">W3C WebSocket</label>
    <br>
    <input id="radio2" type="radio" name="group1" onclick="updateUrl('/sockjs/websocket');">
    <label for="radio2">SockJS</label>
    <div id="sockJsTransportSelect" style="visibility:hidden;">
      <span>SockJS transport:</span>
      <select onchange="updateTransport(this.value)">
        <option value="all">all</option>
        <option value="websocket">websocket</option>
        <option value="xhr-polling">xhr-polling</option>
        <option value="jsonp-polling">jsonp-polling</option>
        <option value="xhr-streaming">xhr-streaming</option>
        <option value="iframe-eventsource">iframe-eventsource</option>
        <option value="iframe-htmlfile">iframe-htmlfile</option>
      </select>
    </div>
    <div>
      <button id="connect" onclick="connect();">连接服务器</button>
      <button id="disconnect" disabled="disabled" onclick="disconnect();">断开连接</button>
    </div>
    <div>
      <textarea id="message" style="width: 350px">测试消息!</textarea>
    </div>
    <div>
      <button id="echo" onclick="echo();" disabled="disabled">发送消息</button>
    </div>
  </div>
  <div id="console-container">
    <div id="console"></div>
  </div>
</div>
</body>
</html>


完整实例下载地址:https://git.oschina.net/zhmlvft/ws_test
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: