【ceph学习】messenger模块代码走读(1)

news2025/10/22 3:50:02

messenger代码走读

  1. messenger的使用

以mgr代码为例,看看messengrr的初始化和启动。

//构造函数,初始化一个client_messenger对象。
MgrStandby::MgrStandby(int argc, const char **argv) :
  Dispatcher(g_ceph_context),
  monc{g_ceph_context, poolctx},
  client_messenger(Messenger::create(
             g_ceph_context,
             cct->_conf.get_val<std::string>("ms_public_type").empty() ?
            cct->_conf.get_val<std::string>("ms_type") : cct->_conf.get_val<std::string>("ms_public_type"),
             entity_name_t::MGR(),
             "mgr",
             Messenger::get_pid_nonce()))
             ....
{}
//初始化,此过程中包含messenger对象的启停。
int MgrStandby::init()
{
  // Initialize Messenger
  client_messenger->add_dispatcher_tail(this);
  client_messenger->add_dispatcher_head(&objecter);
  client_messenger->add_dispatcher_tail(&client);
  client_messenger->start();

  poolctx.start(2);

  // Initialize MonClient
  if (monc.build_initial_monmap() < 0) {
    client_messenger->shutdown();
    client_messenger->wait();
    return -1;
  }

  monc.sub_want("mgrmap", 0, 0);

  monc.set_want_keys(CEPH_ENTITY_TYPE_MON|CEPH_ENTITY_TYPE_OSD
      |CEPH_ENTITY_TYPE_MDS|CEPH_ENTITY_TYPE_MGR);
  monc.set_messenger(client_messenger.get());

  int r = monc.init();
  if (r < 0) {
    monc.shutdown();
    client_messenger->shutdown();
    client_messenger->wait();
    return r;
  }
  mgrc.init();
  client_messenger->add_dispatcher_tail(&mgrc);

  r = monc.authenticate();
  if (r < 0) {
    derr << "Authentication failed, did you specify a mgr ID with a valid keyring?" << dendl;
    monc.shutdown();
    client_messenger->shutdown();
    client_messenger->wait();
    return r;
  }

  monc.set_passthrough_monmap();

  client_t whoami = monc.get_global_id();
  client_messenger->set_myname(entity_name_t::MGR(whoami.v));
  monc.set_log_client(&log_client);
  _update_log_config();
  objecter.set_client_incarnation(0);
  objecter.init();
  objecter.start();
  client.init();
  timer.init();

  py_module_registry.init();
  mgr_perf_start(g_ceph_context);

  tick();

  dout(4) << "Complete." << dendl;
  return 0;
}

  1. 初始化函数解析

从上文可以看到,messenger对象的初始化,调用了create函数。而mstype默认为async+posix,所以messenger对象均为asyncMessenger对象。

Messenger *Messenger::create(CephContext *cct, const std::string &type,
                 entity_name_t name, std::string lname, uint64_t nonce)
{
  if (type == "random" || type.find("async") != std::string::npos)
    return new AsyncMessenger(cct, name, type, std::move(lname), nonce);
  lderr(cct) << "unrecognized ms_type '" << type << "'" << dendl;
  return nullptr;
}
//global.yaml.in
- name: ms_type
  type: str
  level: advanced
  desc: Messenger implementation to use for network communication
  fmt_desc: Transport type used by Async Messenger. Can be ``async+posix``,
    ``async+dpdk`` or ``async+rdma``. Posix uses standard TCP/IP networking and is
    default. Other transports may be experimental and support may be limited.
  default: async+posix
  flags:
  - startup
  with_legacy: true
- name: ms_public_type
  type: str
  level: advanced
  desc: Messenger implementation to use for the public network
  long_desc: If not specified, use ms_type
  see_also:
  - ms_type
  flags:
  - startup
  with_legacy: true

下面看看asyncmessenger的初始化函数。如果默认的路径,transport_type就是posix。

AsyncMessenger::AsyncMessenger(CephContext *cct, entity_name_t name,
                               const std::string &type, std::string mname, uint64_t _nonce)
  : SimplePolicyMessenger(cct, name),
    dispatch_queue(cct, this, mname),
    nonce(_nonce)
{
  std::string transport_type = "posix";
  if (type.find("rdma") != std::string::npos)
    transport_type = "rdma";
  else if (type.find("dpdk") != std::string::npos)
    transport_type = "dpdk";

  auto single = &cct->lookup_or_create_singleton_object<StackSingleton>(
    "AsyncMessenger::NetworkStack::" + transport_type, true, cct);
  single->ready(transport_type);
  stack = single->stack.get();
  stack->start();
  local_worker = stack->get_worker();
  local_connection = ceph::make_ref<AsyncConnection>(cct, this, &dispatch_queue,
                     local_worker, true, true);
  init_local_connection();
  reap_handler = new C_handle_reap(this);
  unsigned processor_num = 1;
  if (stack->support_local_listen_table())
    processor_num = stack->get_num_worker();
  for (unsigned i = 0; i < processor_num; ++i)
    processors.push_back(new Processor(this, stack->get_worker(i), cct));
}

在mgr的init中,client_messenger将this、client、objecter都进行了add_dispatch_head/add_dispatch_tail。

  /**
   * Add a new Dispatcher to the front of the list. If you add
   * a Dispatcher which is already included, it will get a duplicate
   * entry. This will reduce efficiency but not break anything.
   */
  void add_dispatcher_head(Dispatcher *d) {
    bool first = dispatchers.empty();
    dispatchers.push_front(d);
    if (d->ms_can_fast_dispatch_any())
      fast_dispatchers.push_front(d);
    if (first)
      ready();
  }
  /**
   * Add a new Dispatcher to the end of the list. If you add
   * a Dispatcher which is already included, it will get a duplicate
   * entry. This will reduce efficiency but not break anything.
   */
  void add_dispatcher_tail(Dispatcher *d) {
    bool first = dispatchers.empty();
    dispatchers.push_back(d);
    if (d->ms_can_fast_dispatch_any())
      fast_dispatchers.push_back(d);
    if (first)
      ready();
  }

然后就是client_messenger对象的start

int AsyncMessenger::start()
{
  std::scoped_lock l{lock};
  ldout(cct,1) << __func__ << " start" << dendl;

  // register at least one entity, first!
  ceph_assert(my_name.type() >= 0);

  ceph_assert(!started);
  started = true;
  stopped = false;

  if (!did_bind) {
    entity_addrvec_t newaddrs = *my_addrs;
    for (auto& a : newaddrs.v) {
      a.nonce = nonce;
    }
    set_myaddrs(newaddrs);
    _init_local_connection();
  }

  return 0;
}
  1. messenger的组织结构

messenger作为msg的基类,主要负责绑定IP、转发请求等。

class Messenger {
private:
  // 请求分发处理器
  std::deque<Dispatcher*> dispatchers;
  std::deque<Dispatcher*> fast_dispatchers;
  ZTracer::Endpoint trace_endpoint;

public:
  //构造函数和初始化
  Messenger(CephContext *cct_, entity_name_t w);
  virtual ~Messenger() {}
  static Messenger *create(CephContext *cct,
                           const std::string &type,
                           entity_name_t name, std::string lname,
                           uint64_t nonce);
  static Messenger *create_client_messenger(CephContext *cct, std::string lname);

  //将 Dispatcher对象添加到messenger的dispatcher队列中。
  void add_dispatcher_head(Dispatcher *d) {
    bool first = dispatchers.empty();
    dispatchers.push_front(d);
    if (d->ms_can_fast_dispatch_any())
      fast_dispatchers.push_front(d);
    if (first)
      ready();
  }
  void add_dispatcher_tail(Dispatcher *d) {
    bool first = dispatchers.empty();
    dispatchers.push_back(d);
    if (d->ms_can_fast_dispatch_any())
      fast_dispatchers.push_back(d);
    if (first)
      ready();
  }
protected:

  /**
   * A courtesy function for Messenger implementations which
   * will be called when we receive our first Dispatcher.
   */
  virtual void ready() { }
    /**
   * Determine whether a message can be fast-dispatched. We will
   * query each Dispatcher in sequence to determine if they are
   * capable of handling a particular message via "fast dispatch".
   */
  bool ms_can_fast_dispatch(const ceph::cref_t<Message>& m) {
    for (const auto &dispatcher : fast_dispatchers) {
      if (dispatcher->ms_can_fast_dispatch2(m))
    return true;
    }
    return false;
  }
public:
  //绑定ip
  virtual int bind(const entity_addr_t& bind_addr) = 0;
  virtual int bindv(const entity_addrvec_t& addrs);
  virtual int rebind(const std::set<int>& avoid_ports) { return -EOPNOTSUPP; }
  virtual int client_bind(const entity_addr_t& bind_addr) = 0;

  //启动线程,可以接收、发送messages。
  virtual int start() { started = true; return 0; }
  //关闭线程
  virtual void wait() = 0;
  virtual int shutdown() { started = false; return 0; }
  //发送
  virtual int send_to(
    Message *m,
    int type,
    const entity_addrvec_t& addr) = 0;
  int send_to_mon(
    Message *m, const entity_addrvec_t& addrs) {
    return send_to(m, CEPH_ENTITY_TYPE_MON, addrs);
  }
  int send_to_mds(
    Message *m, const entity_addrvec_t& addrs) {
    return send_to(m, CEPH_ENTITY_TYPE_MDS, addrs);
  }
  //建立连接
  virtual ConnectionRef connect_to(
    int type, const entity_addrvec_t& dest,
    bool anon=false, bool not_local_dest=false) = 0;
  ConnectionRef connect_to_mon(const entity_addrvec_t& dest,
      bool anon=false, bool not_local_dest=false) {
    return connect_to(CEPH_ENTITY_TYPE_MON, dest, anon, not_local_dest);
  }
  ConnectionRef connect_to_mds(const entity_addrvec_t& dest,
      bool anon=false, bool not_local_dest=false) {
    return connect_to(CEPH_ENTITY_TYPE_MDS, dest, anon, not_local_dest);
  }
  ConnectionRef connect_to_osd(const entity_addrvec_t& dest,
      bool anon=false, bool not_local_dest=false) {
    return connect_to(CEPH_ENTITY_TYPE_OSD, dest, anon, not_local_dest);
  }
  ConnectionRef connect_to_mgr(const entity_addrvec_t& dest,
      bool anon=false, bool not_local_dest=false) {
    return connect_to(CEPH_ENTITY_TYPE_MGR, dest, anon, not_local_dest);
  }

protected:
  //快速分发
  //Deliver a single Message via "fast dispatch".
  void ms_fast_dispatch(const ceph::ref_t<Message> &m) {
    m->set_dispatch_stamp(ceph_clock_now());
    for (const auto &dispatcher : fast_dispatchers) {
      if (dispatcher->ms_can_fast_dispatch2(m)) {
    dispatcher->ms_fast_dispatch2(m);
    return;
      }
    }
    ceph_abort();
  }
  void ms_fast_dispatch(Message *m) {
    return ms_fast_dispatch(ceph::ref_t<Message>(m, false)); /* consume ref */
  }
  void ms_fast_preprocess(const ceph::ref_t<Message> &m) {
    for (const auto &dispatcher : fast_dispatchers) {
      dispatcher->ms_fast_preprocess2(m);
    }
  }
  /**
   *  Deliver a single Message. Send it to each Dispatcher
   *  in sequence until one of them handles it.
   *  If none of our Dispatchers can handle it, ceph_abort().
   */
  void ms_deliver_dispatch(const ceph::ref_t<Message> &m) {
    m->set_dispatch_stamp(ceph_clock_now());
    for (const auto &dispatcher : dispatchers) {
      if (dispatcher->ms_dispatch2(m))
    return;
    }
    ceph_assert(!cct->_conf->ms_die_on_unhandled_msg);
  }
  void ms_deliver_dispatch(Message *m) {
    return ms_deliver_dispatch(ceph::ref_t<Message>(m, false)); /* consume ref */
  }
  //暂时没看懂
  /**
   * Notify each Dispatcher of a new Connection. Call
   * this function whenever a new Connection is initiated or
   * reconnects.
   */
  void ms_deliver_handle_connect(Connection *con) {
    for (const auto& dispatcher : dispatchers) {
      dispatcher->ms_handle_connect(con);
    }
  }

  void ms_deliver_handle_fast_connect(Connection *con) {
    for (const auto& dispatcher : fast_dispatchers) {
      dispatcher->ms_handle_fast_connect(con);
    }
  }
 //暂时没看懂
  void ms_deliver_handle_accept(Connection *con) {
    for (const auto& dispatcher : dispatchers) {
      dispatcher->ms_handle_accept(con);
    }
  }

  void ms_deliver_handle_fast_accept(Connection *con) {
    for (const auto& dispatcher : fast_dispatchers) {
      dispatcher->ms_handle_fast_accept(con);
    }
  }

  void ms_deliver_handle_reset(Connection *con) {
    for (const auto& dispatcher : dispatchers) {
      if (dispatcher->ms_handle_reset(con))
    return;
    }
  }

  void ms_deliver_handle_remote_reset(Connection *con) {
    for (const auto& dispatcher : dispatchers) {
      dispatcher->ms_handle_remote_reset(con);
    }
  }

  void ms_deliver_handle_refused(Connection *con) {
    for (const auto& dispatcher : dispatchers) {
      if (dispatcher->ms_handle_refused(con))
        return;
    }
  }
};
  1. asyncmessenger的组织结构

SimplePolicyMessenger继承自Messenger,实现了简单的set/get policy.
asyncmessenger继承SimplePolicyMessenger,实习了大部分函数。


class SimplePolicyMessenger : public Messenger
{
private:
  ceph::mutex policy_lock =
    ceph::make_mutex("SimplePolicyMessenger::policy_lock");
  ceph::net::PolicySet<Throttle> policy_set;

public:

  SimplePolicyMessenger(CephContext *cct, entity_name_t name): Messenger(cct, name){}

  Policy get_policy(int t) override {
    std::lock_guard l{policy_lock};
    return policy_set.get(t);
  }

  Policy get_default_policy() override {
    std::lock_guard l{policy_lock};
    return policy_set.get_default();
  }

  void set_default_policy(Policy p) override {
    std::lock_guard l{policy_lock};
    policy_set.set_default(p);
  }

  void set_policy(int type, Policy p) override {
    std::lock_guard l{policy_lock};
    policy_set.set(type, p);
  }

  void set_policy_throttlers(int type,
                 Throttle* byte_throttle,
                 Throttle* msg_throttle) override {
    std::lock_guard l{policy_lock};
    policy_set.set_throttlers(type, byte_throttle, msg_throttle);
  }

}; /* SimplePolicyMessenger */

class AsyncMessenger : public SimplePolicyMessenger {
public:

  AsyncMessenger(CephContext *cct, entity_name_t name, const std::string &type,
                 std::string mname, uint64_t _nonce);
  ~AsyncMessenger() override;

  bool set_addr_unknowns(const entity_addrvec_t &addr) override;
  void set_addrs(const entity_addrvec_t &addrs) override;

  //获取队列长度
  int get_dispatch_queue_len() override {
    return dispatch_queue.get_queue_len();
  }

  double get_dispatch_queue_max_age(utime_t now) override {
    return dispatch_queue.get_max_age(now);
  }

  void set_cluster_protocol(int p) override {
    ceph_assert(!started && !did_bind);
    cluster_protocol = p;
  }

  //实现父类函数
  int bind(const entity_addr_t& bind_addr) override;
  int rebind(const std::set<int>& avoid_ports) override;
  int bindv(const entity_addrvec_t& bind_addrs) override;
  int client_bind(const entity_addr_t& bind_addr) override;
  int client_reset() override;
  bool should_use_msgr2() override;
  int start() override;
  void wait() override;
  int shutdown() override;
  int send_to(Message *m, int type, const entity_addrvec_t& addrs) override;

  ConnectionRef connect_to(int type,
               const entity_addrvec_t& addrs,
               bool anon, bool not_local_dest=false) override;
  ConnectionRef get_loopback_connection() override;
  void mark_down(const entity_addr_t& addr) override {
    mark_down_addrs(entity_addrvec_t(addr));
  }
  void mark_down_addrs(const entity_addrvec_t& addrs) override;
  void mark_down_all() override {
    shutdown_connections(true);
  }

protected:
  //实现父类
  void ready() override;

private:
  //创建连接
  AsyncConnectionRef create_connect(const entity_addrvec_t& addrs, int type, bool anon);

  void _finish_bind(const entity_addrvec_t& bind_addrs, const entity_addrvec_t& listen_addrs);

  entity_addrvec_t _filter_addrs(const entity_addrvec_t& addrs);

 private:
  //以下暂时没搞懂
  NetworkStack *stack;
  std::vector<Processor*> processors;
  friend class Processor;
  DispatchQueue dispatch_queue;

  /**
   * hash map of addresses to Asyncconnection
   *
   * NOTE: a Asyncconnection* with state CLOSED may still be in the map but is considered
   * invalid and can be replaced by anyone holding the msgr lock
   */
  ceph::unordered_map<entity_addrvec_t, AsyncConnectionRef> conns;

  void _init_local_connection() {
    ceph_assert(ceph_mutex_is_locked(lock));
    local_connection->peer_addrs = *my_addrs;
    local_connection->peer_type = my_name.type();
    local_connection->set_features(CEPH_FEATURES_ALL);
    ms_deliver_handle_fast_connect(local_connection.get());
  }

  void shutdown_connections(bool queue_reset);

public:

  /// con used for sending messages to ourselves
  AsyncConnectionRef local_connection;

  /**
   * This wraps _lookup_conn.
   */
  AsyncConnectionRef lookup_conn(const entity_addrvec_t& k) {
    std::lock_guard l{lock};
    return _lookup_conn(k); /* make new ref! */
  }

  int accept_conn(const AsyncConnectionRef& conn);
  bool learned_addr(const entity_addr_t &peer_addr_for_me);
  void add_accept(Worker *w, ConnectedSocket cli_socket,
          const entity_addr_t &listen_addr,
          const entity_addr_t &peer_addr);
  NetworkStack *get_stack() {
    return stack;
  }

  /**
   * Fill in the address and peer type for the local connection, which
   * is used for delivering messages back to ourself.
   */
  void init_local_connection() {
    std::lock_guard l{lock};
    local_connection->is_loopback = true;
    _init_local_connection();
  }

  /**
   * Unregister connection from `conns`
   */
  void unregister_conn(const AsyncConnectionRef& conn) {
    std::lock_guard l{deleted_lock};
    deleted_conns.emplace(std::move(conn));
    conn->unregister();

    if (deleted_conns.size() >= cct->_conf->ms_async_reap_threshold) {
      local_worker->center.dispatch_event_external(reap_handler);
    }
  }

  /**
   * Reap dead connection from `deleted_conns`
   */
  void reap_dead();

} ;

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

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

相关文章

麒麟kylin v10 sp3 升级glibc2.28 到 2.31

1. 下载glibc 2.31 wget https://mirrors.aliyun.com/gnu/glibc/glibc-2.31.tar.gz 2.解压 tar -xf glibc-2.31.tar.gz cd glibc-2.31 mkdir build && cd build 3.修改 Makefile 125行添加一行 yum reinstall libxcrypt -y scripts/test-installation.pl 128行修…

java Boss直聘爬虫数据分析

摘要 本报告利用Java和Selenium爬虫技术获取数据&#xff0c;并使用ECharts库对薪资数据进行可视化分析&#xff0c;旨在探究不同经验和学历的薪资分布情况。 数据来源 数据来源于Boss直聘&#xff0c;使用Java结合Selenium库进行数据抓取。 数据总数&#xff1a;约2000家企…

虚幻引擎(Unreal Engine)技术使得《黑神话悟空传》大火,现在重视C++的开始吃香了,JAVA,Go,Unity都不能和C++相媲美!

虚幻引擎&#xff08;Unreal Engine&#xff09;火了黑神话游戏。 往后&#xff0c;会有大批量的公司开始模仿这个赛道&#xff01; C 的虚拟引擎技术通常指的是使用 C 语言开发的游戏引擎&#xff0c;如虚幻引擎&#xff08;Unreal Engine&#xff09;等。以下是对 C 虚拟引…

[WUSTCTF2020]spaceclub(我把输入的字符切片研究了)

上sublime txt 每一行的长短对应一个二进制位&#xff0c;长空格是1&#xff0c;短空格是0&#xff0c;全部替换掉得到 上python脚本 import binasciiwith open(attachment_5.txt, r) as file:lines file.readlines() # 逐行读取文本内容output # 初始化输出字符串# 遍历…

【C++题解】1145. 数列求和

欢迎关注本专栏《C从零基础到信奥赛入门级&#xff08;CSP-J&#xff09;》 问题&#xff1a;1145. 数列求和 类型&#xff1a;递归基础 题目描述&#xff1a; 有一数列如下&#xff1a; 1 2 4 7 11 16 22 …… 试求该数列前 N 项之和。 输入&#xff1a; 一个整数 N &…

STM32G474定时器触发1次引起ADC转换直至DMA请求传输完所有通道的数据

STM32G474使用定时器1触发1次ADC转换&#xff0c;然后交给DMA循环执行&#xff0c;实现多通道ADC转换和多通道数据传输。若定时器1停止工作&#xff0c;则ADC转换也会随之停止&#xff0c;当然也不会再有DMA数据传输。 1、ADC触发信号分配 2、DMA多路复合器分配&#xff0c;指…

微信表情包格式推荐要求:240*240 +gif

微信表情包格式要求&#xff1a;240*240 gif

2024了,Neo4j能显示节点图片吗?

经过一番调研&#xff0c;答案是官方的是不能的.但有一个中文版可以显示网络图片作为节点背景 如通义千问说说&#xff1a; Neo4j 图数据库本身并不直接支持在节点中存储和显示图片。但是&#xff0c;你可以通过几种方式间接实现这一功能&#xff1a;1. 存储图片URL 最简单的…

K8S持久化存储数据

环境&#xff1a; Ubuntu-1:192.168.114.110作为主 Ubuntu-2:192.168.114.120作为从1&#xff0c;node节点1 Ubuntu-3:192.168.114.130作为从2&#xff0c;node节点2 持久化volumeMounts pod里面&#xff1a;emptyDir和hostPath。存储在node&#xff0c;NFS...&#xff0c;Clo…

微软Win11 24H2最新可选更新补丁26100.1591发布!

系统之家于8月28日发出最新报道&#xff0c;微软面向Win11 24H2用户推出八月最新的可选更新KB5041865&#xff0c;系统更新后版本号升至26100.1591。本次更新进行了多项改进&#xff0c;还优化了小组件面板。接下来&#xff0c;跟随小编一起深入了解这次更新的详细内容吧。 更新…

RSA非对称性加密02: 加密redis的连接密码(下)-私钥加密,公钥解密

全文目录,一步到位 1.前言简介1.1 专栏传送门1.1.2 上文传送门 2. 使用方式2.1 使用druid自带的RSA加密工具2.1.1 引入druid依赖2.1.2 原yml配置(对比使用)2.1.2 新yml配置 2.2 springboot的redis配置类2.2.1 例如在RedisConfig中2.2.2 设置序列化与反序列化代码示例如下: 2.3 …

Golang小项目(1)

Golang小项目(1) 前言 本项目适合Golang初学者,通过简单的项目实践来加深对Golang的基本语法和Web开发的理解。 建议前往 torna.top 查阅效果更佳 项目结构 . ├── main.go └── static├── form.html└── index.html项目流程图 定义三个路由: /:首页,显示static…

SWOT分析:解锁个人优势,规划未来成功路径

在传统的SWOT分析中&#xff0c;人们常常采用手工绘制的方式来创建图表&#xff0c;这种方式不仅过程繁琐&#xff0c;而且耗费大量时间和精力。为了简化这一流程&#xff0c;本文推荐使用可免费使用的实时在线编辑工具——即时白板&#xff0c;它是一款高效便捷的SWOT分析工具…

照片清晰度修复,这里总结了三种方法

照片清晰度修复&#xff0c;在数字化时代&#xff0c;照片不仅记录着生活的点滴&#xff0c;更承载着我们的情感和回忆。如今&#xff0c;有许多珍贵的老照片逐渐失去了原有的清晰度&#xff0c;变得模糊不堪。但随着科技的发展&#xff0c;我们有了多种方法来修复这些模糊的照…

专利检索的重要性

专利检索的重要性

从零开学C++:string类

引言&#xff1a;现在我们将正式踏入C起飞阶段&#xff0c;C语言自带的各种类能极大地简化我们所编写的代码的量&#xff0c;而我们今天要学的string就是其中之一。 更多有关C的知识点详解可前往个人主页&#xff1a;计信猫​​​​​ 一&#xff0c;认识string string其实很简…

zoom 会议 javascript 转录例子

一、启动server-to-server zoom api服务&#xff0c;用于创建会议&#xff0c;参考&#xff1a;如何使用Zoom API创建一个会议&#xff1f;-CSDN博客 二、启动meetingsdk-auth-endpoint服务&#xff0c;用于加入会议&#xff0c;参考&#xff1a;zoom 会议机器人web例子-CSDN博…

前端问答:如何在HTML中插入度数符号?

在前端开发中&#xff0c;你可能经常需要在网页中显示温度、角度或地理坐标&#xff0c;这时就需要用到度数符号&#xff08;&#xff09;。那么&#xff0c;如何在HTML中正确地插入这个符号呢&#xff1f;今天&#xff0c;我来手把手教你三种简单又实用的方法&#xff0c;让你…

怎么压缩图片大小?7款实用图片压缩免费软件大公开,赶紧收藏试试!

电脑怎么压缩图片大小&#xff1f;您是否也正在寻找一种方法&#xff0c;可以在不损失质量的情况下压缩图片&#xff1f;在我们的日常生活中&#xff0c;图片记录着无数美好瞬间&#xff0c;已经成为我们日常生活中不可或缺的一部分&#xff0c;无论是个人使用还是专业需求。随…

解决TESSY导入测试用例后出现提示接口有改变的问题

Tessy支持测试用例的导入&#xff0c;不懂测试用例怎么导入&#xff0c;可以看这一篇文章&#xff1a; TESSY导入导出测试用例-CSDN博客 当成功的导入测试用例到工程后&#xff0c;在TIE中查看了用例的源码和头文件路径都没有问题后&#xff0c;就可以执行分析&#xff1a; 但是…