Websocket解析及用法(封装一个通用订阅发布主题的webSocket类)

1、什么是WebSocket?

websocket的目标是通过一个长连接实现与服务器全双工,双向的通信是一种在单个TCP连接上进行全双工通信的协议,使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 js中创建websocket时,一个http请求会发送到服务器初始化连接,服务器响应后,连接使用http的upgrade头部从http协议切换到websocket协议,这意味着websocket不能通过标准的http服务器完成,而必须使用支持websocket协议的专有服务器。因为websocket使用了自定义协议,所以url方案稍有变化,不能再使用http://或https://,而要使用我是:ws://和wss://。前者是不安全的连接,后者是安全的。

优点缺点
基于TCP协议: WebSocket建立在TCP之上,这使得服务器端的实现相对容易。安全性:WebSocket 使用的是持久性连接,连接建立后会长时间保持打开状态。会增加服务器资源的消耗,WebSocket 协议的双向通信机制,它可能会面临安全隐患,例如 XSS 和 CSRF 攻击等。
与HTTP兼容性良好: WebSocket与HTTP协议兼容,使用HTTP协议进行握手阶段,因此默认端口与HTTP相同(80和443),且不易被屏蔽。这意味着它可以通过各种HTTP代理服务器,增加了通信的灵活性。浏览器兼容性:旧版浏览器中可能会出现兼容性问题。
轻量级数据格式和高效通信: 在连接创建后,持久保存连接状态,并且交换数据时,用于协议控制的数据包头部相对较小数据包大小的限制: WebSocket 协议发送的数据包不能超过 2GB。
支持文本和二进制数据:WebSocket不仅可以发送文本数据,还可以发送二进制数据,相对 HTTP,可以更轻松地处理二进制内容。服务器需求: WebSocket 协议需要服务器支持,这意味着需要更高效的服务器硬件和软件。
无同源限制:与传统的AJAX请求不同,WebSocket没有同源限制,客户端可以与任意服务器通信,不需要处理跨域

2、怎么理解WebSocket,可以用在什么场景?

首先,我们思考一个问题,当前端需要展示实时数据,或实现实时聊天功能,我们怎么设计数据请求方案?
按照传统的解决方法就是利用轮询和长轮询的方案,现在我们来对比一下这三种方案的异同:

轮询长轮询websocket
方案思路客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接在这里插入图片描述客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求在这里插入图片描述客户端向服务器发起websocket请求建立全双工通信连接,一旦服务器有数据更新,服务器可以主动给客户端发送数据,无需客户端询问在这里插入图片描述
优缺点对比后端程序编写比较容易,但是请求中有大半是无用,浪费带宽和服务器资源在无消息的情况下不会频繁的请求,但是服务器hold连接会消耗资源轻量级数据格式和高效通信,支持文本和二进制数据,无同源限制,但是旧版浏览器中可能会出现兼容性问题, WebSocket 协议发送的数据包不能超过 2GB

3、WebSocket对象详解

3-1、websocket()
构造函器会返回一个 WebSocket 对象。
var aWebSocket = new WebSocket(url [, protocols]);

参数名称是否必填释义
url要连接的 URL;这应该是 WebSocket 服务器将响应的 URL。
protocol一个人类可读的字符串,它解释了连接关闭的原因。这个 UTF-8 编码的字符串不能超过 123 个字节。

3-2、api
WebSocket.close() 关闭 WebSocket 连接或连接尝试(如果有的话)。如果连接已经关闭,则此方法不执行任何操作;

参数名称是否必填释义
code一个数字状态码,它解释了连接关闭的原因。如果没有传这个参数,默认使用 1005。
reason一个协议字符串或者一个包含协议字符串的数组。这些字符串用于指定子协议,这样单个服务器可以实现多个 WebSocket 子协议(例如,你可能希望一台服务器能够根据指定的协议(protocol)处理不同类型的交互)。如果不指定协议字符串,则假定为空字符串。

WebSocket.send() 将需要通过 WebSocket 链接传输至服务器的数据排入队列,并根据所需要传输的 data bytes 的大小来增加 bufferedAmount的值。若数据无法传输(例如数据需要缓存而缓冲区已满)时,套接字会自行关闭。

参数名称是否必填释义
data用于传输至服务器的数据。

3-3、属性
WebSocket.binaryType:返回 websocket 连接所传输二进制数据的类型。

WebSocket.bufferedAmount:一个只读属性,用于返回已经被send()方法放入队列中但还没有被发送到网络中的数据的字节数。一旦队列中的所有数据被发送至网络,则该属性值将被重置为 0。但是,若在发送过程中连接被关闭,则属性值不会重置为 0。如果你不断地调用send(),则该属性值会持续增长。

WebSocket.extensions:是只读属性,返回服务器已选择的扩展值。目前,链接可以协定的扩展值只有空字符串或者一个扩展列表。

WebSocket.protocol 是个只读属性,用于返回服务器端选中的子协议的名字;这是一个在创建 WebSocket 对象时,在参数 protocols 中指定的字符串,当没有已建立的链接时为空串。

WebSocket.readyState返回当前 WebSocket 的链接状态,只读。

释义
0正在链接中
1已经链接并且可以通讯
2连接正在关闭
3连接已关闭或者没有链接成功

WebSocket.url是一个只读属性,返回值为当构造函数创建WebSocket实例对象时 URL 的绝对路径。

3-4、事件

WebSocket.onclose:属性返回一个事件监听器,这个事件监听器将在 WebSocket 连接的readyState 变为 3时被调用,它接收一个名字为“close”的 CloseEvent 事件。

WebSocket.onerror:当websocket的连接由于一些错误事件的发生 (例如无法发送一些数据) 而被关闭时,一个error事件将被引发。

WebSocket.onmessage:message 事件会在 WebSocket 接收到新消息时被触发。

WebSocket.onopen:属性定义一个事件处理程序,当WebSocket 的连接状态readyState 变为1时调用;这意味着当前连接已经准备好发送和接受数据。

4、怎么保持WebSocket长时间连接,不断线?

首先我们来探讨一下为什么websocket断线的原因有哪些,由于websocket连接是基于一个tcp连接上的长时间全双工双向通信,所以断线的原因有下几种可能(个人猜测):
1、网络连接:网络连接不稳定或出现故障,可能会导致WebSocket断线;
2、服务器:WebSocket服务器出现故障或过载,可能会导致WebSocket断线;
3、防火墙问题:防火墙可能会阻止WebSocketClient与服务器之间的通信,导致断线。
4、超时问题:WebSocket在一段时间内没有收到服务器发送的数据,则可能会断开连接。
5、客户端异常中断。

如何解决断线问题
1、心跳检测机制:通过定期发送心跳消息,可以确保客户端和服务器之间的连接处于活跃状态。如果一段时间内未收到来自客户端的心跳消息,服务器可以认为客户端已经断线,并采取相应的措施,例如关闭连接或重新建立连接。
2、连接状态管理:在客户端代码中维护连接的状态信息,以便及时检测连接的断开和重新连接的状态变化。这样可以使应用程序更容易地处理连接断开和重新连接时的逻辑。
3、异常处理 :及时捕获和处理在连接过程中可能出现的异常情况,例如网络超时、连接被拒绝等,以提高系统的稳定性和可靠性。

心跳检测机制:
之所以称之为心跳检测是因为:它像心跳一样每隔固定时间发一次,以此来告诉服务器,这个客户端还活着。实际上,这种机制是为了维持长连接而设计的。通常情况下,心跳包的内容并没有特别的规定,通常是一个很小的数据包,甚至可能只包含包头的空包。

心跳检测步骤:
1、客户端会定期发送一个探测包(心跳包)给服务器,以确保连接的活跃性。
2、在发送探测包的同时,客户端会启动一个超时定时器,以便在规定的时间内等待服务器的响应。
3、当服务器接收到客户端发送的探测包时,会立即回应一个确认包,以表明服务器正常接收到了探测包。
4、如果客户端收到服务器的应答包,则说明服务器正常:客户端在收到服务器的确认包后,会立即删除之前启动的超时定时器,表明服务器正常运行。
5、如果客户端的超时定时器超时,仍未收到应答包,则说明服务器挂了:如果客户端的超时定时器到期时仍未收到服务器的确认包,则客户端会认为服务器已经挂了,进而采取相应的措施,例如重新连接或者进行错误处理。

5、封装一个通用的推送、订阅主题的webSocket的类

// 先定义一个基础webSocket类
class WebsocketClient {
  // 连接的URL
  _host = "";
  // websocket对象
  ws = null;
  // 传入的websocket的方法函数,事件回调函数
  _insOwner = null;
  constructor(host: string, ins) {
    this._host = host;
    this._insOwner = ins;
    this.initWebsocket();
  }
  // 初始化创建websocket对象
  initWebsocket() {
    if (this.ws != null) {
      this.ws.close();
    }
    try {
      this.ws = new WebSocket(this._host);
    } catch (error) {
      console.error("create websocket error: ", error.toString());
      if (this._insOwner && typeof this._insOwner.onDisconnect == "function") {
        // websocket连接失败处理函数
        this._insOwner.onDisconnect();
      }
      return;
    }

    this.ws.onopen = () => {
      //开始连接
      if (this._insOwner && typeof this._insOwner.onConnect == "function") {
        this._insOwner.onConnect();
      }
    };
    this.ws.onerror = (error) => {
      console.error("websocket error: ", error);
    };
    this.ws.onclose = (e) => {
      console.error("websocket onclose: ", e);
      if (this._insOwner && typeof this._insOwner.onDisconnect == "function") {
        this._insOwner.onDisconnect(e);
      }
    };
    this.ws.onmessage = (message) => {
      if (this._insOwner && typeof this._insOwner.onMessage == "function") {
        this._insOwner.onMessage(message.data);
      }
    };
  }
  // 发送消息
  sendMessage(message) {
    console.log(this.ws, this.ws.readyState, message);
    //  WebSocket 是否处于打开状态 readyState === 1
    //CONNECTING:值为0,表示正在连接。
    //OPEN:值为1,表示连接成功,可以通信了。
    //CLOSING:值为2,表示连接正在关闭。
    //CLOSED:值为3,表示连接已经关闭,或者打开连接失败。
    if (this.ws && this.ws.readyState == 1) {
      this.ws.send(message);
      return true;
    }
    return false;
  }
  // 关闭连接
  dispose() {
    this._host = "";
    this._insOwner = null;
    if (this.ws != null) {
      this.ws.close();
      this.ws = null;
    }
  }
}

在此基础类之上定义一个可发布,订阅消息主题的类
什么叫消息主题?在真实开发中,我们接收websocket信息时,后端会在一个ws连接里推送不同页面的信息,这个时候我们就要判断当前推送的信息是不是当前页面要用的信息,如果是就调用接收信息的回调,不是就忽略。示例如下:
在这里插入图片描述

可发布,订阅消息主题的类代码如下
interface UpcWebsocketEvent {
  type: UpcWebsocketEventType;
  data: UpcWebsocketEventInfo;
}

interface UpcWebsocketEventInfo {
  isReconnect: boolean; // 是否是重连
  reconnectable: boolean; // 是否自动重连
  reconnectDivide: number; // 重连间隔 S
  reconnectMaxTimes?: number; // 最大重连次数
  reconnectTimes?: number; // 当前重连次数
}

export enum UpcWebsocketEventType {
  Connected,
  Disconnect,
  Reconnect,
}

export class UpcWebsocketClient {
  _client: any = null; // 存储WebSocket客户端实例
  _url = "";
  _urlGenerator = null; // url 生成器

  isConnect = false; //连接状态
  reConnectNum = 0; //重连次数
  reConnectMaxNum = 3; // 最大重连次数

  // 登录相关
  _logined = false;
  _loginTimer = null;
  _loginResolve = null;

  // 心跳相关
  _heartTimer = null;
  _heartBeatResolve = null;
  _heartBeatResolveTimer = null;

  // 重连句柄
  _autoReloginTimer = null;
  _autoReloginDivide = 10; // 每隔 10 S 发起一次重连

  // 实例事件, 需全局订阅
  _instanceEventCallback: ((event: UpcWebsocketEvent) => void) | null = null;
  // 主题订阅
  _subIndex = 0;
  _subMap = new Map<string, Map<number, (data: any) => void>>(); // Map<topic, Map<subIndex, listner>>

  constructor(urlGenerator) {
    if (typeof urlGenerator != "function") {
      throw new Error("urlGenerator is not function");
    }
    this._urlGenerator = urlGenerator;
  }

  onInstanceEvent(fn: (event: UpcWebsocketEvent) => void) {
    if (typeof fn == "function") {
      this._instanceEventCallback = fn;
    }
  }

  emitInstanceEvent(type: UpcWebsocketEventType, data: UpcWebsocketEventInfo) {
    if (typeof this._instanceEventCallback == "function") {
      this._instanceEventCallback({ type, data });
    }
  }
// 根据基础类创建websocket对象
  async createWebsocket() {
    // this._client存储创建出来的websocket对象索引
    if (this._client != null) {
      this._client.dispose();
      this._client = null;
    }
    // 拿到websocket连接url,一般都是要携带token的,所以拿url的方法要抽离出类之外,通过构造函数传入
    const result = await this._urlGenerator();
    if (result.code != 0) {
      return { code: result.code, msg: `获取推送服务认证口令失败` };
    }
    this._url = result.data; //    `${this._host}/ws?token=${this._token}`;

    return new Promise((resolve: (connected: boolean) => void) => {
      this._loginResolve = resolve;
      this._client = new WebsocketClient(this._url, this);
    });
  }
  // 开始连接,如果连接失败,将尝试自动重连,开启连接
  async start() {
    const connected = await this.createWebsocket();
    // 判断ws对象是否创建成功,否就重连创建,是就开启心跳检测机制
    if (!connected) {
      this.autoRelogin();
    } else {
      this.keepHeartBeat(); // 开始心跳
    }
  }
  // 暂停连接,清理相关资源
  pause() {
    // 清理 socket
    this._client && this._client.dispose();
    this._client = null;

    // 如果有心跳,取消心跳
    this._heartTimer && clearTimeout(this._heartTimer);
    this._heartTimer = null;

    // 如果心跳正在进行, 取消回调
    this._heartBeatResolveTimer && clearTimeout(this._heartBeatResolveTimer);
    this._heartBeatResolveTimer = null;
    this._heartBeatResolve = null;

    this._loginResolve = null;

    // 如果在重连, 去掉重连
    this._autoReloginTimer && clearTimeout(this._autoReloginTimer);
    this._autoReloginTimer = null;

    // 清理主题订阅
    this._subIndex = 0;
    this._subMap.clear();

    this.isConnect = false; //连接状态
    this.reConnectNum = 0;

    return;
  }
  // 重连,每隔一定时间尝试重新连接
  async autoRelogin() {
    this._autoReloginTimer = setTimeout(async () => {
      this._autoReloginTimer = null;
      const ret = await this.createWebsocket();
      if (!ret) {
        return this.autoRelogin();
      } else {
        this.keepHeartBeat(); // 开始心跳
      }
      this.isConnect = true;
      // 重连成功
      this.emitInstanceEvent(UpcWebsocketEventType.Reconnect, {
        msg: "",
        relogin_divide: this._autoReloginDivide,
      });
    }, this._autoReloginDivide * 1000);
  }
  // 建立连接,连接建立成功之后要做的事情,基础类的onopen事件回调函数
  onConnect() {
    console.log(`webSocket已连接`);
    this.isConnect = true;
    this.reConnectNum = 0;
    typeof this._loginResolve == "function" && this._loginResolve(true);
    this._loginResolve = null;
    this.recoverSubscribe(); // 恢复订阅
  }
  // WebSocket连接断开时调用,清理资源,基础类的onclose事件回调函数
  onDisconnect(e?) {
    console.log(`webSocket已经关闭 ${e} `);
    // 清理 socket
    this._client && this._client.dispose();
    this._client = null;

    // 如果有心跳,取消心跳
    this._heartTimer && clearTimeout(this._heartTimer);
    this._heartTimer = null;
    // 如果心跳正在进行, 取消回调
    this._heartBeatResolveTimer && clearTimeout(this._heartBeatResolveTimer);
    this._heartBeatResolveTimer = null;
    this._heartBeatResolve = null;

    // 触发掉线通知掉线
    this.emitInstanceEvent(UpcWebsocketEventType.Disconnect, {
      msg: "",
      relogin_divide: this._autoReloginDivide,
    });

    //被动断开,重新连接
    if (e && e?.code) {
      this.autoRelogin();
      console.log("websocket连接不上,请重新登录或联系开发人员!");
    }
  }
  // 接收到的 WebSocket消息,解析消息并调用相应的处理函数,基础类的onmessage事件回调函数
  onMessage(message: string) {
    const msg = JSON.parse(message);
    // console.log("onMessage", msg);
    if (!msg.channel) {
      console.error("onMessage error ");
      return;
    }
    if (
      !(typeof msg.text == "string" && JSON.parse(msg.text).action_ans == "subscriber successed!")
    ) {
      this.handlePublish(msg);
    }
  }

  // 使用nginx代理webSocket链接,客户端和服务器握手成功后,如果在60s时间内没有数据交互,连接就会自动断开
  // 心跳检测,定期 30s 发送心跳请求并处理心跳响应
  keepHeartBeat() {
    this._heartTimer = setTimeout(async () => {
      this._heartTimer = null;
      const ret = await this.sendHeartBeat();
      // if (ret?.code == 1) {
      //   return this.onDisconnect();
      // }
      return this.keepHeartBeat();
    }, 30 * 1000); // 
  }
  // 发送心跳请求,返回一个Promise,用于处理心跳响应
  sendHeartBeat() {
    return new Promise((resolve) => {
      this._heartBeatResolve = resolve;
      this._heartBeatResolveTimer = setTimeout(() => {
        this._heartBeatResolveTimer = null;
        if (this._heartBeatResolve !== null) {
          this._heartBeatResolve({ code: 100, ret_msg: "sendHeartBeat timeout", data: null });
          this._heartBeatResolve = null;
        }
      }, 10 * 1000);
      const reqData = {
        action: "req",
        channel: "ping",
      };
      this._client && this._client.sendMessage(JSON.stringify(reqData));
    });
  }
  // 
  handlePublish(msg) {
    const topic = msg.channel;
    const handleMap = this._subMap.get(topic);
    if (handleMap != null) {
      handleMap.forEach((fn) => {
        try {
          fn(JSON.parse(msg.text));
        } catch (e) {
          console.log(e);
        }
      });
    }
  }
  // 恢复所有主题的订阅
  recoverSubscribe() {
    this._subMap.forEach((handleMap, topic) => {
      if (handleMap != null && handleMap.size > 0) {
        const reqData = {
          action: "sub",
          channel: topic,
          data: {},
        };
        this._client && this._client.sendMessage(JSON.stringify(reqData));
      }
    });
  }
  /**
   * 订阅指定主题,并注册事件监听器。
   * @param topic 订阅的主题字符串。
   * @param listener 当订阅主题触发时执行的回调函数。
   * @returns 返回一个句柄,用于取消订阅。
   */
  subscribe(topic: string, listener: () => void) {
    if (typeof listener != "function") return;
    let handleMap = this._subMap.get(topic);
    if (handleMap == null) {
      handleMap = new Map();
      this._subMap.set(topic, handleMap);
    }
    const handle = ++this._subIndex;
    handleMap.set(handle, listener);
    if (handleMap.size == 1) {
      // 初始添加订阅, 需要向后台发送消息
      const reqData = {
        action: "sub",
        channel: topic,
        data: {},
      };
      this._client && this._client.sendMessage(JSON.stringify(reqData));
    }
    return handle;
  }
  /**
   * 取消订阅指定主题或删除指定句柄对应的监听器。
   * @param topic 要取消订阅的主题字符串。
   * @param handle 可选,取消特定句柄对应的监听器;若未提供,则取消该主题下的所有监听器。
   */
  unsubscribe(topic, handle?) {
    const handleMap = this._subMap.get(topic); // 获取对应主题的订阅
    if (handleMap == null || handleMap.size == 0) {
      // 未订阅,无需退订
      return;
    }
    if (typeof handle == "undefined") {
      // 全部退订
      handleMap.clear();
    } else {
      handleMap.delete(handle);
    }
    if (handleMap.size == 0) {
      // 向后台取消订阅
      const reqData = {
        action: "unsub",
        channel: topic,
        data: {},
      };
      this._client && this._client.sendMessage(JSON.stringify(reqData));
    }
  }
  /**
   * 清理资源并释放所有内部状态。
   */
  dispose() {
    // 清理 socket
    this._client && this._client.dispose();
    this._client = null;

    // 如果有心跳,取消心跳
    this._heartTimer && clearTimeout(this._heartTimer);
    this._heartTimer = null;

    // 如果心跳正在进行, 取消回调
    this._heartBeatResolveTimer && clearTimeout(this._heartBeatResolveTimer);
    this._heartBeatResolveTimer = null;
    this._heartBeatResolve = null;

    // 如果在重连, 去掉重连
    this._autoReloginTimer && clearTimeout(this._autoReloginTimer);
    this._autoReloginTimer = null;

    // 清理系统订阅
    this._instanceEventCallback = null;
    // 清理主题订阅
    this._subIndex = 0;
    this._subMap.clear();
  }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

日期类(java)

文章目录 第一代日期类 Date常用构造方法SimpleDateFormat 日期格式化类日期转字符串&#xff08;String -> Date)字符串转日期 (String->Date) 第二代日期类 Calendar常用字段与如何得到实例对象相关 API 第三代日期类&#xff08;LocalDate\TIme)日期&#xff0c;时间&…

【详细教程】如何使用YOLOv10进行图片与视频的目标检测

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

什么是DEQ?

DEQ (Delivered Ex Quay, Duty Paid) 是指目的港码头交货 (……指定目的港)。 这种术语规定卖方在指定目的港码头将货物交给买方处置&#xff0c;并且不办理进口清关手续。 DEQ适用范围 DEQ术语仅适用于海运、内河运输或多式联运&#xff0c;并且在目的港码头卸货时使用。如…

uniapp,uni-fab组件拖动属性,替代方案

文章目录 1. 背景2. 替代方案2.1 方案一2.2 方案二参考1. 背景 最近基于uniapp开发一款设备参数调试的APP软件,其中有使用到悬浮按钮,快速开发阶段,为了能尽快上线,直接使用了uni-ui的扩展组件uni-fab,参考【1】,效果如下图: 后期,相应的界面内容增多,由于uni-fab是…

简单科普-GPT到底是什么?

1.ChatGPT ChatGPT&#xff08;全名&#xff1a;Chat Generative Pre-trained Transformer&#xff09;&#xff0c;是OpenAI研发的一款聊天机器人程序 &#xff0c;于2022年11月30日发布 。ChatGPT是人工智能技术驱动的自然语言处理工具&#xff0c;它能够基于在预训练阶段所见…

通天星CMSV6车载监控平台CompanyList信息泄露漏洞

1 漏洞描述 通天星CMSV6车载视频监控平台是东莞市通天星软件科技有限公司研发的监控平台,通天星CMSV6产品覆盖车载录像机、单兵录像机、网络监控摄像机、行驶记录仪等产品的视频综合平台。通天星科技应用于公交车车载、校车车载、大巴车车载、物流车载、油品运输车载、警车车…

昇思25天学习打卡营第5天|MindSpore-ResNet50图像分类

MindSpore-ResNet50图像分类 CIFAR-10数据集 CIFAR-10数据集是一个广泛使用的图像分类数据集,它包含了60,000张32x32的RGB彩色图像,分为10个类别,每个类别有6,000张图像。这些类别包括飞机(airplane)、汽车(automobile)、鸟类(bird)、猫(cat)、鹿(deer)、狗(dog…

最新测评!不可错过的三款网络代理服务商

在如今数字化高速发展的时代&#xff0c;网络代理服务已经成为许多企业和个人在进行网络活动时不可或缺的工具。为了给大家提供更具参考价值的推荐&#xff0c;我们的测评团队经过深入研究和实际测试&#xff0c;精心挑选了三款表现出色的网络代理服务商——极光HTTP、全民HTTP…

软件工程 例题

用例图 1. 某个学生成绩管理系统的部分参与者和用例总结如下。 教务管理人员: 登录系统教师、学生名单管理学期教学计划管理成绩管理。课程分配&#xff0c;每次课程分配时都必须打印任课通知书 学生&#xff1a; 登录系统选课。 教师: 登录系统成绩管理&#xff0c;并…

昇思25天学习打卡营第3天|网络构建

学习目标&#xff1a;熟练掌握网络构建方法 了解mindspore.nn 实例构建简单的神经网络 网络模型中各层参数 昇思大模型平台 AI实验室 学习记录&#xff1a; 一、关于mindspore.nn 在MindSpore中&#xff0c;Cell类是构建所有网络的基类&#xff0c;也是网络的基本单元。cell…

如何集成CppCheck到visual studio中

1.CPPCheck安装 在Cppcheck官方网站下载最新版本1.70&#xff0c;官网链接&#xff1a;http://cppcheck.sourceforge.net/ 安装Cppcheck 2.集成步骤 打开VS&#xff0c;菜单栏工具->外部工具->添加&#xff0c;按照下图设置&#xff0c;记得勾选“使用输出窗口” 2.…

高效数据采集监控平台 一体化平台 数据可视化!

提高工作效率&#xff0c;一直是各种厂家在寻找的方法。任何一种有效且实用的方法都值得去尝试。数据采集监控平台是一种能高效处理数据的方式&#xff0c;其主要工作内容是从各个产生数据的仪器设备传感器中采集数据、对数据进行集中整理整合、分析、显示、绘制图表、存储、传…

ubuntu22.04速装中文输入法

附送ubuntu安装chrome wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb sudo dpkg -i google-chrome-stable_current_amd64.deb

如何做互联网项目需求评估?

关于互联网项目需求评估&#xff0c;我们可以按照以下步骤进行&#xff1a; 一、确定项目主题和目标受众&#xff1a;这篇文章首先要明确你要评估的互联网项目的主题是什么&#xff0c;你的目标受众是谁&#xff1f;你需要对项目的背景和目的有清晰的了解。 二、项目规模和内…

将TensorFlow嵌入到Jupyter Notebook中,个人学习记录

起因是学习吴恩达机器学习过程中&#xff0c;在神经网络tensorflow的部分&#xff0c;需要在Jupyter Notebook中跑相关的代码&#xff0c;于是在网上找了很多资料&#xff0c;终于跑成功了。该笔记仅为个人学习记录&#xff0c;如有任何问题请见谅。 import numpy as np impor…

如何3分钟上手传得神乎其神的AI绘画!一篇文章带你搞懂!

前言 今年 AI 绘画绝对是大火的概念之一&#xff0c;这次加入了生财 AI 绘画小航海的船&#xff0c;今天是体验的第1天&#xff0c;那么 AI 绘画是什么呢&#xff1f; 简单来说就是利用 AI 实现绘画&#xff0c;在特定的软件或者程序中&#xff0c;输入一定的关键词或者指令&…

【地理库 Turf.js】

非常全面的地理库 &#xff0c; 这里枚举一些比较常用&#xff0c;重点的功能&#xff0c; 重点功能 提供地理相关的类&#xff1a;包括点&#xff0c;线&#xff0c;面等类。 测量功能&#xff1a;点到线段的距离&#xff0c;点和线的关系等。 判断功能&#xff1a; 点是否在…

哈尔滨高校大学智能制造实验室数字孪生可视化系统平台项目的验收

哈尔滨高校大学智能制造实验室数字孪生可视化系统平台项目的验收&#xff0c;标志着这一技术在教育领域的应用取得了新的突破。项目旨在开发一个数字孪生可视化系统平台&#xff0c;用于哈尔滨高校大学智能制造实验室的设备模拟、监测与数据分析。项目的主要目标包括&#xff1…

【Sklearn-驯化】sklearn中决策树cart的用法,看这篇就够了

【Sklearn-驯化】sklearn中决策树cart的用法&#xff0c;看这篇就够了 本次修炼方法请往下查看 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我工作、学习、实践 IT领域、真诚分享 踩坑集合&#xff0c;智慧小天地&#xff01; &#x1f387; 免费获取相关内容文档…

【深海王国】小学生都能玩的语音模块?ASRPRO打造你的第一个智能语音助手(3)

Hi~ (o^^o)♪, 各位深海王国的同志们&#xff0c;早上下午晚上凌晨好呀~ 辛勤工作的你今天也辛苦啦(/≧ω) 今天大都督继续为大家带来系列——小学生都能玩的语音模块&#xff0c;帮你一周内快速学会语音模块的使用方式&#xff0c;打造一个可用于智能家居、物联网领域的语音助…