WebSocket快速入门

news2024/11/20 10:33:38

WebSocket

借鉴:

https://blog.csdn.net/weixin_45747080/article/details/117477006

https://cloud.tencent.com/developer/article/1887095

简介

WebSocket 是一种网络传输协议,可在单个 TCP 连接上进行全双工通信,位于 OSI 模型的应用层。WebSocket 协议在 2011 年由 IETF 标准化为 RFC 6455,后由 RFC 7936 补充规范。

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。

优点:

  • *1)*较少的控制开销:在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小;
  • *2)*更强的实时性:由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于 HTTP 请求需要等待客户端发起请求服务端才能响应,延迟明显更少;
  • *3)*保持连接状态:与 HTTP 不同的是,WebSocket 需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息;
  • *4)*更好的二进制支持:WebSocket 定义了二进制帧,相对 HTTP,可以更轻松地处理二进制内容;
  • *5)*可以支持扩展:WebSocket 定义了扩展,用户可以扩展协议、实现部分自定义的子协议。

前后端使用WebSocket

img

服务端利用SpringBoot启动一个WebSocket服务,同时暴露出该服务的应用路径,客户端则利用该应用路径进行连接。需要注意的是,在服务端只需要启动一个WebSocket服务,而每一个客户端就是一个WebSocket应用。就有点像:服务端是古老的电话接线员,而客户端就是打电话的人。假如用户A想要给用户B打电话就需要先打电话到接线员那,然后接线员再接通用户B。不过WebSocket可以实现广播和私聊。

前端初始化WebSocket对象

不需要引入第三方依赖包,直接使用js自带的WebSocket对象。

  1. 创建WebSocket对象

    const ws = new WebSocket('ws://localhost:8000/websocket/')
    

    ws://jdbc://http://一样都是协议名,同样的,Websocket还支持更加安全的wss:///websocket即该服务的应用路径名

  2. onopen事件监听

    与服务端连接成功会触发。

    webSocketOnOpen(e){
        console.log('与服务端连接打开->',e)
    },
    
  3. onerror事件监听

    与服务端连接异常时触发。

    webSocketOnError(e){
        console.log('与服务端连接异常->',e)
    },
    
  4. onclose事件监听

    与服务端连接关闭时触发。

    webSocketOnClose(e){
        console.log('与服务端连接关闭->',e)
    },
    
  5. onmessage事件监听

    接收到来自服务端的消息时触发。

    webSocketOnMessage(e){
        console.log('来自服务端的消息->',e)
    },
    

一个完整的WebSocket对象应该具备以上属性,同时需要将以上属性跟WebSocket对象绑定。

使用原生JS初始化WebSocket对象演示

const ws = new WebSocket(webSocketUrl)
//onopen事件监听
ws.addEventListener('open',e=>{
    console.log('与服务端连接打开->',e)
},false)
//onclose事件监听
ws.addEventListener('close',e=>{
    console.log('与服务端连接关闭->',e)
},false)
//onmessage事件监听
ws.addEventListener('message',e=>{
    console.log('来自服务端的消息->',e)
},false)
//onerror事件监听
ws.addEventListener('error',e=>{
    console.log('与服务端连接异常->',e)
},false)

ws对象的addEventListener( )方法,为WebSocket绑定事件监听,从而在各个事件监听中处理事务。

使用Vue初始化WebSocket对象演示

export default {
  name: "Home",
  data() {
    return {
      webSocketObject: null,
    }
  },
  created() {
    //初始化WebSocket
    this.webSocketInit()
  },
  methods: {
    webSocketInit(){
      const webSocketUrl = 'ws://localhost:8000/websocket/'+this.username
      this.webSocketObject = new WebSocket(webSocketUrl);
      this.webSocketObject.onopen = this.webSocketOnOpen
      this.webSocketObject.onmessage = this.webSocketOnMessage
      this.webSocketObject.onerror = this.webSocketOnError
      this.webSocketObject.onclose = this.webSocketOnClose
    },
    webSocketOnOpen(e){
      console.log('与服务端连接打开->',e)
    },
    webSocketOnMessage(e){
      console.log('来自服务端的消息->',e)
    },
    webSocketOnError(e){
      console.log('与服务端连接异常->',e)
    },
    webSocketOnClose(e){
      console.log('与服务端连接关闭->',e)
    },
  },
}
</script>

同样的,利用methods分别定义好OnOpen、OnMessage、OnError、OnClose四个事件监听,然后进行初始化并且绑定就可以了。这样就完成了WebSocket对象以及事件监听的初始化。

后端初始化WebSocket对象

  • SpringBoot自带的WebSocket有以下5个常用注解:

@ServerEndpoint

暴露出的ws应用的路径,支持RESTful风格传参,类似/websocket/{username}

@OnOpen

与当前客户端连接成功,有入参Session对象(当前连接对象),同时可以利用@PathParam()获取上述应用路径中传递的参数,比如@PathParam(“username”) String username。

@OnClose

与当前客户端连接失败,有入参Session对象(当前连接对象),同时也可以利用@PathParam()获取上述应用路径中传递的参数。

@OnError

与当前客户端连接异常,有入参Session对象(当前连接对象)、Throwable对象(异常对象),同时也可以利用@PathParam()获取上述应用路径中传递的参数。

@OnMessage

当前客户端发送消息,有入参Session对象(当前连接对象)、String message对象(当前客户端传递过来的字符串消息)

  • 利用SpringBoot创建项目,需要引入依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
  • 在application.yaml中定义好该服务的端口号:
server:
  port: 8000
  • 利用自定义配置类开启WebSocket:
package cn.wqk.serverwebsocket.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
@EnableWebSocket
public class WebSocketConfig {

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

}
  • 定义Websocket主业务类:
package cn.wqk.serverwebsocket.socket;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;

@Component
@Slf4j
@ServerEndpoint("/websocket/{username}") //暴露的ws应用的路径
public class WebSocket {

    /**
     * 客户端与服务端连接成功
     * @param session
     * @param username
     */
    @OnOpen
    public void onOpen(Session session,@PathParam("username") String username){
        /*
            do something for onOpen
            与当前客户端连接成功时
         */
    }

    /**
     * 客户端与服务端连接关闭
     * @param session
     * @param username
     */
    @OnClose
    public void onClose(Session session,@PathParam("username") String username){
        /*
            do something for onClose
            与当前客户端连接关闭时
         */
    }

    /**
     * 客户端与服务端连接异常
     * @param error
     * @param session
     * @param username
     */
    @OnError
    public void onError(Throwable error,Session session,@PathParam("username") String username) {
    }

    /**
     * 客户端向服务端发送消息
     * @param message
     * @param username
     * @throws IOException
     */
    @OnMessage
    public void onMsg(Session session,String message,@PathParam("username") String username) throws IOException {
        /*
            do something for onMessage
            收到来自当前客户端的消息时
         */
    }
}

前后端联动实现简单聊天室

前端在OnMessage事件监听中接收到来自后端的消息,然后进行处理(展示在页面上);同样的,后端也是在OnMessage中接收到来自前端的消息,然后进行处理(发送到所有客户端)。

前端

  1. 定义一个输入框,再定义一个按钮:接收消息并且发送
<input
       type="text"
       v-model="sendMessage"
       placeholder="请输入你要发送的消息">
<button @click="handleSendButton()">发送</button>

**注意:**直接利用websocket对象的send()方法发送消息,前后端数据传输利用JSON字符串,所以发送的时候需要将对象转为JSON字符串。

  1. 定义一个列表:用于展示聊天信息
<table>
    <thead>
        <tr>
            <th>消息编号</th>
            <th>发送者</th>
            <th>发送时间</th>
            <th>发送内容</th>
        </tr>
    </thead>
    <tbody>
        <tr v-for="item in messageList" :key="item.time">
            <td>{{ item.id }}</td>
            <td>{{ item.username }}</td>
            <td>{{ new Date(item.time).toLocaleTimeString() }}</td>
            <td>{{ item.message }}</td>
        </tr>
    </tbody>
</table>
  1. 当客户端的onMessage接收到消息后就把消息展示到列表中
webSocketOnMessage(e){
    console.log('来自服务端的消息->',e)
    const receiveMessage = JSON.parse(e.data);
    this.messageList.push(receiveMessage)
},

**注意:**通过console.log(e)不难发现,来自服务端的消息是存储在e.data中的,并且是JSON字符串,所以我们需要将它转为JSON对象。

此时已经完成了前端发送消息并且接收消息且展示消息了。

后端

接收消息并且群发消息:

@OnMessage
public void onMsg(Session session,String message,@PathParam("username") String username) throws IOException {
    /*
            do something for onMessage
            收到来自当前客户端的消息时
         */
    sendAllMessage(message);
}
//向所有客户端发送消息(广播)
private void sendAllMessage(String message){
    Set<String> sessionIdSet = onlineClientMap.keySet(); //获得Map的Key的集合
    for (String sessionId : sessionIdSet) { //迭代Key集合
        Session session = onlineClientMap.get(sessionId); //根据Key得到value
        session.getAsyncRemote().sendText(message); //发送消息给客户端
    }
}

前端完整代码

<template>
  <div>
    <table>
      <thead>
      <tr>
        <th>消息编号</th>
        <th>发送者</th>
        <th>发送时间</th>
        <th>发送内容</th>
      </tr>
      </thead>
      <tbody>
      <tr v-for="item in messageList" :key="item.time">
        <td>{{ item.id }}</td>
        <td>{{ item.username }}</td>
        <td>{{ new Date(item.time).toLocaleTimeString() }}</td>
        <td>{{ item.message }}</td>
      </tr>
      </tbody>
    </table>
    <input
        type="text"
        v-model="sendMessage"
        placeholder="请输入你要发送的消息">
    <button @click="handleSendButton()">发送</button>
    <button @click="handleLogoutButton()">退出</button>
  </div>
</template>

<script>

import {
  getUsername,
  removeUsername
} from "@/utils/username";

export default {
  name: "Home",
  data() {
    return {
      webSocketObject: null,
      username: '',
      messageList: [

      ],
      sendMessage: ''
    }
  },
  created() {
    //从localStorage中获得username
    this.username = getUsername()
    //如果username不存在返回到登录页面
    if (!this.username){
      this.$router.push({
        name: 'Login'
      })
    }
    //初始化WebSocket
    this.webSocketInit()
  },
  beforeDestroy() {
    this.webSocketObject.close();//在该组件销毁时关闭该连接以节约资源
  },
  methods: {
    webSocketInit(){
      const webSocketUrl = 'ws://localhost:8000/websocket/'+this.username
      this.webSocketObject = new WebSocket(webSocketUrl);
      this.webSocketObject.onopen = this.webSocketOnOpen
      this.webSocketObject.onmessage = this.webSocketOnMessage
      this.webSocketObject.onerror = this.webSocketOnError
      this.webSocketObject.onclose = this.webSocketOnClose
    },
    webSocketOnOpen(e){
      console.log('与服务端连接打开->',e)
    },
    webSocketOnMessage(e){
      console.log('来自服务端的消息->',e)
      const receiveMessage = JSON.parse(e.data);
      this.messageList.push(receiveMessage)
    },
    webSocketOnError(e){
      console.log('与服务端连接异常->',e)
    },
    webSocketOnClose(e){
      console.log('与服务端连接关闭->',e)
    },
    handleSendButton() {
      const username = this.username
      const message = this.sendMessage
      this.webSocketObject.send(JSON.stringify({
        id: 1,
        message,
        username,
        time: new Date().getTime()
      }))
      this.sendMessage = ''
    },
    handleLogoutButton(){
      removeUsername() //清除username然后断开连接
      this.webSocketObject.close();
      this.$router.push({
        name: 'Login'
      })
    }
  },
}
</script>

这里采用的是在上一个页面获取到用户的用户名然后存储到LocalStorage中。

后端完整代码

package cn.wqk.serverwebsocket.socket;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;

@Component
@Slf4j
@ServerEndpoint("/websocket/{username}") //暴露的ws应用的路径
public class WebSocket {

    /** 当前在线客户端数量(线程安全的) */
    private static AtomicInteger onlineClientNumber = new AtomicInteger(0);

    /** 当前在线客户端集合(线程安全的):以键值对方式存储,key是连接的编号,value是连接的对象 */
    private static Map<String ,Session> onlineClientMap = new ConcurrentHashMap<>();

    /**
     * 客户端与服务端连接成功
     * @param session
     * @param username
     */
    @OnOpen
    public void onOpen(Session session,@PathParam("username") String username){
        /*
            do something for onOpen
            与当前客户端连接成功时
         */
        onlineClientNumber.incrementAndGet();//在线数+1
        onlineClientMap.put(session.getId(),session);//添加当前连接的session
        log.info("时间[{}]:与用户[{}]的连接成功,当前连接编号[{}],当前连接总数[{}]",
                new Date().toLocaleString(),
                username,
                session.getId(),
                onlineClientNumber);
    }

    /**
     * 客户端与服务端连接关闭
     * @param session
     * @param username
     */
    @OnClose
    public void onClose(Session session,@PathParam("username") String username){
        /*
            do something for onClose
            与当前客户端连接关闭时
         */
        onlineClientNumber.decrementAndGet();//在线数-1
        onlineClientMap.remove(session.getId());//移除当前连接的session
        log.info("时间[{}]:与用户[{}]的连接关闭,当前连接编号[{}],当前连接总数[{}]",
                new Date().toLocaleString(),
                username,
                session.getId(),
                onlineClientNumber);
    }

    /**
     * 客户端与服务端连接异常
     * @param error
     * @param session
     * @param username
     */
    @OnError
    public void onError(Throwable error,Session session,@PathParam("username") String username) {
        /*
            do something for onError
            与当前客户端连接异常时
         */
        error.printStackTrace();
    }

    /**
     * 客户端向服务端发送消息
     * @param message
     * @param username
     * @throws IOException
     */
    @OnMessage
    public void onMsg(Session session,String message,@PathParam("username") String username) throws IOException {
        /*
            do something for onMessage
            收到来自当前客户端的消息时
         */
        log.info("时间[{}]:来自连接编号为[{}]的消息:[{}]",
                new Date().toLocaleString(),
                session.getId(),
                message);
        sendAllMessage(message);
    }

    //向所有客户端发送消息(广播)
    private void sendAllMessage(String message){
        Set<String> sessionIdSet = onlineClientMap.keySet(); //获得Map的Key的集合
        for (String sessionId : sessionIdSet) { //迭代Key集合
            Session session = onlineClientMap.get(sessionId); //根据Key得到value
            session.getAsyncRemote().sendText(message); //发送消息给客户端
        }
    }

}

流程图

img

总结

服务端启动一个WebSocket服务,初始化应用路径、连接打开OnOpen、连接关闭OnClose、连接异常OnError、收到消息OnMessage。OnMessage中需要对来自客户端的消息进行对应的处理,比如广播或者私聊给具体某人。

客户端需要利用WebSocket对象(定义好url),然后初始化OnOpen、OnClose、OnError、OnMessage。OnMessage中需要对来自服务端的消息进行处理,如展示到页面上等。同时还可以需用WebSocket对象的send()方法来给服务端发送数据,并且切记在页面关闭时需要将该连接关闭(利用WebSocket对象的close()方法)。

项目地址

博客作者给的github项目可以直接跑通,良心:https://github.com/FanGaoXS/websocket-demo

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1263594.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第七节HarmonyOS UIAbility生命周期以及启动模式

一、UIAbility生命周期 为了实现多设备形态上的裁剪和多窗口的可扩展性&#xff0c;系统对组件管理和窗口管理进行了解耦。UIAbility的生命周期包括Create、Foreground、Background、Destroy四个状态&#xff0c;WindowStageCreate和WindowStageDestroy为窗口管理器&#xff08…

27.0/多态/对象向上转型/向下转型/抽象类/抽象方法。

目录 27.1为什么使用多态? 27.1.2什么是多态 27.1.3对象多态 27.1.4多态的使用前提 27.2 向上转型 27.3向下转型 (面试题) 27.4抽象类和抽象方法 特点(面试题): 27.1为什么使用多态? 需求1&#xff1a;动物园让我们实现一个功能&#xff1a; 创建一个狗类 &#xff0c;狗…

【AICFD案例教程】PCB多变量AI预测分析

AICFD是由天洑软件自主研发的通用智能热流体仿真软件&#xff0c;用于高效解决能源动力、船舶海洋、电子设备和车辆运载等领域复杂的流动和传热问题。软件涵盖了从建模、仿真到结果处理完整仿真分析流程&#xff0c;帮助工业企业建立设计、仿真和优化相结合的一体化流程&#x…

qt实现一个安卓测试小工具

qt实现一个安卓测试小工具 最终效果&#xff1a;目录结构源码gui.py 主要是按钮&#xff0c;文本控制代码main.py 主要是逻辑代码gui.spec 是打包使用的adb.ui 最终效果&#xff1a; 目录结构 上面2个是打包的生成的不用管 源码 gui.py 主要是按钮&#xff0c;文本控制代码…

微信小程序开发——开发账号注册与配置

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 概述 本文的重点在于介绍注册微信小程序开发账号的步骤及其流程。 账号注册 请点击官方网站右上角的 https://mp.weixin.qq.com/ 立即注册&#xff0c;图示如下&#xf…

Python 和 Node.js 之间通信 JSON 数据

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在实际应用中&#xff0c;不同编程语言之间的通信是常见的需求。Python和Node.js是两个流行且功能强大的编程语言&#xff0c;它们之间使用JSON格式进行数据交换是一种高效和灵活的方式。本文将详细介绍如何在Py…

unity3d NPC自动寻路不移动

烘焙的路面不能有间隔&#xff0c;调整地面重新烘焙

基于mvc电影院售票预订选座系统php+vue+elementui

本影院售票系统主要包括二大功能模块&#xff0c;管理员功能模块和用户功能模块。 &#xff08;1&#xff09;管理员模块&#xff1a;系统中的核心用户管理员登录后&#xff0c;通过管理员功能来管理后台系统。主要功能有&#xff1a;首页、个人中心、电影类型管理、场次时间管…

还在担心发抖音没素材跟文案?[腾讯云HAI] AIGC带你蹭热度“今年你失去了什么?”

目录 &#x1f433;前言&#xff1a; &#x1f680;了解高性能应用服务 HAI &#x1f47b;即插即用 轻松上手 &#x1f47b;横向对比 青出于蓝 &#x1f424;应用场景-AI作画 &#x1f424;应用场景-AI对话 &#x1f424;应用场景-算法研发 &#x1f680;使用HAI进行…

人工智能技术发展漫谈

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 人工智能发展历程 人工智能&#xff08;Artificial Intelligence&#xff0c;简称AI&#xff09;的发展历史可以追溯到20世纪中叶。以下是一些关键时刻和阶段&#xff1a; 起…

基于Spring原生框架构建原生Spring的第一个程序!

&#x1f609;&#x1f609; 学习交流群&#xff1a; ✅✅1&#xff1a;这是孙哥suns给大家的福利&#xff01; ✨✨2&#xff1a;我们免费分享Netty、Dubbo、k8s、Mybatis、Spring...应用和源码级别的视频资料 &#x1f96d;&#x1f96d;3&#xff1a;QQ群&#xff1a;583783…

Linux shell编程学习笔记31:alias 和 unalias 操作 命令别名

目录 0 前言1 定义别名2 查看别名 2.1 查看所有别名2.2 查看某个别名 2.2.1 alias 别名2.2.2 alias | grep 别名字符串2.2.3 使用 CtrlAltE 组合键3 unalias&#xff1a;删除别名4 如何执行命令本身而非别名 4.1 方法1&#xff1a;使用 CtrlAltE 组合键 && unalias4…

Appium自动化测试:sdk版本和手机版本不兼容你就这样做,分分钟解决问题

使用pythonappium执行自动化测试脚本提示报错 环境&#xff1a;python3.7以上&#xff0c;appium1.21.0&#xff1b;设备&#xff1a;华为max8真机 报错信息&#xff1a; android.intent.category.LAUNCHER -f 0x10200000 exited with code 4294967295; Stderr: Security ex…

【Python】python天气数据抓取与数据分析(源码+论文)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

MySQL企业版之Firewall(SQL防火墙)

​​​1. 关于Firewall插件 2. Firewall插件的工作方式 3. Firewall插件测试 4. 总结延伸阅读 1. 关于Firewall插件 Friewall是MySQL企业版非常不错的功能插件之一,启用Firewall功能后,SQL的执行流程见下图示意: 2. Firewall插件的工作方式 Firewall插件的工作机制大概是…

算法基础二

回文数 给你一个整数 x &#xff0c;如果 x 是一个回文整数&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。回文数是指正序&#xff08;从左向右&#xff09;和倒序&#xff08;从右向左&#xff09;读都是一样的整数。 示例 1&#xff1a; 输入&#xff1…

振南技术干货集:znFAT 硬刚日本的 FATFS 历险记(8)

注解目录 1、znFAT 的起源 1.1 源于论坛 &#xff08;那是一个论坛文化兴盛的年代。网友 DIY SDMP3 播放器激起了我的兴趣。&#xff09; 1.2 硬盘 MP3 推了我一把 &#xff08;“坤哥”的硬盘 MP3 播放器&#xff0c;让我深陷 FAT 文件系统不能自拔。&#xff09; 1.3 我…

智慧化工~工厂设备检修和保全信息化智能化机制流程

化工厂每年需要现场检修很多机器&#xff0c;比如泵、压缩机、管道、塔等等&#xff0c;现场检查人员都是使用照相机&#xff0c;现场拍完很多机器后&#xff0c;回办公室整理乱糟糟的照片&#xff0c;但是经常照了之后无法分辨是哪台设备&#xff0c;而且现场经常漏拍&#xf…

ROC及曲线面积汇总学习

目录 ROC基础 生成模拟数据 率的计算 R语言计算测试 ROCR&#xff1a; pROC ROC绘制 单个ROC 两个ROC Logistic回归的ROC曲线 timeROC ROC基础 ROC曲线的横坐标是假阳性率&#xff0c;纵坐标是真阳性率&#xff0c;需要的结果是这个率表示疾病阳性的率&#xff08;…

第六节HarmonyOS UIAbility内页面的跳转和数据传递

一、页面跳转 在一个应用包含一个UIAbility的场景下&#xff0c;可以通过新建多个页面来实现和丰富应用的内容。这会涉及到UIAbility内页面的新建以及UIAbility内页面的跳转和数据传递。 打开DevEco Studio&#xff0c;选择一个Empty Ability工程模板&#xff0c;创建一个工程&…