it编程 > 网页制作 > html5

《知识点扫盲 · 学会 WebSocket》

287人参与 2024-08-01 html5

csdn.gif

写在前面的话

本系列的上篇文章《知识点扫盲 · 学会 webservice》介绍了企业开发中webservice技术的实际应用,这边继续介绍一下websocket的基础应用,希望可以帮助到大家。
这里先介绍实战运用,深入的部分后续专题介绍,让我们开始!


长连接 websocket

技术简介

1、websocket 是 html5 开始提供的一种浏览器与服务器进行全双工通讯的网络技术,属于应用层协议。
2、websocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。
3、websocket api中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
4、虽然前后端都可以互相推数据,但主要还是后端推送给前端,常见运用场景:实时聊天、通知公告、视频弹幕。

四个事件

open socket.onopen 连接建立时触发
message socket.onmessage 客户端接收服务端数据时触发
error socket.onerror 通信发生错误时触发
close socket.onclose 连接关闭时触发

springboot 整合 ws

step1、引入依赖

 <dependency>
     <groupid>org.springframework.boot</groupid>
     <artifactid>spring-boot-starter-websocket</artifactid>
 </dependency>

step2、添加配置类

@configuration
public class websocketconfig {

    /**
     * 	注入serverendpointexporter,
     * 	这个bean会自动注册使用了@serverendpoint注解声明的websocket endpoint
     */
    @bean
    public serverendpointexporter serverendpointexporter() {
        return new serverendpointexporter();
    }
}

step3、添加具体socket服务

@component
@slf4j
@serverendpoint("/websocket/{userid}")
public class websockethandle {

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

    /**
     * 用户id
     */
    private string userid;

    /**
     * concurrent包的线程安全set,用来存放每个客户端对应的mywebsocket对象。
     * 虽然@component默认是单例模式的,但sb还是会为每个websocket连接初始化一个bean,所以可以用一个静态set保存起来。
     */
    private static final copyonwritearrayset<websockethandle> web_sockets = new copyonwritearrayset<>();

    /**
     * 用来存在线连接用户信息
     */
    private static final concurrenthashmap<string, session> session_pool = new concurrenthashmap<>();

    @override
    public boolean equals(object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getclass() != o.getclass()) {
            return false;
        }
        websockethandle that = (websockethandle) o;
        return objects.equals(session, that.session) && objects.equals(userid, that.userid);
    }

    @override
    public int hashcode() {
        return objects.hash(session, userid);
    }

    /**
     * 链接成功调用的方法
     */
    @onopen
    public void onopen(session session, @pathparam(value = "userid") string userid) {

        //初始化设置当前实例的信息
        this.session = session;
        this.userid = userid;

        //加入全局socket管理(set)
        web_sockets.add(this);

        //加入全局session管理(map)
        session_pool.put(userid, session);

        log.info("websocket有新的连接,用户为:{}, 总数为:{}", userid, web_sockets.size());
    }

    /**
     * 链接关闭调用的方法
     */
    @onclose
    public void onclose(session session) {
        web_sockets.remove(this);
        session_pool.remove(this.userid);
        log.info("websocket有连接断开,用户为:{}, 总数为:{}", userid, web_sockets.size());
    }

    /**
     * 收到客户端消息后调用的方法
     */
    @onmessage
    public void onmessage(session session, string message) {
        log.info("websocket收到客户端消息,用户为:{}, 消息为:{}:", this.userid, message);
    }

    /**
     * 发送错误时的处理
     */
    @onerror
    public void onerror(session session, throwable error) {
        log.error("用户错误,原因:" + error.getmessage());
        error.printstacktrace();
    }

    /**
     * 发消息给全部人
     */
    public void sendallmessage(string message) {
        log.info("【websocket消息】广播消息:" + message);
        for (websockethandle websocket : web_sockets) {
            try {
                if (websocket.session.isopen()) {
                    websocket.session.getasyncremote()
                            .sendtext(message);
                }
            } catch (exception e) {
                e.printstacktrace();
            }
        }
    }

    /**
     * 发消息给单个人
     */
    public void sendonemessage(string userid, string message) {
        session session = session_pool.get(userid);
        if (session != null && session.isopen()) {
            try {
                log.info("【websocket消息】 单点消息:" + message);
                session.getasyncremote()
                        .sendtext(message);
            } catch (exception e) {
                e.printstacktrace();
            }
        }
    }

    /**
     * 发消息给多个人
     */
    public void sendmoremessage(string[] userids, string message) {
        for (string userid : userids) {
            session session = session_pool.get(userid);
            if (session != null && session.isopen()) {
                try {
                    log.info("【websocket消息】 单点消息:" + message);
                    session.getasyncremote()
                            .sendtext(message);
                } catch (exception e) {
                    e.printstacktrace();
                }
            }
        }
    }
}

step4、测试服务端效果
参考:websocket在线测试
按上述步骤改造完,就可以得到ws的服务端,地址形如:ws://127.0.0.1:8180/websocket/123
如果想不写客户端,可以直接使用测试网站进行测试,如下图。
image.png

step5、测试发送消息
随便写一个接口,就可以触发调用了,看看客户端是否有接收到。
注意,这里的websockethandle确实是单例的,但是单个socket连接也是存在的,而且bean实例里面的static类型的map和set还是存了后续想要操作的内容。

@requestmapping("/sockettest")
public void sockettest(string id) throws exception {
    websockethandle.sendonemessage(id, "hello~");
}

前端 js 实现 ws

前端使用websocket,可以用原生的方式,参考如下。
当然,也可以引用第三方库,比较出名的是socket.io。

/**
 * 备忘
 * 页面地址:http://localhost:8180/test/socketdemo.html
 * 后端发消息:http://localhost:8180/sockettest?id=123
 */

if ("websocket" in window) {

    let $scope = {}
    let wsurl = "ws://127.0.0.1:8180/websocket/123"
    let ws;
    let tt;
    let lockreconnect = false;

    // 创建ws链接
    $scope.createwebsocket = function (wsurl) {
        try {
            ws = new websocket(wsurl);
            $scope.websocketinit();
        } catch (e) {
            lockreconnect = false
            $scope.websocketreconnect(wsurl)//重连函数
        }
    };

    // 初始化ws的方法
    $scope.websocketinit = function () {

        ws.onclose = function (error) {
            //连接关闭的回调函数,进行重连
            console.log("连接已关闭...", error);
            $scope.websocketreconnect(wsurl)
        };

        ws.onerror = function (error) {
            //连接错误的回调函数,进行重连
            console.log("连接错误...", error);
            $scope.websocketreconnect(wsurl)
        };

        ws.onopen = function () {//连接建立
            //发一个初始化连接消息
            ws.send('初始化连接');
            //启动心跳检测
            $scope.heartcheck.start();
        };

        ws.onmessage = function (event) {
            if(event.data !== 'pong'){
                let $test = $('#texta')
                let temp = $test.text();
                $test.text(temp + event.data + "\r\n");
                console.log("收到后端的消息:", event.data);
            } else {
                console.log('收到pong消息,连接还正常~')
            }

            //接收一次后台推送的消息,即进行一次心跳检测重置
            $scope.heartcheck.reset();
        };
    };

    $scope.websocketreconnect = function (url) {
        console.log("socket 连接断开,正在尝试重新建立连接");

        //todo 下面这段代码会导致只重连一次,后续改进了再开放
        //todo 长时间如果没收到pong消息应该也要处理,提示一下报错之类的
        /*if (lockreconnect) {
            return;
        }
        lockreconnect = true;*/
        //没连接上会一直重连,设置延迟,避免请求过多
        tt && cleartimeout(tt);
        tt = settimeout(function () {
            $scope.createwebsocket(url);
        }, 4000)
    };

    //心跳检测
    //onopen连接上,就开始start及时,如果在定时时间范围内,onmessage获取到了服务端消息,就重置reset倒计时,距离上次从后端获取消息30秒后,执行心跳检测,看是不是断了。
    $scope.heartcheck = {
        timeout: 5000, //默认30秒
        timeoutobj: null,
        reset: function () { //接收成功一次推送,就将心跳检测的倒计时重置为30秒
            cleartimeout(this.timeoutobj);//重置倒计时
            this.start();
        },
        start: function () {//启动心跳检测机制,设置倒计时30秒一次
            this.timeoutobj = settimeout(function () {
                //启动心跳
                ws.send("ping");
            }, this.timeout)
        }
    };

    //开始创建websocket连接
    $scope.createwebsocket(wsurl);

    // 点击发消息给后端,后端收到后回复信息
    function sendmessage() {
        let message = document.getelementbyid("messageinput").value;
        if (message) {
            ws.send(message);
        }
    }

} else {
    // 浏览器不支持 websocket
    alert("您的浏览器不支持 websocket!");
}

总结陈词

此篇文章介绍了websocket的基础应用,仅供学习参考。
💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

csdn_end.gif

(0)
打赏 微信扫一扫 微信扫一扫

您想发表意见!!点此发布评论

推荐阅读

【虚幻引擎】UE4初学者系列教程基础篇-全中文新手入门教程

08-01

OpenCV 颜色检测

08-02

HTML5 音频 Audio 标签详解

08-02

HTML5使用<blockquote>标签实现段落缩进效果

07-18

HTML5 视频 Vedio 标签详解

08-06

7大常用Scrum管理工具软件

08-06

猜你喜欢

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论