美文网首页
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