spring boot +WebSocket 广播式实例

1. 项目介绍

  • 客户端页面index.html可以输入消息,点击发送,通过ws协议发送到服务器端;
  • 服务器端收到客户端消息后,广播给各个客户端;
  • 服务器端页面server.html,可以输入消息,点击发送,通过ws双工通信推送给客户端;
  • 客户端有显示服务器端广播消息的区域。

2. 项目目录结构

3. 项目代码分析

3.1 pom.xml文件添加必要依赖

   <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--WebSocket-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>bootstrap</artifactId>
            <version>3.3.7-1</version>
        </dependency>
    </dependencies>

3.2 .java代码

  • 新建WebSocket 的配置类WebSocketConfig.java
package com.springboot.websocket.websocket.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * WebSocket配置类
 *
 */
@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

  • WebSocket的服务端
package com.springboot.websocket.websocket.config;

import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * WebSocket的服务端
 * 因为WebSocket是类似客户端服务端的形式(采用ws协议),
 * 那么这里的WebSocketServer其实就相当于一个ws协议的Controller
 * 直接@ServerEndpoint(“/websocket”)@Component启用即可,
 * 然后在里面实现@OnOpen,@onClose,@onMessage等方法
 */
@ServerEndpoint("/websocket")
@Component
public class WebSocketServer {

    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。可选的
    private static int onlineCount = 0;
    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。必须要有的
    private static CopyOnWriteArraySet<WebSocketServer>
            webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();

    //与某个客户端的连接会话,需要通过它来给客户端发送数据,必须
    private Session session;

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        webSocketSet.add(this);     //加入set中
        addOnlineCount();           //在线数加1
        //一旦连接成功后就在控制台打印信息
        System.out.println("有新窗口开始监听,当前在线人数为" + getOnlineCount());
        try {
            //往客户端发送消息
            sendMessage("连接成功");
        } catch (IOException e) {
            System.out.println("WebSocket IO异常");
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);  //从set中删除
        subOnlineCount();           //在线数减1
        System.out.println("有连接关闭!当前在线人数为" + getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        //message 是从客户端发送过来的消息,并在控制台打印
        System.out.println("收到客户端的信息:" + message);
        //群发消息
        for (WebSocketServer item : webSocketSet) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("发生错误");
        error.printStackTrace();
    }

    /**
     * 实现服务器主动推送
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }


    /**
     * 群发自定义消息
     */
    public static void sendInfo(String message) throws IOException {
        System.out.println("推送消息内容:" + message);
        for (WebSocketServer item : webSocketSet) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                continue;
            }
        }
    }

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

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

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

  • WebSocketController.java 控制层代码
package com.springboot.websocket.websocket.controller;

import com.springboot.websocket.websocket.config.WebSocketServer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.io.IOException;

/**

 * WebSocketController
 */
@Controller
@RequestMapping("/socket")
public class WebSocketController {


    /**
     * 输入消息的页面
     * @return
     */
    @GetMapping("/send")
    public String sendMsg(){
        return "server";
    }

    /**
     * 推送数据接口
     * @Param message
     * @return
     */
    @RequestMapping("/push")
    public String pushMsg(@RequestParam("message")String message) {
        try {
            WebSocketServer.sendInfo(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "server";
    }
}

3.3 html页面代码

  • 主页面index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket</title>
</head>
<body>
<h1>WebScoket练习</h1>
<label>
    <input id="msg" type="text" name="message" style="width:215px;" />
    <input type="submit" id="btn" />
</label>
<br>


<div>
<ul id="message_show_ul">
</ul>
</div>
<script language="JavaScript">
    var send_message = document.getElementById("msg");
    var sub_button = document.getElementById("btn");
    var show_ul = document.getElementById("message_show_ul");
    var socket;
    if (typeof(WebSocket) == "undefined") {
        console.log("您的浏览器不支持WebSocket");
    } else {
        console.log("您的浏览器支持WebSocket");
        //实现化WebSocket对象,指定要连接的服务器地址与端口建立连接
        socket = new WebSocket("ws://localhost:8088/websocket");

        //打开事件
        socket.onopen = function () {
            console.log("Socket已打开");
            sub_button.onclick = function () {
                console.log(send_message);
                //下面这句话是往服务器发送消息
                socket.send("这是来自客户端的消息:" + send_message.value);
            };

        };

        //获得消息事件,从服务器获得消息
        socket.onmessage = function (msg) {

            var li_eme = document.createElement("li");
            li_eme.innerHTML = msg.data;
            show_ul.appendChild(li_eme);
            //控制台打印消息
            console.log(msg.data);
            //弹出框弹出消息
           // alert(msg.data);
        };

        //关闭事件
        socket.onclose = function () {
            console.log("Socket已关闭");
        };

        //发生了错误事件
        socket.onerror = function () {
            alert("Socket发生了错误");
        }
    }
</script>
</body>
</html>
  • 服务器端页面server.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocketServer</title>
</head>
<body>
<h1>服务器端</h1>
<form id="login" action="/socket/push" method="get">
    <label>
        <input id="msg" type="text" name="message" style="width:215px;" />
        <input type="submit" id="btn"/>
    </label>
    <br>
</form>
</body>
</html>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容

  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong阅读 22,388评论 1 92
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,649评论 18 139
  • 原文地址:http://www.ibm.com/developerworks/cn/java/j-lo-WebSo...
    敢梦敢当阅读 8,901评论 0 50
  • mark今天,来日本出差一个月了,想家啊,跟妞在一起的时候有意思 今天去龟户买菜,经过地铁路口时,刚好有地铁路过,...
    Deanbian阅读 141评论 2 0
  • 付费课程报了一大堆、买书的也不少、看的公众号也挺多的,到头来没学到点什么,还是陷在知识焦虑中无法逃脱,在这时候不如...
    1astsummer阅读 369评论 5 2