探索brpc:特性、使用场景与同步异步调用与封装示例

文章目录

  • 前言
    • 特性
    • 使用场景
    • brpc && grpc 对比
  • 相关类与接口
    • 日志输出类与接口
    • protobuf类与接口
    • 服务端类与接口
    • 客户端类与接口
  • 使用
    • 同步调用 & 异步调用
  • 封装
    • 封装思想
    • 代码

前言

brpc 是用 c++语言编写的工业级 RPC 框架,常用于搜索、存储、机器学习、广告、推荐等高性能系统

RPC(Remote Procedure Call,远程过程调用)框架指用于在网络中实现进程间通信的技术,使得程序能够调用远程计算机上的程序或服务,就像调用本地程序一样。


特性

特性描述
高性能针对高并发场景优化,支持异步 IO,低延迟和高吞吐量。
多种传输协议支持 HTTP/2、gRPC、TCP 等多种协议,灵活选择适合的方案。
流式支持支持单向和双向流式 RPC 调用,适合实时数据传输场景。
负载均衡内置多种负载均衡策略,支持客户端和服务端负载均衡。
服务发现提供内置服务发现机制,支持与外部工具(如 Etcd、Consul)集成。
灵活序列化方式默认使用 Protobuf,还支持 JSON 等其他序列化格式。
易用性提供简单易懂的 API,快速上手和实现服务。
扩展性支持插件机制,可以根据需求扩展功能。

使用场景

  1. 微服务架构:作为微服务间通信的基础框架,支持快速构建和部署微服务应用。
  2. 高并发处理:适用于需要处理大量并发请求的应用,如在线支付、即时通讯等。
  3. 实时数据传输:支持实时数据流的场景,例如视频直播、在线游戏等。
  4. 大规模分布式系统:适合于构建大规模的分布式系统,支持动态扩展和负载均衡。
  5. 跨语言服务调用:支持多种编程语言的服务调用。

brpc && grpc 对比

下面是 gRPC 与 brpc 的对比表格,涵盖了 主要特性和优缺点:

特性gRPCbrpc
语言支持多种语言(C++, Java, Python, Go等)多种语言(C++, Python, Java等)
传输协议HTTP/2自定义协议,支持多种传输方式
序列化方式ProtobufProtobuf、JSON等
性能高性能,适合微服务架构较高性能,优化了并发处理
流式支持支持单向和双向流式支持双向流式
负载均衡需要外部支持或使用 gRPC 的内置功能内置支持多种负载均衡策略
服务发现依赖外部服务发现(如 Consul)内置服务发现机制
生态系统强大的生态系统,广泛应用相对较小但在特定场景下表现出色
社区活跃度活跃,广泛使用较小,但有特定用户群体
文档与支持丰富的文档和社区支持文档相对较少,社区较小

总结

  • gRPC 适合需要跨语言和高性能通信的微服务架构,具有强大的生态系统和社区支持。
  • brpc 更加专注于高性能的 RPC 通信,并且在某些场景下具有更好的灵活性和效率,适合特定需求的使用者。

相关类与接口

日志输出类与接口

日志输出类 包含头文件: #include <butil/logging.h>
在编写项目时,日志输出完全根据个人以及项目需求以使用不同的日志输出类,这里介绍如何关闭brpc自带的日志输出类:

namespace logging {// 日志输出目标枚举enum LoggingDestination {LOG_TO_NONE = 0  // 不输出日志};// 日志设置结构体struct BUTIL_EXPORT LoggingSettings {// 构造函数,初始化日志设置LoggingSettings();// 日志输出目标,决定日志将被发送到何处LoggingDestination logging_dest;};// 初始化日志系统// 参数://   settings - 包含日志设置的 LoggingSettings 对象// 返回://   bool - 初始化是否成功bool InitLogging(const LoggingSettings& settings);
}// 0. 关闭 brpc 默认日志输出
logging::LoggingSettings settings;  // 创建一个 LoggingSettings 对象
settings.logging_dest = logging::LoggingDestination::LOG_TO_NONE;  // 设置日志输出为不输出
logging::InitLogging(settings);  // 初始化日志系统,应用上述设置

protobuf类与接口

namespace google
{namespace protobuf{// Closure 类用于定义可调用对象的接口class PROTOBUF_EXPORT Closure{public:// 默认构造函数Closure() {}// 虚析构函数,用于确保派生类被正确析构virtual ~Closure();// 纯虚函数,派生类需要实现该函数以定义具体的操作virtual void Run() = 0;};// 创建一个新的回调对象// 参数://   function - 指向要调用的无参数函数的指针// 返回://   Closure* - 指向新创建的 Closure 对象的指针inline Closure *NewCallback(void (*function)());// RpcController 类用于控制 RPC 调用的状态class PROTOBUF_EXPORT RpcController{public:// 检查 RPC 调用是否失败// 返回://   bool - 如果调用失败则返回 true,否则返回 falsebool Failed();// 获取错误信息文本// 返回://   std::string - 表示错误的文本信息std::string ErrorText();};}
}

服务端类与接口

namespace brpc
{// ServerOptions 结构用于配置服务器选项struct ServerOptions{// 空闲超时时间,超过该时间后关闭连接int idle_timeout_sec; // 默认值: -1(禁用)// 服务器线程数量,默认值为 CPU 核心数int num_threads;      // 默认值: #cpu-cores// 其他可能的选项...};// ServiceOwnership 枚举定义服务的所有权管理方式enum ServiceOwnership {// 当添加服务失败时,服务器负责删除服务对象SERVER_OWNS_SERVICE,// 当添加服务失败时,服务器不会删除服务对象SERVER_DOESNT_OWN_SERVICE};// Server 类表示一个 BRPC 服务器class Server{public:// 添加服务到服务器// 参数://   service - 要添加的服务对象//   ownership - 服务的所有权类型// 返回://   int - 返回操作结果的状态码int AddService(google::protobuf::Service *service, ServiceOwnership ownership);// 启动服务器// 参数://   port - 监听的端口号//   opt - 服务器选项// 返回://   int - 返回启动状态int Start(int port, const ServerOptions *opt);// 停止服务器// 参数://   closewait_ms - 等待关闭的时间(不再使用)// 返回://   int - 返回停止状态int Stop(int closewait_ms /*not used anymore*/);// 等待服务器完成所有任务并退出// 返回://   int - 返回加入状态int Join();// 运行服务器,直到收到退出请求void RunUntilAskedToQuit();};// ClosureGuard 类用于确保在作用域结束时执行回调class ClosureGuard{public:explicit ClosureGuard(google::protobuf::Closure *done): _done(done) {} // 初始化回调指针~ClosureGuard(){if (_done)_done->Run(); // 在析构时调用回调}private:google::protobuf::Closure *_done; // 存储回调指针};// HttpHeader 类表示 HTTP 请求或响应的头部信息class HttpHeader{public:// 设置内容类型void set_content_type(const std::string &type);// 获取指定键的头部值const std::string *GetHeader(const std::string &key);// 设置指定键的头部值void SetHeader(const std::string &key, const std::string &value);// 获取 URIconst URI &uri() const { return _uri; }// 获取 HTTP 方法HttpMethod method() const { return _method; }// 设置 HTTP 方法void set_method(const HttpMethod method);// 获取状态码int status_code();// 设置状态码void set_status_code(int status_code);private:URI _uri;          // 存储 URI 信息HttpMethod _method; // 存储 HTTP 方法// 其他可能的成员...};// Controller 类用于管理 RPC 调用class Controller : public google::protobuf::RpcController{public:// 设置超时时间void set_timeout_ms(int64_t timeout_ms);// 设置最大重试次数void set_max_retry(int max_retry);// 获取响应消息google::protobuf::Message *response();// 获取 HTTP 响应头HttpHeader &http_response();// 获取 HTTP 请求头HttpHeader &http_request();// 检查 RPC 调用是否失败bool Failed();// 获取错误文本std::string ErrorText();// 定义 RPC 响应后的回调函数类型using AfterRpcRespFnType = std::function<void(Controller *cntl,const google::protobuf::Message *req,const google::protobuf::Message *res)>; // 设置 RPC 响应后的回调函数void set_after_rpc_resp_fn(AfterRpcRespFnType &&fn);private:// 其他成员...};
}

客户端类与接口

namespace brpc
{// ChannelOptions 结构用于配置通道选项struct ChannelOptions{// 请求连接超时时间,单位为毫秒int32_t connect_timeout_ms; // 默认值: 200 毫秒// RPC 请求超时时间,单位为毫秒int32_t timeout_ms;          // 默认值: 500 毫秒// 最大重试次数int max_retry;               // 默认值: 3// 序列化协议类型AdaptiveProtocolType protocol; // 默认值: "baidu_std"// 其他可能的选项...};// Channel 类表示一个 RPC 通道,用于和服务器进行通信class Channel : public ChannelBase{public:// 初始化接口// 参数://   server_addr_and_port - 服务器地址和端口//   options - 指向 ChannelOptions 的指针,用于配置通道// 返回://   int - 成功返回 0,失败返回错误码int Init(const char *server_addr_and_port,const ChannelOptions *options);};// 其他相关类和功能...
}

使用

同步调用 & 异步调用

同步调用是指在程序中,当一个函数被调用时,调用者会等待被调用的函数执行完毕并返回结果后,才会继续执行后面的代码。

放在brpc中,同步调用是指客户端在发送请求后,会以阻塞的方式等待服务端的响应;

首先编写proto文件:

syntax = "proto3";package emp;option cc_generic_services = true; // 生成通用服务代码 (用于rpc)message EchoRequest {string message = 1;
}message EchoResponse {string message = 1;
}service EchoService {rpc Echo(EchoRequest) returns (EchoResponse);
}

编辑后开始写server代码:

#include <butil/logging.h>
#include <brpc/server.h>
#include "main.pb.h"class EchoServiceT : public emp::EchoService {
public:EchoServiceT() {}~EchoServiceT() {}// 重写父类回声函数void Echo(google::protobuf::RpcController* controller,const ::emp::EchoRequest* request,::emp::EchoResponse* response,::google::protobuf::Closure* done){brpc::ClosureGuard done_guard(done); // 自动调用done->Run()std::cout << "接收到消息: " << request->message() << std::endl;std::string msg = "响应消息: " + request->message();response->set_message(msg);}
};int main(int argc, char* argv[]) {// 0. 关闭brpc默认日志输出logging::LoggingSettings settings;settings.logging_dest = logging::LoggingDestination::LOG_TO_NONE;logging::InitLogging(settings);// 1. 初始化服务器对象brpc::Server server;// 2. 注册服务EchoServiceT echo_service;// SERVER_OWNS_SERVICE 服务器负责管理销毁 该服务// SERVER_DOESNT_OWN_SERVICE 服务器不负责该服务的生命周期auto ret = server.AddService(&echo_service, brpc::ServiceOwnership::SERVER_OWNS_SERVICE);if (ret != 0) {std::cout << "添加RPC服务失败。" << std::endl;return -1;}// 3. 启动服务器brpc::ServerOptions options;options.idle_timeout_sec = -1; // 设置超时时间 为-1,表示不超时options.num_threads = 1; // 设置线程数ret = server.Start(8080, &options);if (ret != 0) {std::cout << "启动服务器失败。" << std::endl;return -1;}server.RunUntilAskedToQuit(); // 阻塞等待直到收到退出信号return 0;
}

客户端代码:

#include <brpc/channel.h>
#include <thread>
#include <iostream>
#include "main.pb.h"#define SYNC 0// 异步回调函数
void callback(brpc::Controller *cntl, emp::EchoResponse *resp) {std::unique_ptr<brpc::Controller> cntl_guard(cntl);std::unique_ptr<emp::EchoResponse> resp_guard(resp);if (cntl->Failed()) {std::cout << "RPC调用失败: " << cntl->ErrorText() << std::endl;return;}std::cout << "收到响应: " << resp->message() << std::endl;
}int main(int argc, char* argv[]) {// 1. 创建信道 连接服务器brpc::ChannelOptions options;options.protocol = "baidu_std"; // 序列化协议 默认options.connect_timeout_ms = -1; // 连接超时时间 -1表示永不超时options.timeout_ms = -1; // 超时时间 -1表示永不超时options.max_retry = 3; // 最大重试次数brpc::Channel channel;if (channel.Init("127.0.0.1:8080", &options) != 0) {std::cout << "初始化信道失败" << std::endl;return -1;}// 2. 构造EchoService_Stub对象(用于RPC调用)emp::EchoService_Stub stub(&channel);// 3. 进行RPC调用emp::EchoRequest req;std::cout << "请输入消息: ";std::string msg;getline(std::cin, msg);req.set_message(msg);// 4. 构造Controller对象(用于控制RPC调用)brpc::Controller *cntl = new brpc::Controller();emp::EchoResponse *resp = new emp::EchoResponse();#if SYNC // 同步调用stub.Echo(cntl, &req, resp, nullptr); // google::protobuf::Closure *done: 传入nullptr代表同步调用if(cntl->Failed()) {std::cout << "RPC调用失败: " << cntl->ErrorText() << std::endl;return -1;}std::cout << "RPC调用成功, 响应信息: " << resp->message() << std::endl;delete cntl;delete resp;#else // 异步调用auto clusure = google::protobuf::NewCallback(callback, cntl, resp);stub.Echo(cntl, &req, resp, clusure);std::cout << "异步调用成功" << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));#endifreturn 0;
}

在这里插入图片描述


封装

封装思想

由于不同的服务调用使用不同的 Stub,其封装意义不大。因此,我们主要封装通信所需的 Channel。当需要进行服务调用时,只需通过服务名称获取对应的 Channel,然后实例化 Stub 进行调用即可。

设计概要

  • Channel 管理类

    • 每个服务可能有多个主机提供服务,因此一个服务可能对应多个 Channel。需要管理这些 Channel,并提供获取指定服务 Channel 的接口。
    • 在进行 RPC 调用时,根据 Round Robin(RR)轮转策略选择 Channel。
  • 服务声明接口

    • 整体项目中通常会提供多个服务,当前可能并不需要用到所有服务。因此,通过声明来告知模块当前关心的服务,并建立连接进行管理。未声明的服务即使上线也不需要进行连接的建立。
  • 服务上线处理接口

    • 提供新增指定服务的 Channel 的接口,以便在服务上线时进行管理。
  • 服务下线处理接口

    • 提供删除指定服务下的 Channel 的接口,以便在服务下线时进行管理。

代码

class ServiceChannel 
{
public:using ptr = std::shared_ptr<ServiceChannel>;using ChannelPtr = std::shared_ptr<brpc::Channel>;ServiceChannel(const std::string& service_name) : _index(0), _service_name(service_name) {}void append(const std::string& host){// 创建信道auto channel = std::make_shared<brpc::Channel>();brpc::ChannelOptions options;options.protocol = "baidu_std";options.timeout_ms = -1;options.connect_timeout_ms = -1;options.max_retry = 3;int ret = channel->Init(host.c_str(), &options);if (ret != 0){LOG_ERROR("初始化{}-{}信道失败", _service_name, host);return;}std::unique_lock<std::mutex> lock(_mutex);_hosts.insert({host, channel});_channels.push_back(channel);} /* 服务上线一个节点 - 调用append新增信道 */void remove(const std::string& host){std::unique_lock<std::mutex> lock(_mutex);auto it = _hosts.find(host);if(it == _hosts.end()) {LOG_WARN("{}-{}删除时,未找到信道信息", _service_name, host);return;}for(auto vit = _channels.begin(); vit != _channels.end(); ++vit) {if(*vit == it->second) {_channels.erase(vit);break;}}_hosts.erase(it);LOG_INFO("{}-{}删除成功", _service_name, host);} /* 服务下线一个节点 - 调用remove释放信道 */ChannelPtr getChannel() {std::unique_lock<std::mutex> lock(_mutex);if(_channels.empty()) {LOG_ERROR("当前没有能提供{}服务的节点", _service_name);return ChannelPtr();}int32_t idx = _index++ % _channels.size(); // 轮转索引return _channels[idx];}private:std::mutex _mutex; // 互斥锁int32_t _index; // 轮转索引std::string _service_name; // 服务名称std::vector<ChannelPtr> _channels; // 服务对应的信道集合std::unordered_map<std::string, ChannelPtr> _hosts; // // 主机地址到信道映射
};class ServiceManager
{
public:using ptr = std::shared_ptr<ServiceManager>;ServiceManager() {} /* 获取指定服务的信道节点 */ServiceChannel::ChannelPtr getChannel(const std::string& service_name) {std::unique_lock<std::mutex> lock(_mutex);auto it = _services.find(service_name);if(it == _services.end()) {LOG_ERROR("当前没有能提供{}服务的节点", service_name);return ServiceChannel::ChannelPtr();}return it->second->getChannel();}/* 声明关注的服务 */void declareTrackService(const std::string& service_name) {std::unique_lock<std::mutex> lock(_mutex);_track_services.insert(service_name);}/* 服务上线回调 */void onServiceOnline(const std::string& service_instance, const std::string& host) {std::string service_name = getServiceName(service_instance);ServiceChannel::ptr service;{std::unique_lock<std::mutex> lock(_mutex);auto tit = _track_services.find(service_name);if(tit == _track_services.end()) {LOG_DEBUG("{}-{}服务上线了 (不在关注列表中)", service_name, host);return;}auto sit = _services.find(service_name);if(sit == _services.end()) {service = std::make_shared<ServiceChannel>(service_name);_services.insert({service_name, service});} else {service = sit->second;}}if(!service) {LOG_ERROR("{}服务新增失败", service_name);return;}service->append(host);LOG_DEBUG("{}服务新增成功", service_name);}/* 服务下线回调 */void onServiceOffline(const std::string& service_instance, const std::string& host) {std::string service_name = getServiceName(service_instance);ServiceChannel::ptr service;{std::unique_lock<std::mutex> lock(_mutex);auto tit = _track_services.find(service_name);if(tit == _track_services.end()) {LOG_DEBUG("{}-{}服务下线了 (不在关注列表中)", service_name, host);return;}auto sit = _services.find(service_name);if(sit == _services.end()) {LOG_WARN("删除{}服务时,未找到管理对象", service_name);return;}service = sit->second;}service->remove(host);LOG_DEBUG("{}服务删除成功", service_name);}
private:std::string getServiceName(const std::string& service_instance) {auto pos = service_instance.find_last_of('/');if(pos == std::string::npos) {return service_instance;}return service_instance.substr(0, pos);}private:std::mutex _mutex;std::unordered_set<std::string> _track_services; // 跟踪的服务集合std::unordered_map<std::string, ServiceChannel::ptr> _services; // 服务名称到信道集合的映射
};

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ansible自动化工具

一、Ansible概述 1.1 什么是Ansible Ansible 是一个开源的自动化工具&#xff0c;用于配置管理、应用程序部署和任务自动化。它让你可以通过编写简单的 YAML 文件&#xff08;剧本&#xff0c;Playbooks&#xff09;&#xff0c;轻松管理和配置多个服务器。Ansible 的特点是无…

4.redis通用命令

文章目录 1.使用官网文档2.redis通用命令2.1set2.2get2.3.redis全局命令2.3.1 keys 2.4 exists2.5 del(delete)2.6 expire - (失效时间)2.7 ttl - 过期时间2.7.1 redis中key的过期策略2.7.2redis定时器的实现原理 2.8 type2.9 object 3.生产环境4.常用的数据结构4.1认识数据类型…

【C++进阶】哈希表的介绍及实现

【C进阶】哈希表的介绍及实现 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;C&#x1f96d; &#x1f33c;文章目录&#x1f33c; 1. 哈希的概念 1.1 直接定址法 1.2 哈希冲突 1.3 负载因子 1.4 将关键字转为整数 2. 哈希函数 2.1 …

mqtt学习

简介&#xff1a; MQTT(消息队列遥测传输)是ISO 标准(ISO/IEC PRF 20922)下基于发布/订阅模式的消息协议。它工作在 TCP/IP协议族上&#xff0c;是为硬件性能低下的远程设备以及网络状况糟糕的情况下而设计的发布/订阅型消息协议&#xff0c;为此&#xff0c;它需要一个消息中…

Android 未来可能支持 Linux 应用,Linux 终端可能登陆 Android 平台

近日&#xff0c;根据 android authority 的消息&#xff0c;Google 正在开发适用于 Android 的 Linux 终端应用&#xff0c;而终端应用可以通过开发人员选项启用&#xff0c;并将 Debian 安装在虚拟机中。 在几周前&#xff0c;Google 的工程师开始为 Android 开发新的 Termi…

推荐一个可以免费上传PDF产品图册的网站

​在数字化时代&#xff0c;企业将产品图册以PDF格式上传至网络&#xff0c;不仅便于客户浏览和下载&#xff0c;还能提升企业的专业形象。今天&#xff0c;就为您推荐一个可以免费上传PDF产品图册的网站——FLBOOK&#xff0c;轻松实现产品图册的在线展示。 1.注册登录&#x…

JAVA就业笔记7——第二阶段(4)

课程须知 A类知识&#xff1a;工作和面试常用&#xff0c;代码必须要手敲&#xff0c;需要掌握。 B类知识&#xff1a;面试会问道&#xff0c;工作不常用&#xff0c;代码不需要手敲&#xff0c;理解能正确表达即可。 C类知识&#xff1a;工作和面试不常用&#xff0c;代码不…

Mysql常用sql语句与刷题知识点

目录 1. 常用sql2. 刷题知识点 1. 常用sql #查询MySQL中所有的数据库 SHOW DATABASES; #查询当前正在使用的数据库 SELECT DATABASE();#普通创建&#xff08;创建已经存在的数据库会报错&#xff09; CREATE DATABASE 数据库名称; #创建并判断&#xff08;该数据库不存在才创建…

终端威胁检测与响应 (EDR) 技术研究

终端安全面临的挑战 从安全日常管理实践出发&#xff0c;终端安全的常见风险点是钓鱼攻击。因终端业务场景复杂&#xff0c;涉及即时通信软件、邮件等方式&#xff0c;如设置较严苛的拦截规则&#xff0c;则会造成较大的业务影响&#xff0c;且部分钓鱼通道为加密通道&#xf…

C_数据结构(栈) —— 栈的初始化、栈的销毁、入栈、出栈、bool类型判断栈是否为空、取栈顶元素、获取栈中有效元素个数

目录 一、栈 1、概念与结构 二、栈的实现 1、定义栈的结构 2、栈的初始化 3、栈的销毁 4、入栈 5、出栈 6、bool类型判断栈是否为空 7、取栈顶元素 8、获取栈中有效元素个数 三、完整实现栈的三个文件 Stack.h Stack.c test.c 一、栈 1、概念与结构 栈&#x…

K8s环境下使用sidecar模式对EMQX的exhook.proto 进行流量代理

背景 在使用emqx作为mqtt时需要我们需要拦截client的各种行为&#xff0c;如连接&#xff0c;发送消息&#xff0c;认证等。除了使用emqx自带的插件机制。我们也可以用多语言-钩子扩展来实现这个功能&#xff0c;但是目前emqx仅仅支持单个grpc服务端的设置&#xff0c;所以会有…

论文阅读-U3M(2)

HOW MUCH POSITION INFORMATION DO CONVOLUTIONAL NEURAL NETWORKS ENCODE? 文章目录 HOW MUCH POSITION INFORMATION DO CONVOLUTIONAL NEURAL NETWORKS ENCODE?前言一、位置编码网络&#xff08;PosENet&#xff09;二、训练数据三、实验3.1 位置信息的存在性3.2 分析PosEN…

多机编队—(3)Fast_planner无人机模型替换为Turtlebot3模型实现无地图的轨迹规划

文章目录 前言一、模型替换二、Riz可视化三、坐标变换四、轨迹规划最后 前言 前段时间已经成功将Fast_planner配置到ubuntu机器人中&#xff0c;这段时间将Fast_planner中的无人机模型替换为了Turtlebot3_waffle模型&#xff0c;机器人识别到环境中的三维障碍物信息&#xff0…

X(twitter)推特的广告类型有哪些?怎么选择?

X&#xff08;twitter&#xff09;推特是全球最热门的几大社交媒体平台之一&#xff0c;也是很多电商卖家进行宣传推广工作的阵地之一。在营销过程中不可避免地需要借助平台广告&#xff0c;因此了解其广告类型和适配场景也十分重要。 一、广告类型及选择 1.轮播广告 可滑动的…

谷歌浏览器办公必备扩展推荐有哪些

在现代办公环境中&#xff0c;谷歌浏览器凭借其强大的功能和丰富的扩展生态&#xff0c;成为了许多人日常工作中不可或缺的工具。为了进一步提升办公效率&#xff0c;本文将为您推荐几款实用的谷歌浏览器扩展&#xff0c;并解答在使用过程中可能遇到的一些常见问题。&#xff0…

基于SpringBoot+Vue+Uniapp家具购物小程序的设计与实现

详细视频演示 请联系我获取更详细的演示视频 项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而…

【原创】java+springboot+mysql在线课程学习网设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…

【xilinx-versal】【Petalinux】添加TMP75温度传感器Linux驱动

Xilinx versal添加TMP75温度传感器Linux驱动 I2C总线的内核配置打开Cadence I2C 控制器配置xilinx I2C配置(不使用)添加设备树总结I2C总线的内核配置 TMP75挂载第一个i2c总线上,地址是0x48。 petalinux-config -c kernel打开内核配置界面。 打开Cadence I2C 控制器配置 │…

MySQL中常见函数

1&#xff0c;日期类函数 1&#xff0c;获取年月日 关键字&#xff1a;current_date(); 2,获取时间 关键字&#xff1a;current_time(); 3,获取时间戳 关键字&#xff1a;current_timestamp(); 注意&#xff0c;MySQL的时间戳显示是以时间的方式显示&#xff0c;所以可以看…

调查显示软件供应链攻击增加

OpenText 发布了《2024 年全球勒索软件调查》&#xff0c;强调了网络攻击的重要趋势&#xff0c;特别是在软件供应链中&#xff0c;以及生成式人工智能在网络钓鱼诈骗中的使用日益增多。 尽管各国政府努力加强网络安全措施&#xff0c;但调查显示&#xff0c;仍有相当一部分企…