WebSocket详解(二)应用实例

目前关于WebSocket写了两篇
WebSocket详解(一)基础概念
WebSocket详解(二)应用实例

上一节中我们介绍了HTTP协议的缺点以及WebSocket做了哪些改进,知道了相关的基本概念。这一节通过一个简单的例子来展示如何使用WebSocket。

这个例子的场景是这样的:
年会上,展示给观众的是一个页面,后台人员控制的是另一个页面。在后台控制页面上点击停止摇一摇,然后展示给观众的页面弹出最后的获奖者。页面控制的例子还是很常见的,也不一定用WebSocket,这里只是用WebSocket举个例子。

本例中的实现方式是针对Tomcat7的,即利用Tomcat对WebSocket的支持。为什么不利用Spring呢?
这是因为Spring4.x才添加对于WebSocket的支持。我们的项目中用的是基于Spring3.x的dubbox,改成基于Spring4.x时间来不及,因此直接使用Tomcat提供的WebSocket支持。

1 maven添加依赖

<dependency>
    <groupId>org.apache.tomcatgroupId>
    <artifactId>tomcat-catalinaartifactId>
    <version>7.0.70version>
    <scope>providedscope>
dependency>
<dependency>
    <groupId>org.apache.tomcatgroupId>
    <artifactId>tomcat-coyoteartifactId>
    <version>7.0.70version>
    <scope>providedscope>
dependency>
<dependency>
    <groupId>org.java-websocketgroupId>
    <artifactId>Java-WebSocketartifactId>
    <version>1.3.0version>
dependency>
<dependency>
    <groupId>javax.servletgroupId>
    <artifactId>servlet-apiartifactId>
    <version>2.5version>
dependency>

2 后端代码

这里用的是dubbox框架,在web层新建websocket.properties,用于存储WebSocket的对应通道。

WebSocket详解(二)应用实例_第1张图片

websocket.properties内容如下:

shake.websocket.address = ws://localhost/shakeChannel

在web.xml中注册通道

<servlet>
    <servlet-name>shakeChannelservlet-name>
    <servlet-class>com.sf.lottery.web.websocket.WebsocketChannelservlet-class>
servlet>
<servlet-mapping>
    <servlet-name>shakeChannelservlet-name>
    <url-pattern>/shakeChannelurl-pattern>
servlet-mapping>

shakeController.java

@Controller
public class ShakeController {
    private final static Logger log = LoggerFactory.getLogger(ShakeController.class);

    @Value("${shake.websocket.address}")
    private String shakeAddress;

    @ResponseBody
    @RequestMapping(value = "/shake/closeShake", method = RequestMethod.POST)
    public JsonResult closeShake(){
        //得到获奖人的代码省略,只看下面WebSocket这一段
        if(topUser != null){
            UserShakeVo luckUser = (UserShakeVo)topUser;
            result.setData(luckUser);
            try {
                //将获奖人持久化到数据库
                 userService.setUserShakeAward(luckUser.getUserId());
                //发送到前台显示
                WebSocketClient webSocketClient = WebsocketClientFactory.getWebsocketClient("shake", shakeAddress);
                webSocketClient.connectBlocking();
                webSocketClient.send(JSON.toJSONString(luckUser));
                webSocketClient.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
}

WebsocketClientFactory.java。由于Dubbox依赖于spring3.x,但是spring对于websocket支持是从4.x开始的,所以通过本地建立client访问websocket。

package com.sf.lottery.web.websocket;

import com.sf.lottery.common.utils.StrUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft_17;
import org.java_websocket.handshake.ServerHandshake;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URI;
import java.net.URISyntaxException;

/**
 * @author Hash Zhang
 * @version 1.0.0
 * @date 2016/11/30.
 */
public class WebsocketClientFactory {
    public static WebSocketClient getWebsocketClient(final String name, final String uri) throws URISyntaxException {
        return new WebSocketClient(new URI(uri), new Draft_17()) {
            private final Logger log = LoggerFactory.getLogger(WebSocketClient.class);
            @Override
            public void onMessage(String message) {
            }

            @Override
            public void onOpen(ServerHandshake handshake) {
                log.info(StrUtils.makeString("websocket client [",name,"] is established!"));
            }

            @Override
            public void onClose(int code, String reason, boolean remote) {
                log.info(StrUtils.makeString("websocket client [",name,"] is closed!"));
            }

            @Override
            public void onError(Exception ex) {
                log.warn(ExceptionUtils.getFullStackTrace(ex));
            }
        };
    }
}

WebsocketChannel.java

package com.sf.lottery.web.websocket;

import com.alibaba.dubbo.common.utils.ConcurrentHashSet;
import org.apache.catalina.websocket.MessageInbound;
import org.apache.catalina.websocket.StreamInbound;
import org.apache.catalina.websocket.WebSocketServlet;
import org.apache.catalina.websocket.WsOutbound;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Hash Zhang
 * @version 1.0.0
 * @date 2016/11/30.
 */
public class WebsocketChannel extends WebSocketServlet {
    private ConcurrentHashSet mmiList = new ConcurrentHashSet<>();

    @Override
    protected StreamInbound createWebSocketInbound(String s, HttpServletRequest httpServletRequest) {
        return new MyMessageInbound();
    }

    private class MyMessageInbound extends MessageInbound {
        WsOutbound myoutbound;

        //维护一个WebSocket连接池,新建通道的时候,就向ConcurrentHashSet添加一个WebSocket连接
        @Override
        public void onOpen(WsOutbound outbound) {
                this.myoutbound = outbound;
                mmiList.add(this);
        }

        //关闭一个通道,本质上就是ConcurrentHashSet中删除一个元素
        @Override
        public void onClose(int status) {
            mmiList.remove(this);
        }

        @Override
        public void onTextMessage(CharBuffer cb) throws IOException {
            for (MyMessageInbound mmib : mmiList) {
                CharBuffer buffer = CharBuffer.wrap(cb);
                try {
                    mmib.myoutbound.writeTextMessage(buffer);
                    mmib.myoutbound.flush();
                }catch (Exception e){
                }
            }
        }

        @Override
        public void onBinaryMessage(ByteBuffer bb) throws IOException {
        }

        @Override
        public int getReadTimeout() {
            return 0;
        }
    }
}

3 前端代码

在variable.js中,记录WebSocket通道地址:

var shakeChannelAddress="ws://XX.XX.XX.XXX/shakeChannel";

这里为什么不能继续使用localhost了?原因在于,后端的代码是部署在服务器上的,所以用localhost;但是年会现场的时候,你是用本地的浏览器去解析variable.js,想要连接通道,当然要用服务器的具体IP地址。

在摇一摇界面的js(shake.js)中,

$(document).ready(function () {
    time();
    refreshShakePage();
    //连接WebSocket通道
    var ws = new WebSocket(shakeChannelAddress);
    //打开通道时不作任何处理
    ws.onopen = function(){
    };
    //一旦收到后端从WebSocket通道发来的消息,执行该方法
    ws.onmessage = function(message){
        var shakeWinnerMessage = JSON.parse(message.data);
        var shakeWinnerHtml  = "";
    };
});

说明
如有转载,请务必注明出处
http://blog.csdn.net/antony9118/article/details/54381114

你可能感兴趣的:(网络)