前言:

        业务需要实时通讯,所以就调研了一下。整体感觉 websocket 使用门槛低、配置简单、稳定性相对较高。

一、核心依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
    <version>2.7.0</version>
</dependency>


<!-- 日志注解用的是 lombok 的 @Slf4j,可以按照自己的习惯替换掉-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
</dependency>

二、配置类

websocket 的配置类就是一个简单的配置声明,没有任何特殊的地方。

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 endpointExporter() {
        return new ServerEndpointExporter();
    }
}

三、服务

这块是最重要的地方,关键地方都写了注释;其中几个有注解的方法主要是给前端调用的。

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

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

@Component
@Slf4j
@Service
@ServerEndpoint("/websocket/{clientId}")
public class WebSocketServer {
    //静态变量,用来记录当前在线连接数
    private static int onlineCount = 0;

    //用来存放每个客户端对应的的连接信息
    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();

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

    //连接的 clientId
    private String clientId = "";

    /**
     * 前端连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("clientId") String clientId) {
        this.session = session;
        this.clientId = clientId;
        webSocketSet.add(this);
        addOnlineCount();
        try {
            String msg = "clientId:" + clientId + "连接成功,当前在线客户端数:" + getOnlineCount();
            sendMessage(msg);
            log.info(msg);
        } catch (IOException e) {
            log.error("client:{},连接故障,原因:{}", clientId, e.getMessage());
        }
    }

    /**
     * 前端连接关闭调用的方法
     * 可以在里面写一些关闭连接需要处理的逻辑
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);
        subOnlineCount();
        log.info("释放连接,clientId:{};当前连接数:{}", clientId, getOnlineCount());
    }

    /**
     * 该方法用于接受客户端发送的消息
     * 当前逻辑是:相同 clientId 的连接可以接受消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("收到来自clientId:{} 的消息:{}", clientId, message);

        for (WebSocketServer item : webSocketSet) {
            try {
                if (item.clientId.equals(clientId)) {   //广播发送的话就去掉if
                    item.sendMessage(message);
                }
            } catch (IOException e) {
                log.error("client:{} 发送消息故障,原因:{}", item.clientId, e.getMessage());
            }
        }
    }

    /**
     * 用于记录发生的错误
     */
    @OnError
    public void onError(Session session, Throwable e) {
        log.error("出现故障:{}", e.getMessage());
    }

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

    /**
     * 定向 clientId 发送消息
     */
    public void sendInfo(String clientId, String message) throws IOException {
        log.info("推送消息到clientId:{},msg:{}", clientId, message);
        for (WebSocketServer item : webSocketSet) {
            if (item.clientId.equals(clientId)) {
                item.sendMessage(message);
            }
        }
    }

    /**
     * 群发消息,广播形式
     */
    public void sendInfoAll(String message) throws IOException {
        for (WebSocketServer item : webSocketSet) {
            item.sendMessage(message);
        }
    }

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

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

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

    public static CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() {
        return webSocketSet;
    }
}

四、测试

测试需要一个前端,这简直就是难为搞后端的同学,所以我在网上找了一个别人写好的前端。

打开网址:http://www.websocket-test.com

启动本地websocket的工程,输入本地websocket的地址(ws://localhost:port/{路由}/{clientId}),点击连接(先断开之前的连接)

spring boot wsdl客户端 spring boot starter websocket_spring boot

注意:

1.因为当前 onMessage 方法是按照 clientId 相同才能接受消息,所以当发消息的时候只能是相同的 clientId 收到消息(可以再打开两个页面分别输入与上述不同和相同的clientId做测试)。如果需要广播就把判断条件去掉。

2.如果想通过后端推送消息到前端,可以调用  WebSocketServer 的 sendInfo 和 sendInfoAll 方法。