SpringMvc中使用JSR356定义的WebSocket规范(tomcat8)与前端通信

参考:

WebSocket初探

【Java Web开发学习】Spring MVC整合WebSocket通信

很多时候,后端增删改查了一个数据,前端需要实时进行数据刷新,这时候,正常的Http请求就无法满足要求了(不轮询),就需要一个可以实现客户端和服务器端的长连接,双向实时通信。就是websocket。

websocket是java标准库的一部分,位于javax包下,但它只是定义一些接口。
websocket有不同的实现,如Tomcat的,jetty的,Spring的,还有一个名叫TooTallNate组织发布的java-websocket库,atmosphere库,socket.io的java版本等。

这里使用web应用服务器是tomcat8,在javax.websocket接口出来之前,tomcat7就已经对websocket提供支持了。于是在javax.websocket出来之后,tomcat8就开始废弃tomcat7中定义的websocket,tomcat7关于websocket的包位于org.apache.catalina.websocket中。

首先,tomcat8使用的javax.websocket,所以可以直接引用tomcat安装目录下/lIbrary/websocket.jar这个jar包,也可以直接在pom.xml中定义好Maven的依赖

     <!-- Web Socket-->
       <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-websocket</artifactId>
            <version>4.0.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>javax.websocket</groupId>
            <artifactId>javax.websocket-api</artifactId>
            <version>1.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>7.0</version>
        </dependency>

下面直接看服务端代码:

package com.springapp.mvc.websocket;


import com.springapp.mvc.util.JsonUtils;
import org.apache.http.util.TextUtils;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.socket.server.standard.SpringConfigurator;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * Created by qinyy on 12/10/2018.
 */

/**
 * 加configurator = SpringConfigurator.class是为了让这个类中可以通过注解的方式 注入实例
 * 比如如果不加这个选项的话,那么使用@Autowired 注解引入的实例就是null,无法被spring注入
 */
@ServerEndpoint(value = "/websocket/{id}", configurator = SpringConfigurator.class)
public class WebSocketService
{
    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static int onlineCount = 0;
    // 维护一个map,用来管理不同用户的不同socket实例
    public static HashMap<String,WebSocketService> webSocketMap = new HashMap<String, WebSocketService>();
    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
    private String currentKey;
    private org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(WebSocketService.class.getSimpleName());

    /**
     * 连接建立成功调用的方法
     * @param session  可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    @OnOpen
    public void onOpen(Session session,@PathParam(value = "id")String id){
        this.session = session;
        webSocketMap.put(id,this); // 加入map中
        currentKey = id;
        addOnlineCount();           //在线数加1
        logger.info("有新连接加入!当前在线人数为" + getOnlineCount());
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(){
        if(!TextUtils.isEmpty(currentKey))
            webSocketMap.remove(currentKey);
        subOnlineCount();           //在线数减1
        logger.info("有一连接关闭!当前在线人数为" + getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     * @param message 客户端发送过来的消息
     * @param session 可选的参数
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        logger.info("来自客户端的消息:" + message);
    }

    /**
     * 发生错误时调用
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error){
        logger.info("发生错误");
        error.printStackTrace();
    }

    public void sendObject(Object o) throws IOException
    {
        try {
            this.session.getBasicRemote().sendText(JsonUtils.encode(o));
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    /**
     *  向指定client发送信息
     * @param id
     * @param o
     */
    public static void sendObjectToSomebody(String id,Object o) throws IOException
    {
        if(webSocketMap != null && webSocketMap.containsKey(id))
            webSocketMap.get(id).sendObject(o);
    }

    /**
     *  向所有的client发送相同的数据
     * @param o
     * @throws IOException
     */
    public static void sendObjectToAll(Object o) throws IOException
    {
        Iterator iter = webSocketMap.entrySet().iterator();
        while (iter.hasNext())
        {
            Map.Entry entry = (Map.Entry) iter.next();
            WebSocketService ser = (WebSocketService) entry.getValue();
            ser.sendObject(o);
        }
    }


    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        WebSocketService.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        WebSocketService.onlineCount--;
    }

}

这里实现了简单的websocket的管理,每次websocket启动,都会从启动的url中获取到client传过来的id,用来标识一个唯一的client,这样就可以实现向指定client发送信息了。这里需要注意的是,取ID时用的注解一定是@ParamPath。不然编译的时候会保存。

下面看下前端js的代码:

// 连接websocket
var webSocket;
function initWebsocket(id)
{
    webSocket = new WebSocket("ws://localhost:18081/websocket/{"+id+"}");
    webSocket.session
    webSocket.onmessage = function (event)
    {
        onWebsockMessage(event.data);
    };
}

// 重写websocket 客户端接受函数
function onWebsockMessage(msg)
{
    var bean = JSON.parse(msg);

    if(bean.type == 1)
    {
        // 如果是有新的位置消息上传,更新下对应的operation状态
        // 根据operationId选择行
        var row = $("table.operation-table").DataTable()
            .row( function ( idx, data, node ) {

                return data.operationId == bean.data.operationId ?

                    true : false;

            } );
        row.data().excutetime = $.myTime.UnixToDate(bean.data.excutetime,true);
        row.draw();
    }
}

/**
 * 获取一个5位的随机数
 * @returns {string}
 */
function getRnd5()
{
    // 生成一個5位随机数并向server发送
    var rnd = "";
    for(var i=0;i<5;i++)
        rnd+=Math.floor(Math.random()*10);
    return rnd;
}

var socketId = getRnd5();
initWebsocket(socketId);

猜你喜欢

转载自blog.csdn.net/u010696826/article/details/86241160