千家信息网

如何用单线程和定时任务分别实现WebSocket聊天室

发表于:2024-11-22 作者:千家信息网编辑
千家信息网最后更新 2024年11月22日,这篇文章主要讲解了"如何用单线程和定时任务分别实现WebSocket聊天室",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"如何用单线程和定时任务分别实现
千家信息网最后更新 2024年11月22日如何用单线程和定时任务分别实现WebSocket聊天室

这篇文章主要讲解了"如何用单线程和定时任务分别实现WebSocket聊天室",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"如何用单线程和定时任务分别实现WebSocket聊天室"吧!

1.需求场景

  • 多媒体实时聊天

  • 股票基金等数据报价

  • 位置定位

  • 社交订阅

  • 数据库更新,前端实时显示

要实现这种实时性较强的功能,以前经常采用的方法:轮询Comet技术

  • 轮询:要求客户端已设定的时间间隔周期性地向服务器端发送请求,频繁查询数据是否变动。缺点:过多不必要的请求,浪费流量和服务器资源。

  • Comet技术:可以分为长轮询和流技术。长轮询改进上述轮询,减少无用的请求,设定过期时间,当数据过期后才会向服务器端发送请求,适合数据改动不频繁的场景;流技术是指客户端通过一个隐藏的窗口与服务器建立一个HTTP长连接,不断更新连接状态保持连接不断开。

总结:都是基于请求-应答模式,不算真正意义上的实时技术,每一次请求应答,都要消耗一定流量。

2.WebSocket原理

WebSocket协议基于TCP协议实现,工作流程是这 样的:浏览器通过JavaScript向服务端发出建立WebSocket连接的请求,在WebSocket连接建立成功后,客户端和服务端就可以通过 TCP连接传输数据。因为WebSocket连接本质上是TCP连接,不需要每次传输都带上重复的头部数据, 其优点:

  • 通过第一次Http Request第一次建立连接之后,之后的数据交换都不要再重新发送Http Request,节省了宽带资源

  • WebSocket协议是双向通信协议,既可以发送又可以接受

  • 多路复用即多个不同的URL可以复用同一个Websocket连接

3.打造Websocket聊天室

温馨提示:基于IAEA+SpringBoot+Gradle开发,得益于SpringBoot提供的自动配置,只需要通过简单注解@ServerEndpoint就能创建WebSocket服务端,再通过简单的回调函数就能完成WebSocket服务端的编写!
  • build.gadle

        //spring-boot-starter-websocket的依赖springboot的高级组件会自动引用基础的组件,        // 像spring-boot-starter-websocket就引入了spring-boot-starter-web和spring-boot-starter        compile group: 'org.springframework.boot', name: 'spring-boot-starter-websocket', version: '2.1.6.RELEASE'       //thymeleaf    compile group: 'org.springframework.boot', name: 'spring-boot-starter-thymeleaf', version: '2.1.6.RELEASE'
  • 创建一个WebSocketConfig

package com.example.SmartHome.config;import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.stereotype.Component;import org.springframework.web.socket.server.standard.ServerEndpointExporter;/* *@Description:  自动注册使用了@ServerEndpoint注解声明的Websocket endpoint *@ClassName: WebSocketConfig *@Author: zzq *@Date: 2019/7/7 11:01 *@Version: 1.0 */@Configuration@Component@ConditionalOnWebApplicationpublic class WebSocketConfig {    /**     * 自动注册使用了@ServerEndpoint注解声明的Websocket endpoint     */    @Bean    public ServerEndpointExporter serverEndpointExporter() {        return new ServerEndpointExporter();    }}
提醒:ServerEndpointExporter 是由Spring官方提供的标准实现,用于扫描ServerEndpointConfig配置类和@ServerEndpoint注解实例。使用规则:1.如果使用默认的嵌入式容器 比如Tomcat 则必须手工在上下文提供ServerEndpointExporter。2. 如果使用外部容器部署war包,则不要提供提供ServerEndpointExporter,因为此时SpringBoot默认将扫描服务端的行为交给外部容器处理。
  • 创建WebSocket服务器

    核心思路:① 通过注解@ServerEndpoint来声明实例化WebSocket服务端。② 通过注解@OnOpen、@OnMessage、@OnClose、@OnError 来声明回调函数。
    事件类型注解事件描述
    open@OnOpen当打开连接后触发
    message@OnMessage当接受客户端消息时触发
    error@OnError当通信异常时触发
    close@OnClose当连接关闭时触发
    package com.example.SmartHome.server;import org.springframework.stereotype.Component;import javax.websocket.*;import javax.websocket.server.ServerEndpoint;import java.io.IOException;import java.util.concurrent.CopyOnWriteArraySet;/* *@Description: WebSocketServer服务器端 *@ClassName: WebSocketServer *@Author: zzq *@Date: 2019/7/3 17:05 *@Version: 1.0 *///ServerEndpoint这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint。// 要注意,如果使用独立的servlet容器,而不是直接使用springboot的内置容器,// 就不要注入ServerEndpointExporter,// 因为它将由容器自己提供和管理。/** * @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端, * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端 */@ServerEndpoint("/websocket")@Component  //它的主要作用就是将这个监听器纳入到Spring容器中进行管理public class WebSocket {//    MyThread thread1=new MyThread();//    Thread  thread  =new Thread(thread1);    //每个客户端都会有相应的session,服务端可以发送相关消息    private Session session;    public static int onlineCount = 0;    //J.U.C包下线程安全的类,主要用来存放每个客户端对应的webSocket连接    private static CopyOnWriteArraySet copyOnWriteArraySet = new CopyOnWriteArraySet();    /**     * @Name:onOpen     * @Description:打开连接。进入页面后会自动发请求到此进行连接     */    @OnOpen    public void onOpen(Session session) throws IOException {        this.session = session;        copyOnWriteArraySet.add(this);        addOnlineCount();        System.out.println("websocket有新的连接, 总数:" + getOnlineCount());        sendMessage("成功连接");    }    /**     * @Name:onClose     * @Description:用户关闭页面,即关闭连接     */    @OnClose    public void onClose() {        copyOnWriteArraySet.remove(this);        shortOnlineCount();        System.out.println("websocket连接断开, 总数:" + getOnlineCount());    }    /**     * @Name:onMessage     * @Description:收到客户端消息后调用的方法     */    @OnMessage    public void onMessage(String message,Session session) throws IOException {        System.out.println("websocket收到客户端发来的消息:" + message);        for(WebSocket webSocket:copyOnWriteArraySet){            webSocket.sendMessage(message);        }    }    /**     * @Name:onError     * @Description:出现错误     */    @OnError    public void onError(Session session, Throwable error) {        System.out.println("发生错误:" + error.getMessage() + "; sessionId:" + session.getId());        error.printStackTrace();    }    public void sendMessage(String message) throws IOException {        this.session.getBasicRemote().sendText(message);        // this.session.getAsyncRemote().sendText(message);    }    public void sendMessage(Object object) {        //遍历客户端        for (WebSocket webSocket : copyOnWriteArraySet) {            System.out.println("websocket广播消息:" + object.toString());            try {                //服务器主动推送                webSocket.session.getBasicRemote().sendObject(object);            } catch (Exception e) {                e.printStackTrace();            }        }    }    /**     * @Name:sendMessage     * @Description:用于发送给客户端消息(群发)     */    public static void sendInfo(String message) {        //遍历客户端        for (WebSocket webSocket : copyOnWriteArraySet) {            System.out.println("websocket广播消息:" + message);            try {                //服务器主动推送                webSocket.session.getBasicRemote().sendText(message);            } catch (Exception e) {                e.printStackTrace();            }        }    }    /**     * @Name:sendMessage     * @Description:用于发送给指定客户端消息     */    public void sendMessage(String sessionId, String message) throws IOException {        Session session = null;        WebSocket tempWebSocket = null;        for (WebSocket webSocket : copyOnWriteArraySet) {            if (webSocket.session.getId().equals(sessionId)) {                tempWebSocket = webSocket;                session = webSocket.session;                break;            }        }        if (session != null) {            tempWebSocket.session.getBasicRemote().sendText(message);        } else {            System.out.println("没有找到你指定ID的会话:{}" + "; sessionId:" + sessionId);        }    }    public static  synchronized int getOnlineCount(){        return onlineCount;    }    public static synchronized void addOnlineCount(){        WebSocket.onlineCount++;    }    public static  synchronized void shortOnlineCount(){        WebSocket.onlineCount--;    }}


    4.Controller类

package com.example.SmartHome.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;/* *@Description: TODO *@ClassName: ChatController *@Author: zzq *@Date: 2019/7/9 16:56 *@Version: 1.0 */@Controllerpublic class ChatController {    @RequestMapping("/websocket")    public String init() {        return "websocket.html";    }}

5.前端代码

      My WebSocket Test Welcome

6.结果展示

服务端:

感谢各位的阅读,以上就是"如何用单线程和定时任务分别实现WebSocket聊天室"的内容了,经过本文的学习后,相信大家对如何用单线程和定时任务分别实现WebSocket聊天室这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

0