百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

简单websocket应用(websocket的方法)

toyiye 2024-07-06 00:14 17 浏览 0 评论

websocket长连接,概念就不描述了。先说我代码的应用场景,用户扫码支付后,后台通知到前端,前端二维码消失。废话不多说直接上代码

@Configuration
@ConditionalOnWebApplication
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

    @Bean
    public MySpringConfigurator mySpringConfigurator() {
        return new MySpringConfigurator();
    }

}

先是配置类,这就不过多的解读

@ServerEndpoint(value = "/api/noauth/websocket/{userId}")
@Service
@Scope("prototype")
@Slf4j
public class WebSocketServer {

    /**
     * concurrent包的线程安全map,用来存放每个客户端对应的MyWebSocket对象。
     */
    private static ConcurrentMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
    /**
     * 与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    private Session session;
    /**
     * 接收userId
     */
    private String userId;

    /**
     * 连接建立成功调用的方法
     **/
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        this.session = session;
        this.userId = userId;
        // 加入map中
        webSocketMap.put(userId, this);
        // 在线人数
        int i = webSocketMap.size();
        log.info("窗口开始监听:" + userId + ",当前在线人数为" + i);
        try {
            sendMessage("连接成功");
        } catch (IOException e) {
            log.error("websocket IO异常");
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        // 从map中删除
        webSocketMap.remove(userId);
        // 在线人数
        int i = webSocketMap.size();
        log.info("窗口:" + userId +"连接关闭!当前在线人数为" + i);
    }

    /**
     * 收到客户端消息后调用的方法
     * 
     * @param message
     *            客户端发送过来的消息
     **/
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("收到来自窗口" + userId + "的信息:" + message);
        // 群发消息
        webSocketMap.forEach((k, v) -> {
            try {
                // 只响应对应的客户端
                if (userId.equals(k)) {
                    if ("ping".equals(message)) {
                        v.sendMessage("pong");
                    } else {
                        v.sendMessage(message);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }

    /**
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误",error);
    }

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

    /**
     * 群发自定义消息
     * */
    public static void sendInfo(String message, @PathParam("userId") String userId) throws IOException {
        log.info("推送消息到窗口" + userId + ",推送内容:" + message);
        webSocketMap.forEach((k, v) -> {
            try {
                // 这里可以设定只推送给这个sid的,为null则全部推送
                if (userId == null) {
                    v.sendMessage(message);
                } else if (v.userId.equals(userId)) {
                    v.sendMessage(message);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }
}
有很多兄弟也看了网上代码,我这是改版,首先将CopyOnWriteArraySet转换为ConcurrentMap,以用户id为key。。。之所以这样做是因为我们系统可以在浏览器多个账户同时登录,如果用CopyOnWriteArraySet就会造成上一个登录用户会被当前用户挤占,websocket消息紊乱,接受出错。
用synchronized 将每个会话锁住,不会出现上个要发送的消息被下个会话所发送。。

@Scope("prototype")则是将@Service服务的作用范围扩大,大家都知道@Service默认都是单例的,即每个服务都是一个全新的实例,这样就会造成每次请求都重新生成一个WebSocketServer,正常情况是大家都在一个server里面。通过各自的id来区分各自的socket


前端代码:
import store from '../store';
// websocket连接
var websocket_connected_count = 0;
var websocket_heartBreak_count = 0;
var onclose_connected_count = 0;
function newWebSocket(url, onmessage) {
    var websocket = null;
    // 判断当前环境是否支持websocket
    if (window.WebSocket) {
        if (!websocket) {
            var ws_url = url.replace('https://', 'wss://').replace('http://', 'ws://')
            websocket = new WebSocket(ws_url);
        }
    } else {
        console.log("not support websocket");
    }

    // 连接成功建立的回调方法
    websocket.onopen = function (e) {
        heartCheck.reset().start();   // 成功建立连接后,重置心跳检测
        reconnectSocket.reset().start()
        console.log("websocket建立连接成功")
    }
    // 连接发生错误,连接错误时会继续尝试发起连接(尝试5次)
    websocket.onerror = function () {
        console.log("websocket连接发生错误")
        websocket_connected_count++;
        if (websocket_connected_count <= 5) {
            newWebSocket()
        }
    }
    // 接受到消息的回调方法
    websocket.onmessage = function (e) {
        console.log("接受到消息:")
        console.log(e)
        var res = checkIsJsonString(e.data)? JSON.parse(e.data):e.data;
            //执行接收到消息的操作,一般是刷新UI
            if (res) {
                if(res === 'pong'){
                    console.log("接受到心跳-pong")
                    heartCheck.reset().start();    // 如果获取到消息,说明连接是正常的,重置心跳检测
                }
                if (res.bizToken || res.hasNewMsg) {
                    store.commit('user/setbizTokenInfo',res)
                }
                if (res.type === 'welfare') {
                   store.commit('welfarePlan/SET_WELFARE_PAY_MESSAGE', res.data)
                }
            
        }
    }

    // 接受到服务端关闭连接时的回调方法
    websocket.onclose = function () {
        console.log("onclose断开连接");
    }
    // 监听窗口事件,当窗口关闭时,主动断开websocket连接,防止连接没断开就关闭窗口,server端报错
    window.onbeforeunload = function () {
        websocket.close();
    }

    // 心跳检测, 每隔一段时间检测连接状态,如果处于连接中,就向server端主动发送消息,来重置server端与客户端的最大连接时间,如果已经断开了,发起重连。
    var heartCheck = {
        timeout: 9* 60*1000,        // 9分钟发一次心跳,比server端设置的连接时间稍微小一点,在接近断开的情况下以通信的方式去重置连接时间。
        serverTimeoutObj: null,
        reset: function () {
            clearTimeout(this.timeoutObj);
            clearTimeout(this.serverTimeoutObj);
            return this;
        },
        start: function () {
            var self = this;
            this.serverTimeoutObj = setInterval(function () {
                if (websocket.readyState == 1) {
                    console.log("发送心跳-ping");
                    websocket.send("ping");
                   heartCheck.reset().start();    // 如果获取到消息,说明连接是正常的,重置心跳检测
                } else {
                    console.log("断开状态,尝试重连");
                    websocket_heartBreak_count++;
                    if (websocket_heartBreak_count <= 5) {
                        newWebSocket()
                    }else{
                        websocket.close();
                        heartCheck.reset();
                    }
                }
            }, this.timeout)
        }
    }
    var reconnectSocket={
        timeout: 9* 60*1000,        // 9分钟在接近断开的情况下以通信的方式去重置连接时间。
        serverTimeoutObj: null,
        reset: function () {
            clearTimeout(this.timeoutObj);
            clearTimeout(this.serverTimeoutObj);
            return this;
        },
        start: function () {
            var self = this;
            this.serverTimeoutObj = setInterval(function () {
                if (websocket.readyState == 1) {
                    console.log("重连。。。。。。");
                    newWebSocket(url)
                } else {
                    console.log("断开状态,尝试重连");
                    websocket_heartBreak_count++;
                    if (websocket_heartBreak_count <= 5) {
                        newWebSocket(url)
                    }else{
                        websocket.close();
                    }
                }
            }, this.timeout)
        }
    }
}
export default newWebSocket

有时候大家会遇到连接一段时间后就会断开,很多网上会说做心跳检测。。也就是上图的 heartCheck方法,但是如果你用了nginx了,你的心跳检测是不会起作用的,

哪怕你把这些参数设置很长了(比如我们nginx设置了10分钟的连接时间了,这已经算是长的了)

keepalive_timeout
proxy_connect_timeout
proxy_send_timeout
proxy_read_timeout

到时效了也会断开连接,所以我这边又加了一个重连机制reconnectSocket,即在9分钟时进行一次重连操作,这样就可无缝持续的连接了。

相关推荐

如何用 coco 数据集训练 Detectron2 模型?

随着最新的Pythorc1.3版本的发布,下一代完全重写了它以前的目标检测框架,新的目标检测框架被称为Detectron2。本教程将通过使用自定义coco数据集训练实例分割模型,帮助你开始使...

CICD联动阿里云容器服务Kubernetes实践之Bamboo篇

本文档以构建一个Java软件项目并部署到阿里云容器服务的Kubernetes集群为例说明如何使用Bamboo在阿里云Kubernetes服务上运行RemoteAgents并在agents上...

Open3D-ML点云语义分割实验【RandLA-Net】

作为点云Open3D-ML实验的一部分,我撰写了文章解释如何使用Tensorflow和PyTorch支持安装此库。为了测试安装,我解释了如何运行一个简单的Python脚本来可视化名为...

清理系统不用第三方工具(系统自带清理软件效果好不?)

清理优化系统一定要借助于优化工具吗?其实,手动优化系统也没有那么神秘,掌握了方法和技巧,系统清理也是一件简单和随心的事。一方面要为每一个可能产生累赘的文件找到清理的方法,另一方面要寻找能够提高工作效率...

【信创】联想开先终端开机不显示grub界面的修改方法

原文链接:【信创】联想开先终端开机不显示grub界面的修改方法...

如意玲珑成熟度再提升,三大发行版支持教程来啦!

前期,我们已分别发布如意玲珑在deepinV23与UOSV20、openEuler24.03发行版的操作指南,本文,我们将为大家详细介绍Ubuntu24.04、Debian12、op...

118种常见的多媒体文件格式(英文简写)

MP4[?mpi?f??]-MPEG-4Part14(MPEG-4第14部分)AVI[e?vi??a?]-AudioVideoInterleave(音视频交错)MOV[m...

密码丢了急上火?码住7种console密码紧急恢复方式!

身为攻城狮的你,...

CSGO丨CS2的cfg指令代码分享(csgo自己的cfg在哪里?config文件位置在哪?)

?...

使用open SSL生成局域网IP地址证书

某些特殊情况下,用户内网访问多可文档管理系统时需要启用SSL传输加密功能,但只有IP,没有域名和证书。这种情况下多可提供了一种免费可行的方式,通过openSSL生成免费证书。此方法生成证书浏览器会提示...

Python中加载配置文件(python怎么加载程序包)

我们在做开发的时候经常要使用配置文件,那么配置文件的加载就需要我们提前考虑,再不使用任何框架的情况下,我们通常会有两种解决办法:完整加载将所有配置信息一次性写入单一配置文件.部分加载将常用配置信息写...

python开发项目,不得不了解的.cfg配置文件

安装软件时,经常会见到后缀为.cfg、.ini的文件,一般我们不用管,只要不删就行。因为这些是程序安装、运行时需要用到的配置文件。但对开发者来说,这种文件是怎么回事就必须搞清了。本文从.cfg文件的创...

瑞芯微RK3568鸿蒙开发板OpenHarmony系统修改cfg文件权限方法

本文适用OpenHarmony开源鸿蒙系统,本次使用的是开源鸿蒙主板,搭载瑞芯微RK3568芯片。深圳触觉智能专注研发生产OpenHarmony开源鸿蒙硬件,包括核心板、开发板、嵌入式主板,工控整机等...

Python9:图像风格迁移-使用阿里的接口

先不多说,直接上结果图。#!/usr/bin/envpython#coding=utf-8importosfromaliyunsdkcore.clientimportAcsClient...

Python带你打造个性化的图片文字识别

我们的目标:从CSV文件读取用户的文件信息,并将文件名称修改为姓名格式的中文名称,进行规范资料整理,从而实现快速对多个文件进行重命名。最终效果:将原来无规律的文件名重命名为以姓名为名称的文件。技术点:...

取消回复欢迎 发表评论:

请填写验证码