spring mvc + tomcat + websocket配置通知消息

1 篇文章 0 订阅
0 篇文章 0 订阅
本文深入探讨WebSocket协议,一种HTML5提供的全双工通信方式,对比传统AJAX轮询,展示其实现原理与优势。通过Spring框架实现了一个示例项目,详细介绍了代码配置过程,包括环境搭建、依赖引入、类设计与功能实现,最终演示了如何在前端与后端间建立稳定的数据传输通道。
摘要由CSDN通过智能技术生成

一、websocket定义

WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

为实现两个一百年的奋斗目标、实现中华名族伟大复兴而努力

二、websocket出现之前是如何通信的

之前很多网站为了前后台通讯,基本采用技术为AJAX轮询,轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。

HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

 

三、websocket使用

看了这么多,该上代码的时候了,该项目基于spring + spring mvc + tomcat +websocket实现对指定用户通知

首先新建spring 项目

ps:如何用idea新建spring+spring mvc + maven项目

pom文件添加java-webSocket包,和servlet包

        <dependency>
            <groupId>org.java-websocket</groupId>
            <artifactId>Java-WebSocket</artifactId>
            <version>1.3.0</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>3.0-alpha-1</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

新建websocket类,路径如下图

wspool里代码如下,wspool类主要功能负责添加用户及给用户发信息。

package com.ceshi.util.websocketNotice;

import org.java_websocket.WebSocket;

import java.util.*;

public class WsPool {
    private static final Map<WebSocket, String> wsUserMap = new HashMap<WebSocket, String>();

    /**
     * 通过websocket连接获取其对应的用户
     * 
     * @param conn
     * @return
     */
    public static String getUserByWs(WebSocket conn) {
        return wsUserMap.get(conn);
    }

    /**
     * 根据userName获取WebSocket,这是一个list,此处取第一个
     * 因为有可能多个websocket对应一个userName(但一般是只有一个,因为在close方法中,我们将失效的websocket连接去除了)
     * 
     * @param userName
     */
    public static WebSocket getWsByUser(String userName) {
        Set<WebSocket> keySet = wsUserMap.keySet();
        synchronized (keySet) {
            for (WebSocket conn : keySet) {
                String cuser = wsUserMap.get(conn);
                if (cuser.equals(userName)) {
                    return conn;
                }
            }
        }
        return null;
    }

    /**
     * 向连接池中添加连接
     * 
     * @param userName
     * @param conn
     */
    public static void addUser(String userName, WebSocket conn) {
        wsUserMap.put(conn, userName); // 添加连接
        System.out.println(wsUserMap);
    }

    /**
     * 获取所有连接池中的用户,因为set是不允许重复的,所以可以得到无重复的user数组
     * 
     * @return
     */
    public static Collection<String> getOnlineUser() {
        List<String> setUsers = new ArrayList<String>();
        Collection<String> setUser = wsUserMap.values();
        for (String u : setUser) {
            setUsers.add(u);
        }
        return setUsers;
    }

    /**
     * 移除连接池中的连接
     * 
     * @param conn
     */
    public static boolean removeUser(WebSocket conn) {
        if (wsUserMap.containsKey(conn)) {
            wsUserMap.remove(conn); // 移除连接
            return true;
        } else {
            return false;
        }
    }

    /**
     * 向特定的用户发送数据
     * 
     * @param conn
     * @param message
     */
    public static void sendMessageToUser(WebSocket conn, String message) {
        if (null != conn && null != wsUserMap.get(conn)) {
            conn.send(message);
        }
    }

    /**
     * 向所有的用户发送消息
     * 
     * @param message
     */
    public static void sendMessageToAll(String message) {
        Set<WebSocket> keySet = wsUserMap.keySet();
        synchronized (keySet) {
            for (WebSocket conn : keySet) {
                String user = wsUserMap.get(conn);
                if (user != null) {
                    conn.send(message);
                }
            }
        }
    }

}

新建WsService类,继承WebSocketService,检测WebSocket链接、断开、发信息、出现错误等的状态

package com.ceshi.util.websocketNotice;

import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;

import java.net.InetSocketAddress;

public class WsServer extends WebSocketServer {
    public WsServer(int port) {
        super(new InetSocketAddress(port));
    }

    public WsServer(InetSocketAddress address) {
        super(address);
    }

    @Override
    public void onOpen(WebSocket conn, ClientHandshake handshake) {
        // ws连接的时候触发的代码,onOpen中我们不做任何操作

    }

    @Override
    public void onClose(WebSocket conn, int code, String reason, boolean remote) {
        //断开连接时候触发代码
        userLeave(conn);
        System.out.println(reason);
    }

    @Override
    public void onMessage(WebSocket conn, String message) {
        System.out.println(message);
        if(null != message &&message.startsWith("online")){
            String userName=message.replaceFirst("online", "");//用户名
            userJoin(conn,userName);//用户加入
        }else if(null != message && message.startsWith("offline")){
            userLeave(conn);
        }
    }

    @Override
    public void onError(WebSocket conn, Exception ex) {
        //错误时候触发的代码
        System.out.println("on error");
        ex.printStackTrace();
    }
    /**
     * 去除掉失效的websocket链接
     * @param conn
     */
    private void userLeave(WebSocket conn){
        WsPool.removeUser(conn);
    }
    /**
     * 将websocket加入用户池
     * @param conn
     * @param userName
     */
    private void userJoin(WebSocket conn,String userName){
        WsPool.addUser(userName, conn);
    }

}

在web-info下面新建静态资源包,如下图所示

配置文件增加下面一句话,支持访问静态资源

<!-- 对静态资源文件的访问 restful -->
	<mvc:resources mapping="/static/**" location="WEB-INF/static/"/>

wbs内容如下,jquery可以从网上下载一个

var websocket = '';
var last_health;
var health_timeout = 10;
// 假装某个用户已经登录系统
var userName = "admin";
var layer;
if (window.WebSocket) {
	// 建立链接
    var URL = document.URL;
	if(URL.indexOf("https") == -1){
		websocket = new WebSocket(encodeURI('ws://' + document.domain + ':8887'));
	}else{
		websocket = new WebSocket(encodeURI('wss://' + document.domain + ':8887'));
	}
	websocket.onopen = function() {
		console.log('已连接' + userName);
		websocket.send("online" + userName);
		heartbeat_timer = setInterval(function() {
			keepalive(websocket)
		}, 60000);
	};
	websocket.onerror = function() {
		console.log('连接发生错误');
	};
	websocket.onclose = function() {
		console.log('已经断开连接');
		initWs();
	};
	// 消息接收
	websocket.onmessage = function(message) {
		alert(message.data);
	};
} else {
	alert("该浏览器不支持下单提醒。<br/>建议使用高版本的浏览器,<br/>如 IE10、火狐 、谷歌  、搜狗等");
}
var initWs = function() {
    if (window.WebSocket) {
//        websocket = new WebSocket(                encodeURI('ws://' + document.domain + ':8887'));
        websocket.onopen = function() {
            console.log('已连接');
            websocket.send("online"+userName);
            heartbeat_timer = setInterval(function() {
                keepalive(websocket)
            }, 60000);
        };
        websocket.onerror = function() {
            console.log('连接发生错误');
        };
        websocket.onclose = function() {
            console.log('已经断开连接');
            initWs();
        };
        // 消息接收
        websocket.onmessage = function(message) {
            console.log(message);
        };
    } else {
        alert("该浏览器不支持下单提醒。<br/>建议使用高版本的浏览器,<br/>如 IE10、火狐 、谷歌  、搜狗等");
    }
}

// 心跳包
function keepalive(ws) {
    var time = new Date();
    if (last_health != -1 && (time.getTime() - last_health > health_timeout)) {

        // ws.close();
    } else {
        if (ws.bufferedAmount == 0) {
            ws.send('');
            // ws.send('~HC~');
        }
    }
}

hello.jsp引入js,如下图所示

controller类中模拟发送信息,代码如下图

package com.ceshi.controller;

import com.ceshi.util.websocketNotice.WsPool;
import org.java_websocket.WebSocket;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/ceshi")
public class CeshiController {

    /**
     * 测试页面,进入页面时,加载js,websocket建立连接
     * @return
     */
    @RequestMapping("/index")
    public String ceshi(){
        return "hello";
    }

    /**
     * 测试返回数据
     * @return
     */
    @RequestMapping("/send")
    @ResponseBody
    public String index(){

        WebSocket ws = WsPool.getWsByUser("admin");
        if(ws != null){
            WsPool.sendMessageToUser(ws, "你好,admin,我是使用websocket发送的一条信息!");
            return "给admin发信息成功!";
        }else{
            return "给admin发信息失败!";
        }
    }
}

新建StartFilters类,继承Filter 类,代码如下

package com.ceshi.filter;

import com.ceshi.util.websocketNotice.WsServer;
import org.java_websocket.WebSocketImpl;

import javax.servlet.*;
import java.io.IOException;

public class StartFilters implements Filter {

    public void destroy() {

    }

    public void doFilter(ServletRequest arg0, ServletResponse arg1,
            FilterChain arg2) throws IOException, ServletException {

    }

    public void init(FilterConfig arg0) throws ServletException {
        this.startWebsocketInstantMsg();
    }

    /**
     * 启动即时聊天服务
     */
    public void startWebsocketInstantMsg() {
        WebSocketImpl.DEBUG = false;
        WsServer s;
		s = new WsServer(8887);
        s.start();
    }
}

web.xml中配置StartFilters,使启动项目时,加载WsService类,启动websocket服务,ok,至此所有配置完成,启动项目测试一下

        <filter>
		<filter-name>startFilters</filter-name>
		<filter-class>com.ceshi.filter.StartFilters</filter-class>
	</filter>

进入hello.jsp页面,F12调试模式,显示已经链接

从新开一个浏览器 ,模拟发送信息,如下图所示

此时google浏览器已经接受到发送的信息,如下图,至此websocket配置成功!

项目源码地址

链接:https://pan.baidu.com/s/1OA21g_YhaX6SanWUb-ra5A 
提取码:ngn2 

一个相信坚持写好代码可以改变生活的程序员小哥哥

坚持、热爱、奋斗、幸福

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值