美文网首页
SpringBoot 集成websocket

SpringBoot 集成websocket

作者: CXY_XZL | 来源:发表于2019-06-11 22:19 被阅读0次

    一、示例概述

           本示例代码简单,用来研究websocket内部方法及它们的调用关系。示例前端做了一个很丑的页面,用于展示聊天界面,其中用到了富文本编辑器wangEditor,后端还是常规的代码

    二、代码结构目录

    代码目录.png

    三、代码展示

    1、pom.xml

    <?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.5.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>mxyz.xiongzelin</groupId>
        <artifactId>websocket2</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>websocket2</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.58</version>
            </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>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <optional>true</optional>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <configuration>
                        <fork>true</fork>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
    
    

    2、application.properties

    server.port=1111
    
    # 定位模板的目录
    #当引入模板引擎jar包时,默认的静态根路径变成了templates
    #但如果是html静态文件,默认资源根目录为static下
    spring.mvc.view.prefix=/html/
    
    # 给返回的页面添加后缀名
    spring.mvc.view.suffix=.html
    
    spring.devtools.restart.enabled=true
    
    #设置重启的目录
    spring.devtools.restart.additional-paths=src/main/java
    #classpath目录下的static文件夹内容修改不重启
    spring.devtools.restart.exclude=static/**
    
    #页面不加载缓存,修改即时生效
    spring.freemarker.cache=false
    spring.thymeleaf.cache=false
    

    3、WebsocketServer.java代码

    package mxyz.xiongzelin.websocket2.component;
    
    import com.alibaba.fastjson.JSON;
    import org.springframework.stereotype.Component;
    import javax.websocket.*;
    import javax.websocket.server.PathParam;
    import javax.websocket.server.ServerEndpoint;
    import java.io.IOException;
    import java.lang.reflect.Array;
    import java.text.SimpleDateFormat;
    import java.util.*;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
    * @description: 
    *
    * @author: xiongzelin
    *
    * @create: 2019/06/10
    **/
    @Component
    @ServerEndpoint("/web/{username}")
    public class WebsocketServer {
        private static int count = 0;
        private static ConcurrentHashMap<String,WebsocketServer> clients = new ConcurrentHashMap<>();
        private String guid;
        private Session session;
        private String username;
    
        //在线人数增加方法
        private static synchronized void addOnlineCount(){
            WebsocketServer.count++;
        }
        //在线人数减少方法
        private static synchronized void subOnlineCount(){
            WebsocketServer.count--;
        }
    
        //获取在线人数方法
        public static synchronized int getOnlineCount(){
            return WebsocketServer.count;
        }
    
        //消息群发
        public void sendMessageAll(String message,String username) throws IOException {
    
            Collection<WebsocketServer> wss = getClients().values();
    //        wss.forEach(ws ->{
    //            synchronized (ws){
    //                if (!ws.getUsername().equals(guid)){
    //                        ws.session.getAsyncRemote().sendText(message);
    //                }
    //            }
    //        });
            Iterator<WebsocketServer> iterator = wss.iterator();
            while (iterator.hasNext()){
                WebsocketServer ws = iterator.next();
                if (!ws.getUsername().equals(username)){
    
                    //注意getBasicRemote()和getAsyncRemote()的区别,用错会报错,报错异常信息:java.lang.IllegalStateException: The remote endpoint was in state [TEXT_FULL
                    ws.session.getBasicRemote().sendText(message);
                }
            }
    
        }
    
        //消息单发
        public void sendMessageForOne(String message,String guid)throws IOException{
            clients.get(guid).session.getBasicRemote().sendText(message);
        }
    
        /**
         * 用户建立连接时调用此方法
         */
        @OnOpen
        public void onOpen(@PathParam("username") String username, Session session) throws IOException{
    
            this.guid = UUID.randomUUID().toString();
            this.username = username;
            this.session = session;
    
            //当用户账号在另一台设备登陆时,当前账号被挤出来,类似qq
            for(Map.Entry<String,WebsocketServer> entry : getClients().entrySet()){
                if (entry.getValue().getUsername().equals(username)  && !entry.getValue().getGuid().equals(getGuid())){
                    sendMessageForOne("refuse",entry.getKey());
                    System.out.println("退出人:"+entry.getKey());
                    getClients().remove(entry.getKey());
    
                    sendMessageAll("用户 "+username+" 已离开!",username);
                    subOnlineCount();
                    System.out.println("用户 "+ username+" 已离开!,用户 id 是: "+ entry.getKey() +" ,当前人数 "+getCount()+" 人 ,剩余聊友:" + getGuids());
                    break;
                }
            }
    
    
            addOnlineCount();
            WebsocketServer.clients.put(guid,this);
            System.out.println("用户 "+username+" 已上线!,用户 id 是: "+ getGuid() +" ,当前人数 "+getCount()+" 人,剩余聊友:" + getGuids());
            sendMessageAll("用户 "+username+" 已上线!",getUsername());
    
        }
    
        /**
         * 接收到客户端消息时调用
         * @param message 
         * @param session
         * @throws IOException
         */
        @OnMessage
        public synchronized void onMessage(String message,Session session)throws IOException{
            System.out.println("来自 "+getUsername()+" 的消息:" + message + ",用户 id 是: "+ getGuid());
            
            sendMessageAll("来自 "+getUsername()+" 的消息:&nbsp;&nbsp;&nbsp;&nbsp;--" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Calendar.getInstance().getTime()) + message,getUsername());
        }
    
        //关闭连接时调用,当客户端关闭连接时,服务端也会调用此方法
        @OnClose
        public void onClose() throws IOException{
    
            //前端页面刷新时会再次调用此方法
            if (getGuids().contains(getGuid())){
    
                getClients().remove(getGuid());
                sendMessageAll("用户 "+getUsername()+" 已离开!",getUsername());
    
                subOnlineCount();
                System.out.println("用户 "+getUsername()+" 已离开!,用户 id 是: "+ getGuid()  +" ,当前人数 "+getCount()+" 人 ,剩余聊友:" + getGuids());
            }
    
        }
    
        //发生错误时调用
        @OnError
        public void onError(Session session, Throwable error) {
            error.printStackTrace();
        }
    
        //获取所有连接
        public static synchronized Map<String, WebsocketServer> getClients() {
            return clients;
        }
    
        public static int getCount() {
            return count;
        }
    
        public Session getSession() {
            return session;
        }
    
        public String getUsername() {
            return username;
        }
    
        public String getGuid() {
            return guid;
        }
        public String getGuids(){
            return  JSON.toJSON(getClients().keySet()).toString();
        }
    }
    
    
    

    4、WebsocketConfig.java代码

    package mxyz.xiongzelin.websocket2.conf;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.socket.server.standard.ServerEndpointExporter;
    
    @Configuration
    public class websocketConfig {
    
        @Bean
        public ServerEndpointExporter serverEndpointExporter(){
            return new ServerEndpointExporter();
        }
    }
    

    上面是配置文件代码,不能少,不然也会出问题

    5、WebsocketControllor.java代码

    package mxyz.xiongzelin.websocket2.controllor;
    
    import mxyz.xiongzelin.websocket2.component.WebsocketServer;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    /**
    * @description: 用户通过此接口进入聊天界面
    *
    * @author: xiongzelin
    *
    * @create: 2019/06/11
    **/
    @Controller
    @RequestMapping("/websocket")
    public class WebsocketControllor {
    
        @Autowired
        private WebsocketServer websocketServer;
    
        @RequestMapping("/send")
        public String sendMsg(){
            return "websocket3";
        }
    }
    

    四、效果展示

    浏览器地址栏输入 http://localhost:1111/websocket/send ,会出现如下界面:

    首页.png

    这个输入框的限制就是用户名为空或者空格

    登陆以后界面如下:

    登陆后界面.png

    可以多浏览器、多窗口打开此页面,然后就可以在编辑器中输入内容了,效果如下:

    聊天界面.png

    如果大家想看一下全部代码,可以从 github 拉取代码

    五、参考资料

    WebSocket详解教程
    Java中Spring WebSocket详解
    SpringBoot2.0集成WebSocket,实现后台向前端推送信息
    SpringBoot集成WebSocket实现群聊,后台消息推送

    相关文章

      网友评论

          本文标题:SpringBoot 集成websocket

          本文链接:https://www.haomeiwen.com/subject/kypcfctx.html