springboot整合websocket实现服务器端获取当前在线数并向客户端主动发送消息

参考链接:

springboot 整合websocket 站内消息 (支持广播式和只给一人发送)单独信息发送 信息群发 统计连接数_苏雨丶的博客-CSDN博客_springboot站内信

springboot整合websocket(1)_慕课手记

WebSocket 注解 (一)_b7410852963的博客-CSDN博客_websocket注解

项目目录结构:

1.创建一个jar类型的项目(使用内置容器),在pom文件中添加websocket的依赖:

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.wyh</groupId>
    <artifactId>springboot_websocket</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot_websocket</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2. 创建一个websocket的配置类:

package com.wyh.springboot_websocket.demo;

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

/**
 * @author: wyh
 * @description:webSocket配置类
 * @date: 12/22/2018 14:51
 */
@Configuration
public class WebSocketConfig {


    /*首先在该类中注入一个ServerEndpointExporter的bean,
     *ServerEndpointExporter这个bean会自动注册使用了@ServerEndpoint这个注解的websocket endpoint
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}

3.使用@ServerEndpoint注解声明websocket服务器端点MySocketServer,在该类中处理websocket的具体逻辑。

package com.wyh.springboot_websocket.demo;

import org.springframework.stereotype.Component;

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

/**
 * @author: wyh
 * @description:websocket服务端点,处理具体逻辑
 * @date: 12/22/2018 15:09
 */
@ServerEndpoint(value="/myWebsocket/{userid}")//该注解是一个类级别的注解,它的作用就是将当前类定义为一个websocket的服务器端,value的值表示访问路径。
@Component//将该类注册到spring容器中,个人理解这里并没有用到分层,所以不必使用@controller等分化后的注解
public class MySocketServer {

    private Session session;//记录当前连接,每个客户端都有对应的session,服务器端通过它向客户端发送消息
    //private static Map<String,Session> sessionPool = new HashMap<String,Session>();//存储所有userid及对应的session
    //private static Map<String,String> sessionIds = new HashMap<String,String>();//存储所有的sessionid和userid
    //因为HashMap是线程不安全的,所以我们使用concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
    private static CopyOnWriteArraySet<MySocketServer> webSocketSet = new CopyOnWriteArraySet<MySocketServer>();//泛型就是当前类
    private static int onLineNum = 0;//静态变量,记录当前在线连接数,所有对在线数的操作都应加上synchhronized关键字以确保线程安全
    /**
     * @author wyh
     * @description  有新的链接建立成功时调用该方法
     * @param session
     * @param userid
     * @return void
     **/
    @OnOpen//该注解表示当有新用户连接时调用该方法
    public void open(Session session, @PathParam(value="userid")String userid){//@PathParam(value="userid")表示从访问路径上获取参数赋值给当前形参,value的值与路径上的名字一致
        this.session = session;//获取当前session
        webSocketSet.add(this);//将当前session加入到set中
        addOnlineNum();//在线数增加
    }


    /**
     * @author wyh
     * @description  接收到消息时调用该方法
     * @param message
     * @return void
     **/
    @OnMessage//该注解表示当服务器端收到客户端发送的消息时调用该方法
    public void onMessage(String message){

        System.out.println("当前发送人sessionid为"+session.getId()+",发送内容为:"+message);
    }



    /**
     * @author wyh
     * @description  断开连接时调用该方法
     * @param
     * @return void
     **/
    @OnClose//该注解表示当前用户连接断开时调用该方法
    public void onClose(){
        webSocketSet.remove(this);
        subOnlineNum();//在线数减1
    }


    /**
     * @author wyh
     * @description 出现错误时调用
     * @param session
     * @param error
     * @return void
     **/
    @OnError
    public void onError(Session session,Throwable error){
        System.out.println("发生错误:"+error.getMessage()+",sessionid为"+session.getId());
        error.printStackTrace();
    }



    /**
     * @author wyh
     * @description  服务器端主动发送消息
     * @param message
     * @return void
     **/
    public void sendMessage(String message) throws IOException {
       this.session.getBasicRemote().sendText(message);
        System.out.println("发总给指定客户端,sessionid为:"+this.session.getId()+"消息为:"+message);
    }



    /**
     * @author wyh
     * @description  群发消息
     * @param message
     * @return void
     **/
    public void sendMessages(String message){
        for(MySocketServer s : webSocketSet){
            try{
                s.session.getBasicRemote().sendText(message);
                System.out.println("群发给sessionid为:"+s.session.getId()+"的客户端,消息为:"+message);
            }catch (IOException e){
                e.printStackTrace();
            }

        }
    }



    /**
     * @author wyh
     * @description  获取当前在线数,使用关键字
     * @param
     * @return int
     **/
    public synchronized int getOnlineNum(){
        return onLineNum;
    }


    /**
     * @author wyh
     * @description  在线数加1,注意关键字
     * @param
     * @return void
     **/
    public static synchronized void addOnlineNum(){
        onLineNum++;
    }


    /**
     * @author wyh
     * @description  在线数减1
     * @param
     * @return void
     **/
    public static synchronized void subOnlineNum(){
        onLineNum--;
    }





}

4.Controller:

package com.wyh.springboot_websocket.demo;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
 
/**
 * @author: wyh
 * @description: 推送消息的controller
 * @date: 12/22/2018 17:26
 */
 
@Controller
public class WebSocketController {
 
    @Autowired
    private MySocketServer socketServer;
 
 
    //访问服务器端的统计信息页面
    @RequestMapping("/count")
    public String onLineNum(Model model){
        int count = socketServer.getOnlineNum();
        model.addAttribute("num",count);//把在线数放到model中,前端直接获取
        return "server";//服务端发送信息的页面
    }
 
 
 
    //访问客户端首页
    @RequestMapping("/index")
    public String index(){
        return "client";//跳转到客户端首页
    }
 
 
    @RequestMapping("/sendMessages")
    public String sendMessages(String msg){
        socketServer.sendMessages(msg);
        return "server";//如果该方法返回void,那么运行时会抛出org.thymeleaf.exceptions.TemplateInputException: Error resolving template
    }
 
}

5.application.properties:

#thymeleaf start
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.servlet.content-type=text/html
#开发时关闭缓存,不然没法看到实时页面
spring.thymeleaf.cache=false
#thymeleaf end

6.客户端页面:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>客户端首页</title>
    <!--这里必须是双标签-->
    <!--<script th:src="@{/js/jquery.min.js}"/>-->
    <script th:src="@{/js/jquery.min.js}"></script>
</head>
<body>
    请输入您的用户id<input type="text" id="user"/>
                <input type="button" value="连接" onclick="connect()"/><br/>
    请填写要发送的内容<input type="text" id="writeMsg"/>
                    <input type="button" value="发送" onclick="sendMsg()"/>

<script type="text/javascript">

    //放在这里是获取不到值的,应该放在function里面
    //var user = $("#user").val();
    var ws = null;//不能在send()里再新建ws,因为ws建立连接时已存在
    //创建连接
    function connect(){

        var user = $("#user").val();
        alert(user);
        if(user != null){
            if('WebSocket' in window){
                ws = new WebSocket("ws://localhost:8080/myWebsocket/"+user);//这里的Username其实我们还是用userid
            }else if('MozWebSocket' in window){
                ws = new MozWebSocket("ws://localhost:8080/myWebsocket/"+user);
            }else{
                alert("该浏览器不支持websocket");
            }


            ws.onmessage = function(evt){
                alert(evt.data);
            }


            ws.onclose = function(evt){
                alert("连接中断");
            }


            ws.onopen = function(evt){
                alert("连接成功");
            }

        }else {//userid为空
            alert("请输入您的uerid");

        }

    }


    //发送消息
    function sendMsg(){

        
        ws.send($("#writeMsg").val());
    }
</script>
</body>
</html>

7.服务器端管理页面:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>服务器端发送信息的页面</title>
    <script th:src="@{/js/jquery.min.js}"></script>
</head>
<body>
    当前在线人数总计<div id="sum" th:text="${num}"></div>
    向客户端发送消息<input type="text" id="msg"/><br/>
    <input type="button" value="全体发送" onclick="sendAll()"/>


<script type="text/javascript">
    function sendAll() {
        var msg = $("#msg").val();
        if(msg != null){
            $.ajax({
                method:'get',
                url:'/sendMessages',
                data:{
                    msg:msg
                },
                success:function (data) {
                    console.log(data);
                }
                }

            )
        }else{
            alert("请填写要发送的内容");
        }
    }
</script>

</body>
</html>

8.启动服务,访问客户端,打开多个会话(此处创建3个会话),再访问服务器发送消息页面(此时还未创建会话),截图中的第三个id(765)可以忽略,忘了刷新:

使三个会话都创建连接:

以上可以看到三个会话连接都已创建成功,然后我们分别向服务器发送消息:

此时访问服务器端发送消息的页面:

在服务器端的页面向所有当前会话发送消息:

点击全体发送后,我们再看当前三个客户端,都收到了服务器端发来的信息:

最后来看一下控制台的打印信息:

 

  • 1
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

QYHuiiQ

听说打赏的人工资翻倍~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值