Etcd 框架

基本了解

客户端、长连接与租约的关系

客户端对象

etcd的客户端对象是用户与etcd服务进行交互的主要接口,主要功能就是存储、通知和事务等功能访问

  • 键值存储:客户端通过put 和 get操作存储数据;数据存储在etcd的层级化键值数据库中
  • 监听器(Watcher):客户端可以监听指定键的变化事件,如果键值发生改变,etcd会通过回调通知客户端;借助监听器可以实现动态管理配置和服务发现
  • 事务:提供txn接口,允许客户端进行原子性操作

工作原理

  • 客户端通过gRpc与etcd集群进行交互
  • 连接时,客户端需要提供集群的地址和认证信息
  • 连接建立后,客户端可以调用功能不同API完成具体功能

长连接

长连接是指客户端与etcd服务之间的持续连接,主要用于保持会话状态以及实现实时性功能

  • 保持租约长期有效:租约需要客户端通过长连接向etcd定期发送心跳包,否则租约会到期
  • 监听:监听功能依赖于长连接,当监听的键发生变化的时候,etcd通过长连接向客户端推送事件
  • 减少开销:长连接避免了频繁的连接断开,节省了建立连接的时间和资源

工作原理

  • 客户端与 etcd 集群节点通过 gRPC 建立连接
  • 建立连接后,客户端发送 KeepAlive 请求以保持连接
  • 如果 etcd 未在一定时间内接收到心跳,会认为连接断开

租约

etcd提供的一种机制,主要用于一段时间内绑定键值数据,并确保键值在租约有效期内有效

  • 动态生存时间:键值可以绑定到租约,租约到期后,绑定的键值会自动删除
  • 分布式锁:租约结合键值和keeplive实现分布式锁,确保资源的唯一占用
  • Session管理:通过租约,可以熟实现分布式会话管理

工作原理

  • 客户端调用 LeaseGrant 接口创建租约,并设置租期
  • 将键值与租约绑定。例如,使用 Put 命令时指定租约ID
  • 客户端通过长连接定期向 etcd 发送心跳,续租以保持租约有效
  • 如果未续约,租约到期后,绑定的键值会自动被删除

应用:服务实例向 etcd 注册自己(通过租约绑定信息),当实例下线或不可用时,租约自动到期并移除信息

基本使用

安装

框架安装

GitHub - etcd-cpp-apiv3/etcd-cpp-apiv3: The etcd-cpp-apiv3 is a C++ library for etcd's v3 client APIs, i.e., ETCDCTL_API=3.

验证动态库和相应的文件是否存在

使用

上传和获取逻辑

// get.cc

#include <etcd/Client.hpp>
#include <etcd/KeepAlive.hpp>
#include <etcd/Response.hpp>
#include <etcd/Watcher.hpp>
#include <etcd/Value.hpp>
#include <thread>

// 回调函数:用于处理监控到的键值变化事件
void callback(const etcd::Response &resp) {
    // 如果收到的事件通知无效,打印错误信息并返回
    if (!resp.is_ok()) {
        std::cout << "收到一个错误的事件通知: " << resp.error_message() << std::endl;
        return;
    }

    // 遍历事件列表,逐个处理每个事件
    for (auto const& ev : resp.events()) {
        // 如果事件类型是 PUT(键值被创建或修改)
        if (ev.event_type() == etcd::Event::EventType::PUT) {
            std::cout << "服务信息发生了改变:\n";
            std::cout << "当前的值:" << ev.kv().key() << " - " << ev.kv().as_string() << std::endl;
            std::cout << "原来的值:" << ev.prev_kv().key() << " - " << ev.prev_kv().as_string() << std::endl;
        }
        // 如果事件类型是 DELETE(键值被删除)
        else if (ev.event_type() == etcd::Event::EventType::DELETE_) {
            std::cout << "服务信息下线被删除:\n";
            std::cout << "当前的值:" << ev.kv().key() << " - " << ev.kv().as_string() << std::endl;
            std::cout << "原来的值:" << ev.prev_kv().key() << " - " << ev.prev_kv().as_string() << std::endl;
        }
    }
}

int main(int argc, char *argv[])
{
    // 定义etcd服务的地址,默认使用本地运行的etcd服务
    std::string etcd_host = "http://127.0.0.1:2379";

    // 1. 实例化etcd客户端对象,用于和etcd服务交互
    etcd::Client client(etcd_host);

    // 2. 获取 "/service" 路径下的所有键值对
    auto resp = client.ls("/service").get(); // 同步调用获取响应
    if (!resp.is_ok()) {
        // 如果获取失败,打印错误信息并退出程序
        std::cout << "获取键值对数据失败: " << resp.error_message() << std::endl;
        return -1;
    }

    // 3. 遍历返回的键值对,打印每个键值对的信息
    int sz = resp.keys().size(); // 获取键值对的数量
    for (int i = 0; i < sz; ++i) {
        std::cout << resp.value(i).as_string() << " 可以提供 " << resp.key(i) << " 服务\n";
    }

    // 4. 实例化一个Watcher对象,用于监听 "/service" 路径下的键值变化
    // 参数说明:
    // - `client`: 使用前面实例化的客户端对象
    // - "/service": 要监听的路径
    // - `callback`: 监听到键值变化后调用的回调函数
    // - `true`: 是否递归监听路径下的子路径(true 表示递归)
    auto watcher = etcd::Watcher(client, "/service", callback, true);

    // 5. 启动Watcher,阻塞当前线程,持续监听事件,直到程序被中断
    watcher.Wait();

    // 6. 结束程序
    return 0;
}
//put.cc

#include <etcd/Client.hpp>
#include <etcd/KeepAlive.hpp>
#include <etcd/Response.hpp>
#include <thread>

int main(int argc, char *argv[])
{
    std::string etcd_host = "http://127.0.0.1:2379";
    //实例化客户端对象
    etcd::Client client(etcd_host);
    //获取租约保活对象--伴随着创建一个指定有效时长的租约
    auto keep_alive = client.leasekeepalive(3).get();
    //获取租约ID
    auto lease_id = keep_alive->Lease();
    //向etcd新增数据
    auto resp1 = client.put("/service/user", "127.0.0.1:8080", lease_id).get();
    if (resp1.is_ok() == false) {
        std::cout << "新增数据失败:" << resp1.error_message() << std::endl;
        return -1;
    }
    auto resp2 = client.put("/service/friend", "127.0.0.1:9090").get();
    if (resp2.is_ok() == false) {
        std::cout << "新增数据失败:" << resp2.error_message() << std::endl;
        return -1;
    }
    std::this_thread::sleep_for(std::chrono::seconds(10));
    return 0;
}

接口学习

基于上传和获取学习接口

etcd::Client

etcd::Client 是核心客户端类,用于与 etcd 服务进行交互,提供基本的键值存取和操作功能

构造函数

  • etcd_url:etcd 服务地址(例如 http://127.0.0.1:2379
  • 创建一个与 etcd 服务通信的客户端对象
etcd::Client client(const std::string &etcd_url);

ls() 方法

  • 列出路径下所有的键值
  • key:路径键,表示需要获取其子键的根路径(例如 /service
  • 返回一个 std::future<etcd::Response>,可以通过 .get() 同步获取结果
std::future<etcd::Response> ls(const std::string &key);
auto resp = client.ls("/service").get();
if (resp.is_ok()) {
    // 获取成功,遍历键值
    for (const auto &key : resp.keys()) {
        std::cout << key << ": " << resp.value(key).as_string() << std::endl;
    }
} else {
    std::cerr << "获取失败: " << resp.error_message() << std::endl;
}

etcd::Watcher

 用于监听指定路径下的键值变化事件,并触发回调函数处理这些事件

构造函数

  • clientetcd::Client 对象,用于连接 etcd 服务。
  • key:要监听的路径键(例如 /service)。
  • callback:回调函数,当监听到键值变化时触发,参数是 etcd::Response 对象。
  • recursive:是否递归监听子路径下的键值变化,默认为 false
  • 功能:创建一个监听器对象,监听制定路径下的键值变化
etcd::Watcher watcher(etcd::Client &client, const std::string &key,
                      std::function<void(const etcd::Response &)> callback,
                      bool recursive = false);

Wait() 方法

  • 阻塞当前线程,持续监听键值变化事件,直到被中断
auto watcher = etcd::Watcher(client, "/service", callback, true);
watcher.Wait(); // 阻塞线程等待事件

 etcd::Response

 etcd 请求或事件的响应对象,包含操作结果及相关数据

 is_ok() 方法

  • 判断请求是否成功:成功则返回true,失败则返回false
bool is_ok() const;

error_message() 方法

  • 获取请求或者事件失败的错误信息
std::string error_message() const;

 keys() 方法

  • 获取路径下所有键的列表
std::vector<std::string> keys() const;

value(key) 方法

  •  返回指向键的值对象
etcd::Value value(const std::string &key) const;

 events() 方法

  • 获取监听器捕获到的事件列表
std::vector<etcd::Event> events() const;

etcd::Value

etcd::Value 表示 etcd 中的键值对,包含键、值及其元数据

key() 方法

  • 获取键的名称
std::string key() const;

 as_string() 方法

  • 获取键对应的值(字符串形式)
std::string as_string() const;

etcd::Value val = resp.value(key);
std::cout << "Key: " << val.key() << ", Value: " << val.as_string() << std::endl;

二次封装 

基本思想

服务注册客户端类(Registry)

主要目标

  • 向etcd注册服务信息(通过键值对方式)并确保其活跃状态
  • 借用租约机制保证注册信息的有效性,如果租约过期,服务注册信息回自动清除

实现逻辑

  • 创建一个etcd客户端,用于与etcd进行通信
  • 创建租约(Lease),为服务注册信息绑定一个动态生存空间
  • 定期发送心跳保活(KeepLive),续约租约,确保服务信息不会被自动删除
  • 提供一个registry方法,用于注册服务信息(键值对)

服务发现客户端类

主要目标

  • 从etcd中获取指定路径下的当前服务信息
  • 通过监听机制实时监控服务信息的变化(例如服务新增、服务下线)
  • 使用回调函数处理服务变更事件

实现逻辑

  • 创建etcd客户端用于与etcd通信
  • 使用ls方法获取当前路径下所有的键值对,处理当前已有数据
  • 创建Watcher对象,监听指定路径下的数据变更事件
  • 在事件回调中,根据事件的类型调用对应的回调函数

具体实现

细节补充:Watcher中使用Bind进行回调的逻辑总结

  • Watcher 检测到事件etcd::Watcher 检测到指定路径的键值发生了变化(例如新增或删除)
  • Watcher 调用回调函数Watcher 将变化事件封装为 etcd::Response 对象,调用绑定后的函数对象,并将 etcd::Response 作为参数传递
  • 绑定的函数对象调用 callback:其中,thisDiscovery 类的实例,respetcd::Watcher 提供的事件响应
  • callback 函数执行callback 使用 resp 提供的事件信息,执行具体的业务逻辑(例如调用 _put_cb_del_cb
this->callback(resp);

etcd二次封装实现 

#pragma once
#include <etcd/Client.hpp>
#include <etcd/KeepAlive.hpp>
#include <etcd/Response.hpp>
#include <etcd/Watcher.hpp>
#include <etcd/Value.hpp>
#include <functional>
#include "logger.hpp"

namespace mag {
    // 服务注册客户端类
    class Registry 
    {
    public:
        using ptr = std::shared_ptr<Registry>;

        // 构造函数:初始化etcd客户端和租约(Lease)机制
        Registry(const std::string &host):
            _client(std::make_shared<etcd::Client>(host)), // 创建etcd客户端
            _keep_alive(_client->leasekeepalive(3).get()), // 创建一个3秒TTL的租约并保持续约
            _lease_id(_keep_alive->Lease()){} // 获取租约ID

        // 析构函数:取消租约续约,释放资源
        ~Registry() { _keep_alive->Cancel(); }

        // 注册服务信息:将键值对存入etcd并绑定租约
        bool registry(const std::string &key, const std::string &val) 
        {
            auto resp = _client->put(key, val, _lease_id).get(); // 绑定租约,写入键值对
            if (resp.is_ok() == false) 
            {
                LOG_ERROR("注册数据失败:{}", resp.error_message()); // 打印错误日志
                return false;
            }
            return true; // 返回注册结果
        }
    private:
        std::shared_ptr<etcd::Client> _client; // etcd客户端对象
        std::shared_ptr<etcd::KeepAlive> _keep_alive; // 租约续约对象
        uint64_t _lease_id; // 租约ID
    };

    // 服务发现客户端类
    class Discovery 
    {
    public:
        using ptr = std::shared_ptr<Discovery>;
        using NotifyCallback = std::function<void(std::string, std::string)>;

        // 构造函数:初始化etcd客户端,执行服务发现和事件监听
        Discovery(const std::string &host, 
            const std::string &basedir,
            const NotifyCallback &put_cb,
            const NotifyCallback &del_cb)
            :_client(std::make_shared<etcd::Client>(host)), // 创建etcd客户端
             _put_cb(put_cb), _del_cb(del_cb) // 设置回调函数
            {
            // 服务发现:获取当前路径下的已有服务数据
            auto resp = _client->ls(basedir).get(); // 列出路径下的所有键值
            if (resp.is_ok() == false) 
            {
                LOG_ERROR("获取服务信息数据失败:{}", resp.error_message()); // 打印错误日志
            }
            int sz = resp.keys().size();
            for (int i = 0; i < sz; ++i) 
            {
                if (_put_cb) _put_cb(resp.key(i), resp.value(i).as_string()); // 调用服务新增回调
            }

            // 事件监控:监听路径数据变更并调用回调处理
            _watcher = std::make_shared<etcd::Watcher>(*_client.get(), basedir,
                std::bind(&Discovery::callback, this, std::placeholders::_1), true);
        }

        // 析构函数:取消事件监听,释放资源
        ~Discovery() {
            _watcher->Cancel();
        }
    private:
        // 事件回调函数:处理服务新增和删除事件
        void callback(const etcd::Response &resp) 
        {
            if (resp.is_ok() == false) {
                LOG_ERROR("收到一个错误的事件通知: {}", resp.error_message()); // 打印错误日志
                return;
            }
            for (auto const& ev : resp.events()) 
            {
                // 新增服务事件
                if (ev.event_type() == etcd::Event::EventType::PUT) {
                    if (_put_cb) _put_cb(ev.kv().key(), ev.kv().as_string()); // 调用服务新增回调
                    LOG_DEBUG("新增服务:{}-{}", ev.kv().key(), ev.kv().as_string()); // 打印调试信息
                }
                // 服务下线事件
                else if (ev.event_type() == etcd::Event::EventType::DELETE_) {
                    if (_del_cb) _del_cb(ev.prev_kv().key(), ev.prev_kv().as_string()); // 调用服务删除回调
                    LOG_DEBUG("下线服务:{}-{}", ev.prev_kv().key(), ev.prev_kv().as_string()); // 打印调试信息
                }
            }
        }
    private:
        NotifyCallback _put_cb; // 服务新增事件回调
        NotifyCallback _del_cb; // 服务删除事件回调
        std::shared_ptr<etcd::Client> _client; // etcd客户端对象
        std::shared_ptr<etcd::Watcher> _watcher; // 事件监听器
    };
}

 测试注册和发现逻辑

#include "etcd.hpp"
#include <iostream>
#include <chrono>
#include <thread>

// 服务新增回调
void onServiceAdded(const std::string &key, const std::string &value) {
    std::cout << "发现新增服务:" << key << " -> " << value << std::endl;
}

// 服务下线回调
void onServiceRemoved(const std::string &key, const std::string &value) {
    std::cout << "服务下线:" << key << " -> " << value << std::endl;
}

int main() {
    std::string etcd_host = "http://127.0.0.1:2379";
    std::string watch_dir = "/service";

    // 创建Discovery对象
    mag::Discovery::ptr discovery = std::make_shared<mag::Discovery>(
        etcd_host, watch_dir, onServiceAdded, onServiceRemoved
    );

    // 保持监听状态
    std::cout << "开始监听服务变更,按 Ctrl+C 退出..." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(600));

    return 0;
}
#include "etcd.hpp"
#include <iostream>
#include <chrono>
#include <thread>

int main() {
    std::string etcd_host = "http://127.0.0.1:2379";
    std::string key = "/service/example";
    std::string value = "127.0.0.1:8080";

    // 创建Registry对象
    mag::Registry::ptr registry = std::make_shared<mag::Registry>(etcd_host);

    // 注册服务
    if (registry->registry(key, value)) {
        std::cout << "服务注册成功:" << key << " -> " << value << std::endl;
    } else {
        std::cerr << "服务注册失败!" << std::endl;
        return -1;
    }

    // 保持注册信息的活跃状态
    std::cout << "服务正在运行,按 Ctrl+C 退出..." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(600));

    return 0;
}

问题

编译问题慢

问题描述

 多次对该文件进行编译,编译总是卡在80%左右停止,服务器环境是2核2G的云服务器

分析

 经htop进行排查,发现部分服务占用了大量内存,从而导致编译写入和读取文件速度变慢

解决思路

停止占用内存较高的MySQL服务

sudo systemctl stop mysql

清理缓存和无用数据

sync; echo 3 | sudo tee /proc/sys/vm/drop_caches

删除临时文件进一步释放空间

sudo apt autoremove -y
sudo apt clean

使用两个线程对程序进行编译

make -j2

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

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

相关文章

滑动窗口篇——如行云流水般的高效解法与智能之道(1)

前言&#xff1a; 上篇我们介绍了双指针算法&#xff0c;并结合具体题目进行了详细的运用讲解。本篇我们将会了解滑动窗口。滑动窗口是一种常用的算法技巧&#xff0c;主要用于处理子数组、子串等具有“窗口”特性的题目。柳暗花明&#xff0c;乃巧解复杂问题的高效之道。 一. …

数据结构-树状数组专题(2)

一、前言 接上回树状数组专题&#xff08;1&#xff09;&#xff0c;这次主要介绍差分跟树状数组联动实现区间更新 二、我的模板 重新放了一遍&#xff0c;还是提一嘴&#xff0c;注意下标从0开始&#xff0c;区间左闭右开 template <typename T> struct Fenwick {in…

QA|使用 MapleSim 模拟卷料生产 (Converting)和卷对卷系统 (R2R)

使用 MapleSim 模拟卷料生产 (Converting)和卷对卷系统 (R2R) 纸张、薄膜、塑料、金属箔、新能源电池和卷料生产设备 (converting equipment) 的制造商正在转向建模和仿真&#xff0c;以提升卷料处理的设备性能和产品质量。MapleSim 卷料处理库提供了专业的建模元件以及功能&a…

2024ARM网络验证 支持一键云注入引流弹窗注册机 一键脱壳APP加固搭建程序源码及教程

此套源码功能强大&#xff0c;支持APK脱壳、注入、网络验证、注册机、引流弹窗、更新弹窗和公告等功能&#xff0c;并具有强大的系统应用管理端&#xff0c;可轻松管理用户数量和卡密状态等数据统计。armpro脱壳软件可在线修改手机文件和游戏数据&#xff0c;并可添加会员功能、…

汉诺塔(hanio)--C语言函数递归

文章目录 前言一、汉诺塔的图解二、问题分析总结 前言 什么是汉诺塔&#xff1f; 汉诺塔(Tower of Hanoi)&#xff08;也称河内塔&#xff09;是有法国数学家爱德华卢卡斯于1883年发明的一道智力题。它源于印度的一个古老传说&#xff1a;大梵天创造世界的时候做了三根钻石柱子…

【MySQL】数据库精细化讲解:内置函数知识穿透与深度学习解析

前言&#xff1a;本节内容讲述mysql里面的函数的概念&#xff0c; 在mysql当中&#xff0c; 内置了很多函数工作。 这些函数丰富了我们的操作。 比如字符串函数、数据函数以及一些其他函数等等。 ps:友友们学习了表的基本操作后就可以观看本节内容啦! 目录 日期函数 current_…

Is:cannat access /data: Input/output error

说明&#xff1a; 1&#xff09;访问应用业务&#xff0c;输入账号密码报如下图所示&#xff1a;invalid login. 2&#xff09;登录服务器查看数据日志&#xff0c;报如下图所示&#xff1a;ls:cannot access /data: Input/output error 3&#xff09;查看日志dmesg |grep erro…

Python MySQL SQLServer操作

Python MySQL SQLServer操作 Python 可以通过 pymysql 连接 MySQL&#xff0c;通过 pymssql 连接 SQL Server。以下是基础操作和代码实战示例&#xff1a; 一、操作 MySQL&#xff1a;使用 pymysql python 操作数据库流程 1. 安装库 pip install pymysql2. 连接 MySQL 示例 …

迅为RK3562开发板直连电脑配置方法(无线上网)

概述 由于环境限制&#xff0c;笔记本电脑和开发板无法通过路由器连接起来&#xff0c;所以本文的目的是要实现笔记本电脑和虚拟机能够通过 WIFI 上网&#xff0c;并且开发板通过网线连接笔记本电脑和虚拟机在同一个网段内&#xff0c;最终实现 TFTP 或 NFS 来进行开发调试。 通…

Mono Repository方案与ReactPress的PNPM实践

ReactPress Github项目地址&#xff1a;https://github.com/fecommunity/reactpress 欢迎Star。 Mono Repository方案与ReactPress的PNPM实践 在当今软件开发领域&#xff0c;Mono Repository&#xff08;简称Monorepo&#xff09;已成为一种流行的代码管理方式&#xff0c;特…

timedatectl命令修改时间和时区

1.默认情况下&#xff0c;Linux系统通常每64分钟进行一次NTP时间同步。但是&#xff0c;这可以通过编辑/etc/ntp.conf文件来修改。在/etc/ntp.conf中设置minpoll和maxpoll参数。 timedatectl可以用来查询和更改系统时间设定&#xff0c;同时可以设定和修改时区信息。 一、查…

基于Opencv的图像处理软件

目录 一、背景及意义介绍背景意义 二、概述一、背景及意义介绍背景意义 三、论文思路解决问题 四、复现过程&#xff08;一&#xff09;图像处理模块二&#xff09;图形界面模块&#xff08;一&#xff09;图像处理模块实现步骤&#xff08;二&#xff09;图形界面模块实现步骤…

HTML的自动定义倒计时,这个配色存一下

<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>自定义倒计时</title><style>* {mar…

私有化部署视频平台EasyCVR宇视设备视频平台如何构建视频联网平台及升级视频转码业务?

在当今数字化、网络化的时代背景下&#xff0c;视频监控技术已广泛应用于各行各业&#xff0c;成为保障安全、提升效率的重要工具。然而&#xff0c;面对复杂多变的监控需求和跨区域、网络化的管理挑战&#xff0c;传统的视频监控解决方案往往显得力不从心。 EasyCVR视频融合云…

MacOS通过X11转发远程运行virt-manager进行虚机分配

今天需要通过本地macbook机器连接远程物理机&#xff0c;执行虚机分配&#xff0c;现有文档仅提供window环境安装&#xff0c;如下整理Mac环境下的安装步骤 操作篇 前提条件 支持x11转发的terminal&#xff0c;我本地使用iTerm2&#xff1b;本地安装XQuartz&#xff0c;作为…

【每天学点AI】实战图像增强技术在人工智能图像处理中的应用

图像增强&#xff08;Image Enhancement&#xff09;是人工智能和计算机视觉中一项重要的技术&#xff0c;也是人工智能数据集预处理的一个重要步骤。它旨在提高图像的质量&#xff0c;使其在视觉上更加清晰、细节更丰富。这项技术在自动驾驶、医疗诊断、安防监控等领域有着广泛…

hbase mongodb hive starrocks比较

本文是在学习大数据的几个数据存储系统相关的组件所记录下来的&#xff0c;主要是不同组件的基础概念初步了解与对比。 NoSql 在大数据时代&#xff0c;虽然RDBMS很优秀&#xff0c;但是面对快速增长的数据规模和日渐复杂的数据模型&#xff0c;RDBMS渐渐力不从心&#xff0c…

STM32端口模拟编码器输入

文章目录 前言一、正交编码器是什么&#xff1f;二、使用步骤2.1开启时钟2.2配置编码器引脚 TIM3 CH1(PA6) CH2 (PA7)上拉输入2.3.初始化编码器时基2.4 初始化编码器输入2.5 配置编码器接口2.6 开启定时器2.7获取编码器数据 三、参考程序四、测试结果4.1测试方法4.2串口输出结果…

wireshark使用lua解析自定义协议

wireshark解析自定义协议 1.自定义的lua放入路径2.修改init.lua2.1 开启lua2.2 init.lua文件最后加入自己的lua文件位置&#xff0c;这里需要确保与自己的文件名相同 3.编写lua4.编写c抓包5.wireshark添加自定义协议如何加调试信息 1.自定义的lua放入路径 一般是自己软件的安装…

基于docker进行任意项目灵活发布

引言 不管是java还是python程序等&#xff0c;使用docker发布的优势有以下几点&#xff1a; 易于维护。直接docker命令进行管理&#xff0c;如docker stop、docker start等&#xff0c;快速方便无需各种进程查询关闭。环境隔离。项目代码任何依赖或设置都可以基本独立&#x…