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;
    }
}
相关推荐
编程洪同学36 分钟前
Spring Boot 中实现自定义注解记录接口日志功能
android·java·spring boot·后端
GraduationDesign1 小时前
基于SpringBoot的蜗牛兼职网的设计与实现
java·spring boot·后端
颜淡慕潇1 小时前
【K8S问题系列 | 20 】K8S如何删除异常对象(Pod、Namespace、PV、PVC)
后端·云原生·容器·kubernetes
customer082 小时前
【开源免费】基于SpringBoot+Vue.JS安康旅游网站(JAVA毕业设计)
java·vue.js·spring boot·后端·kafka·开源·旅游
搬码后生仔3 小时前
将 ASP.NET Core 应用程序的日志保存到 D 盘的文件中 (如 Serilog)
后端·asp.net
Suwg2093 小时前
《手写Mybatis渐进式源码实践》实践笔记(第七章 SQL执行器的创建和使用)
java·数据库·笔记·后端·sql·mybatis·模板方法模式
凡人的AI工具箱4 小时前
每天40分玩转Django:Django文件上传
开发语言·数据库·后端·python·django
spcodhu4 小时前
在 Ubuntu 上搭建 MinIO 服务器
linux·后端·minio
小码编匠4 小时前
2024 年各编程语言运行百万并发任务需多少内存?
java·后端·python
小码编匠4 小时前
C# 实现多线程启动停止暂停继续
后端·c#·.net