Springboot 集成WebSocket作为客户端,含重连接功能,开箱即用

  • 使用演示
java 复制代码
 public static void main(String[] args) throws Exception{
        //初始化socket客户端
        BaseWebSocketClient socketClient = BaseWebSocketClient.init("传入链接");
     	//发送消息
        socketClient.sendMessage("填写需要发送的消息", (receive) -> {
            //这里编写接收消息的代码
        });
    }

只需要init后调用sendMessage方法即可,做到开箱即用。内部封装了失败重连接、断线重连接等功能。

基于Springboot工程

  • 引入websocket依赖
java 复制代码
	<!--websocket-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
  • 开箱即用的工具类
java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import javax.websocket.ClientEndpoint;
import javax.websocket.CloseReason;
import javax.websocket.ContainerProvider;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import java.io.IOException;
import java.net.URI;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 封装简易websocket客户端的类,带重连机制
 */
@ClientEndpoint
@Slf4j
public class BaseWebSocketClient {
    /**
     * 接收消息的函数,发送消息的时候传入
     */
    private Consumer<String> receiveConsumer;

    /**
     * 连接socket的url,init的时候传入
     */
    private String url;


    /**
     * 当前socket会话对象,init执行后生成
     */
    private Session session;

    /**
     * 重连延迟2.5秒执行(连接需要时间,重连的时候延迟执行)
     */
    private final Long reconnectTime = 2500L;

    /**
     * 重连次数
     */
    private AtomicInteger retryReconnectCount = new AtomicInteger(0);

    /**
     * 发送消息重试次数
     */
    private AtomicInteger reconnectSendCount = new AtomicInteger(0);

    /**
     * 发送消息最大重试次数
     */
    private final int maxReconnectSendCount = 10;

    /**
     * 初始化,初始化完才能正常使用
     *
     * @param url websocket连接的地址
     */
    public static BaseWebSocketClient init(String url) throws Exception {
        BaseWebSocketClient client = new BaseWebSocketClient();
        URI uri = new URI(url);
        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
        container.connectToServer(client, uri);
        client.setUrl(url);
        return client;
    }

    /**
     * 发送消息
     *
     * @param message         消息
     * @param receiveConsumer 接收消息的函数
     */
    public void sendMessage(String message, Consumer<String> receiveConsumer) {
        if (session == null) {
            throw new RuntimeException("socket还未初始化");
        }
        this.setReceiveConsumer(receiveConsumer);
        try {
            if (session.isOpen()) {
                //如果是open状态就能够发送消息
                session.getBasicRemote().sendText(message);
                reconnectSendCount = new AtomicInteger(0);
            } else {
                //进行重连
                this.reconnect();
                //重连2s后重新发送消息
                new Timer().schedule((new TimerTask() {
                    @Override
                    public void run() {
                        //为了防止重试次数过多,这里做一下限制,一直连接不成功的就不发消息了
                        if (reconnectSendCount.getAndIncrement() >= maxReconnectSendCount) {
                            return;
                        }
                        //再次重试发送消息
                        sendMessage(message, receiveConsumer);
                    }
                }), reconnectTime + reconnectTime);
            }
        } catch (Exception e) {
            log.error("socket发送消息失败,url:{}", url, e);
        }
    }

    /**
     * 手动关闭连接,当不使用的时候手动关闭,减少连接资源的损耗
     */
    public void close() throws IOException {
        session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "正常关闭"));
    }

    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
    }

    /**
     * 接收消息,接收消息的响应动作由使用者在send的时候自行传入
     *
     * @param message 消息内容
     */
    @OnMessage
    public void onMessage(String message) {
        receiveConsumer.accept(unicodeDecode(message));
    }

    /**
     * 关闭时的操作,分为正常关闭和异常关闭,这里异常关闭的做重连操作
     */
    @OnClose
    public void onClose(CloseReason closeStatus) throws Exception {
        if (closeStatus == null || closeStatus.getCloseCode() != CloseReason.CloseCodes.NORMAL_CLOSURE) {
            log.info("socket连接异常关闭url:{},closeStatus:{}", closeStatus, url);
            //重连
            reconnect();
        } else {
            log.info("socket连接关闭:{}", url);
        }
    }

    @OnError
    public void onError(Throwable throwable) throws Exception {
        log.error("socket连接异常,url:{}", url, throwable);
        //重连
        reconnect();
    }

    /**
     * 重连机制
     */
    private void reconnect() throws Exception {
        if (session == null || session.isOpen()) {
            return;
        }
        //schedule里的this不是当前client对象
        Object that = this;
        new Timer().schedule(new TimerTask() {
            public void run() {
                //如果是打开的就不执行重连
                if (session.isOpen()) {
                    return;
                }
                log.info("当前socket重连次数:{},url:{}", retryReconnectCount.getAndIncrement(), url);
                try {
                    URI uri = new URI(url);
                    WebSocketContainer container = ContainerProvider.getWebSocketContainer();
                    container.connectToServer(that, uri);
                    retryReconnectCount = new AtomicInteger(0);
                    log.info("重连成功");
                } catch (Exception e) {
                    log.error("socket重连失败,url:{}", url, e);
                }
            }
        }, reconnectTime);
    }

    private void setReceiveConsumer(Consumer<String> receiveConsumer) {
        this.receiveConsumer = receiveConsumer;
    }

    private void setUrl(String url) {
        this.url = url;
    }

    /**
     * unicode转中文
     */
    public static String unicodeDecode(String string) {
        if (StringUtils.isBlank(string)) {
            return string;
        }
        Pattern pattern = Pattern.compile("(\\\\u(\\p{XDigit}{4}))");
        Matcher matcher = pattern.matcher(string);
        char ch;
        while (matcher.find()) {
            ch = (char) Integer.parseInt(matcher.group(2), 16);
            string = string.replace(matcher.group(1), String.valueOf(ch));
        }
        return string;
    }
}
相关推荐
召摇2 分钟前
Java 21到25的核心API演进总结
java·后端
buddy_red9 分钟前
Knox工具调用功能测试
人工智能·后端·程序员
木头左11 分钟前
讯飞星火大模型Spark4.0Ultra的WebSocket交互实现解析
websocket·网络协议·交互
知其然亦知其所以然13 分钟前
SpringAI 玩转 OCI GenAI:这次我们聊聊 Cohere 聊天模型
java·后端·spring
种子q_q17 分钟前
Redis的三种典型的 “缓存失效” 问题
后端·面试
金銀銅鐵18 分钟前
[Java] 观察 CompactStrings 选项的影响
java·后端
程序猿二饭19 分钟前
Spring Boot 项目启动报错:MongoSocketOpenException 连接被拒绝排查日记
后端
UP19 分钟前
【C++基础】内存管理——malloc/free和new/delete之间的盘根错节
后端
paopaokaka_luck22 分钟前
绿色环保活动平台(AI问答、WebSocket即时通讯、协同过滤算法、Echarts图形化分析)
java·网络·vue.js·spring boot·websocket·网络协议·架构
wow_DG22 分钟前
【WebSocket✨】入门之旅(三):WebSocket 的实战应用
网络·websocket·网络协议