[C++][第三方库][RabbitMq]详细讲解

目录

  • 1.介绍
  • 2.安装
    • 1.RabbitMq
    • 2.客户端库
  • 3.AMQP-CPP 简单使用
    • 1.介绍
    • 2.使用
  • 4.类与接口
    • 1.Channel
    • 2.ev
  • 5.使用
    • 1.publish.cc
    • 2.consume.cc
    • 3.makefile


1.介绍

  • RabbitMQ消息队列组件,实现两个客户端主机之间消息传输的功能(发布&订阅)
  • 核心概念:交换机、队列、绑定、消息
  • 交换机类型
    • 广播交换:当交换机收到消息,则将消息发布到所有绑定的队列中
    • 直接交换:根据消息中的bkey与绑定的rkey对比,一致则放入队列
    • 主题交换:使用bkey与绑定的rkey进行规则匹配,成功则放入队列

2.安装

1.RabbitMq

  • 安装sudo apt install rabbitmq-server
  • 简单使用
    # 安装完成的时候默认有个用户guest,但是权限不够,要创建一个administrator用户,才可以做为远程登录和发表订阅消息#添加用户 
    sudo rabbitmqctl add_user root <PASSWORD>#设置用户tag 
    sudo rabbitmqctl set_user_tags root administrator #设置用户权限 
    sudo rabbitmqctl set_permissions -p / root "." "." ".*" # RabbitMQ自带了web管理界面, 执行下面命令开启, 默认端口15672
    sudo rabbitmq-plugins enable rabbitmq_management 
    

2.客户端库

  • C语言库
  • C++库
    sudo apt install libev-dev #libev 网络库组件
    git clone https://github.com/CopernicaMarketingSoftware/AMQP-CPP.git
    cd AMQP-CPP/
    make
    make install
    
  • 如果安装时出现以下报错,则表示ssl版本出现问题
    /usr/include/openssl/macros.h:147:4: error: #error 
    "OPENSSL_API_COMPAT expresses an impossible API compatibility 
    level" 147 | #  error "OPENSSL_API_COMPAT expresses an impossible API 
    compatibility level" |    ^~~~~ 
    In file included from /usr/include/openssl/ssl.h:18, from linux_tcp/openssl.h:20, from linux_tcp/openssl.cpp:12: 
    /usr/include/openssl/bio.h:687:1: error: expected constructor, 
    destructor, or type conversion before ‘DEPRECATEDIN_1_1_0’ 687 | DEPRECATEDIN_1_1_0(int BIO_get_port(const char *str, 
    unsigned short *port_ptr))
    
  • 解决方案:卸载当前的ssl库,重新进行修复安装
    dpkg -l | grep ssl
    sudo dpkg -P --force-all libevent-openssl-2.1-7
    sudo dpkg -P --force-all openssl
    sudo dpkg -P --force-all libssl-dev
    sudo apt --fix-broken install
    

3.AMQP-CPP 简单使用

1.介绍

  • AMQP-CPP是用于与RabbitMq消息中间件通信的C++库
    • 它能解析从RabbitMq服务发送来的数据,也可以生成发向RabbitMq的数据包
    • AMQP-CPP库不会向RabbitMq建立网络连接,所有的网络IO由用户完成
  • AMQP-CPP提供了可选的网络层接口,它预定义了TCP模块,用户就不用自己实现网络IO,
    • 也可以选择libevent、libev、libuv、asio等异步通信组件, 需要手动安装对应的组件
  • AMQP-CPP完全异步,没有阻塞式的系统调用,不使用线程就能够应用在高性能应用中
  • 注意:它需要C++17的支持

2.使用

  • AMQP-CPP的使用有两种模式:
    • 使用默认的TCP模块进行网络通信
    • 使用扩展的libevent、libev、libuv、asio异步通信组件进行通信
  • 此处以libev为例,不需要自己实现monitor函数,可以直接使用AMQP::LibEvHandler

4.类与接口

1.Channel

  • channel是一个虚拟连接,一个连接上可以建立多个通道
    • 并且所有的RabbitMq指令都是通过channel传输
      • 所以连接建立后的第一步,就是建立channel
    • 因为所有操作是异步的,所以在channel上执行指令的返回值并不能作为操作执行结果
      • 实际上它返回的是Deferred类,可以使用它安装处理函数
namespace AMQP 
{ /** *  Generic callbacks that are used by many deferred objects */ using SuccessCallback = std::function<void()>; using ErrorCallback = std::function<void(const char *message)>; using FinalizeCallback = std::function<void()>;/** *  Declaring and deleting a queue */ using QueueCallback = std::function<void(const std::string &name, uint32_t messagecount, uint32_t consumercount)>;using DeleteCallback = std::function<void(uint32_t deletedmessages)>; using MessageCallback = std::function<void(const Message &message, uint64_t deliveryTag, bool redelivered)>; // 当使用发布者确认时,当服务器确认消息已被接收和处理时,将调用AckCallback using AckCallback = std::function<void(uint64_t deliveryTag, bool multiple)>;// 使用确认包裹通道时,当消息被ack/nacked时,会调用这些回调 using PublishAckCallback = std::function<void()>; using PublishNackCallback = std::function<void()>; using PublishLostCallback = std::function<void()>; // 信道类class Channel { Channel(Connection *connection); bool connected();/** *声明交换机 *如果提供了一个空名称,则服务器将分配一个名称。 *以下flags可用于交换机: * *-durable     持久化,重启后交换机依然有效 *-autodelete  删除所有连接的队列后,自动删除交换 *-passive     仅被动检查交换机是否存在 *-internal    创建内部交换 * *@param name    交换机的名称 *@param-type    交换类型 enum ExchangeType { fanout,  广播交换,绑定的队列都能拿到消息 direct,  直接交换,只将消息交给routingkey一致的队列 topic,   主题交换,将消息交给符合bindingkey规则的队列 headers, consistent_hash, message_deduplication }; *@param flags    交换机标志 *@param arguments其他参数 * *此函数返回一个延迟处理程序。可以安装回调 using onSuccess(), onError() and onFinalize() methods. */ Deferred &declareExchange(const std::string_view &name,ExchangeType type,int flags,const Table &arguments);/** *声明队列 *如果不提供名称,服务器将分配一个名称。 *flags可以是以下值的组合: * *-durable 持久队列在代理重新启动后仍然有效 *-autodelete 当所有连接的使用者都离开时,自动删除队列 *-passive 仅被动检查队列是否存在 *-exclusive 队列仅存在于此连接,并且在连接断开时自动删除 * *@param name        队列的名称 *@param flags       标志组合 *@param arguments  可选参数 * *此函数返回一个延迟处理程序。可以安装回调 *使用onSuccess()、onError()和onFinalize()方法。 * Deferred &onError(const char *message) * *可以安装的onSuccess()回调应该具有以下签名: void myCallback(const std::string &name,  uint32_t messageCount,  uint32_t consumerCount); 例如: channel.declareQueue("myqueue").onSuccess( [](const std::string &name,  uint32_t messageCount, uint32_t consumerCount) { std::cout << "Queue '" << name << "' "; std::cout << "has been declared with "; std::cout << messageCount; std::cout << " messages and "; std::cout << consumerCount; std::cout << " consumers" << std::endl; *  }); */ DeferredQueue &declareQueue(const std::string_view &name,int flags,const Table &arguments);/** *将队列绑定到交换机 * *@param exchange     源交换机 *@param queue        目标队列 *@param routingkey   路由密钥 *@param arguments    其他绑定参数 * *此函数返回一个延迟处理程序。可以安装回调 *使用onSuccess()、onError()和onFinalize()方法。 */ Deferred &bindQueue(const std::string_view &exchange,const std::string_view &queue,const std::string_view &routingkey,const Table &arguments);/** *将消息发布到exchange*您必须提供交换机的名称和路由密钥。 然后,RabbitMQ将尝试将消息发送到一个或多个队列。 使用可选的flags参数,可以指定如果消息无法路由到队列时应该发生的情况。默认情况下,不可更改的消息将被静默地丢弃。 *  *如果设置了'mandatory'或'immediate'标志, 则无法处理的消息将返回到应用程序。 在开始发布之前,请确保您已经调用了recall()-方法, 并设置了所有适当的处理程序来处理这些返回的消息。 *  *可以提供以下flags: *  *-mandatory 如果设置,服务器将返回未发送到队列的消息 *-immediate 如果设置,服务器将返回无法立即转发给使用者的消息。 *@param exchange要发布到的交易所 *@param routingkey路由密钥 *@param envelope要发送的完整信封 *@param message要发送的消息 *@param size消息的大小 *@param flags可选标志 */ bool publish(const std::string_view &exchange,const std::string_view &routingKey,const std::string &message,int flags = 0);/** *告诉RabbitMQ服务器已准备好使用消息-也就是 订阅队列消息 * *调用此方法后,RabbitMQ开始向客户端应用程序传递消息。 consumer tag是一个字符串标识符, 如果您以后想通过channel::cancel()调用停止它, 可以使用它来标识使用者。 *如果您没有指定使用者tag,服务器将为您分配一个。 * *支持以下flags: * *-nolocal    如果设置了,则不会同时消耗在此通道上发布的消息 *-noack      如果设置了,则不必对已消费的消息进行确认 *-exclusive  请求独占访问,只有此使用者可以访问队列 * *@param queue    您要使用的队列 *@param tag      将与此消费操作关联的消费者标记 *@param flags    其他标记 *@param arguments其他参数 * *此函数返回一个延迟处理程序。 可以使用onSuccess()、onError()和onFinalize()方法安装回调可以安装的onSuccess()回调应该具有以下格式: void myCallback(const std::string_view&tag); 样例: channel.consume("myqueue").onSuccess( [](const std::string_view& tag) { std::cout << "Started consuming under tag "; std::cout << tag << std::endl; }); */ DeferredConsumer &consume(const std::string_view &queue,const std::string_view &tag,int flags,const Table &arguments);/** *确认接收到的消息 **消费者客户端对收到的消息进行确认应答**当在DeferredConsumer::onReceived()方法中接收到消息时, 必须确认该消息, 以便RabbitMQ将其从队列中删除(除非使用noack选项消费)* *支持以下标志: * *-多条确认多条消息:之前传递的所有未确认消息也会得到确认 * *@param deliveryTag    消息的唯一delivery标签 *@param flags          可选标志 *@return bool */ bool ack(uint64_t deliveryTag, int flags=0);};class DeferredConsumer { /* 注册一个回调函数,该函数在消费者启动时被调用void onSuccess(const std::string &consumertag) */ DeferredConsumer &onSuccess(const ConsumeCallback& callback);/* 注册回调函数,用于接收到一个完整消息的时候被调用 void MessageCallback(const AMQP::Message &message,  uint64_t deliveryTag, bool redelivered) */ DeferredConsumer &onReceived(const MessageCallback& callback);/* Alias for onReceived() */ DeferredConsumer &onMessage(const MessageCallback& callback);/* 注册要在服务器取消消费者时调用的函数 void CancelCallback(const std::string &tag) */ DeferredConsumer &onCancelled(const CancelCallback& callback);};class Message : public Envelope{ const std::string &exchange();const std::string &routingkey();};class Envelope : public MetaData{ const char *body();  // 获取消息正文uint64_t bodySize(); // 获取消息正文大小};
}

2.ev

typedef struct ev_async 
{ EV_WATCHER (ev_async);EV_ATOMIC_T sent; /* private */ 
}ev_async; //break type 
enum 
{ EVBREAK_CANCEL = 0, /* undo unloop */ EVBREAK_ONE    = 1, /* unloop once */ EVBREAK_ALL    = 2  /* unloop all loops */ 
}; // 实例化并获取IO事件监控接口句柄
struct ev_loop *ev_default_loop (unsigned int flags EV_CPP (= 0));
# define EV_DEFAULT  ev_default_loop (0) // 开始运行IO事件监控, 这是一个阻塞接口
int  ev_run (struct ev_loop *loop);/* break out of the loop */
// 结束IO监控
// 如果在主线程进行ev_run(), 则可以直接调用,
// 如果在其他线程中进行ev_run(), 需要通过异步通知进行
void ev_break (struct ev_loop *loop, int32_t break_type) ;  void (*callback)(struct ev_loop *loop, ev_async *watcher, int32_t revents);// 初始化异步事件结构, 并设置回调函数
void ev_async_init(ev_async *w, callback cb);
// 启动事件监控循环中的异步任务处理
void ev_async_start(struct ev_loop *loop, ev_async *w); 
// 发送当前异步事件到异步线程中执行
void ev_async_send(struct ev_loop *loop, ev_async *w);

5.使用

1.publish.cc

#include <ev.h>
#include <amqpcpp.h>
#include <amqpcpp/libev.h>
#include <openssl/ssl.h>
#include <openssl/opensslv.h>int main()
{// 1.实例化底层网络通信框架的IO事件监控句柄auto *loop = EV_DEFAULT;// 2.实例化libEvHandler句柄 -> 将AMQP框架与事件监控关联起来AMQP::LibEvHandler handler(loop);// 3.实例化连接对象AMQP::Address address("amqp://root:SnowK8989@127.0.0.1:5672/");AMQP::TcpConnection connection(&handler, address);// 4.实例化信道对象AMQP::TcpChannel channel(&connection);// 5.声明交换机channel.declareExchange("test-exchange", AMQP::ExchangeType::direct).onError([](const char *message){ std::cout << "声明交换机失败: " << message << std::endl; }).onSuccess([](){ std::cout << "test-exchange 交换机创建成功" << std::endl; });// 6.声明队列channel.declareQueue("test-queue").onError([](const char *message){ std::cout << "声明队列失败: " << message << std::endl; }).onSuccess([](){ std::cout << "test-queue 队列创建成功" << std::endl; });// 7.针对交换机和队列进行绑定channel.bindQueue("test-exchange", "test-queue", "test-queue-key").onError([](const char *message){ std::cout << "test-exchange - test-queue 绑定失败: " \<< message << std::endl; }).onSuccess([](){ std::cout << "test-exchange - test-queue 绑定成功" << std::endl; });// 8.向交换机发布消息for (int i = 0; i < 5; ++i){std::string msg = "Hello SnowK-" + std::to_string(i);if(channel.publish("test-exchange", "test-queue-key", msg) == false){std::cout << "publish 失败" << std::endl;}}// 9.启动底层网络通信框架 -> 开启IOev_run(loop, 0);return 0;
}

2.consume.cc

#include <ev.h>
#include <amqpcpp.h>
#include <amqpcpp/libev.h>
#include <openssl/ssl.h>
#include <openssl/opensslv.h>void MessageCB(AMQP::TcpChannel* channel, const AMQP::Message& message, uint64_t deliveryTag, bool redelivered)
{std::string msg;msg.assign(message.body(), message.bodySize());// 不能这样使用, AMQP::Message后面没有存'\0'// std::cout << message << std::endl std::cout << msg << std::endl;channel->ack(deliveryTag);
}int main()
{// 1.实例化底层网络通信框架的IO事件监控句柄auto *loop = EV_DEFAULT;// 2.实例化libEvHandler句柄 -> 将AMQP框架与事件监控关联起来AMQP::LibEvHandler handler(loop);// 3.实例化连接对象AMQP::Address address("amqp://root:SnowK8989@127.0.0.1:5672/");AMQP::TcpConnection connection(&handler, address);// 4.实例化信道对象AMQP::TcpChannel channel(&connection);// 5.声明交换机channel.declareExchange("test-exchange", AMQP::ExchangeType::direct).onError([](const char *message){ std::cout << "声明交换机失败: " << message << std::endl; }).onSuccess([](){ std::cout << "test-exchange 交换机创建成功" << std::endl; });// 6.声明队列channel.declareQueue("test-queue").onError([](const char *message){ std::cout << "声明队列失败: " << message << std::endl; }).onSuccess([](){ std::cout << "test-queue 队列创建成功" << std::endl; });// 7.针对交换机和队列进行绑定channel.bindQueue("test-exchange", "test-queue", "test-queue-key").onError([](const char *message){ std::cout << "test-exchange - test-queue 绑定失败: " \<< message << std::endl; }).onSuccess([](){ std::cout << "test-exchange - test-queue 绑定成功"; });// 8.订阅消息对垒 -> 设置消息处理回调函数auto callback = std::bind(MessageCB, &channel, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);channel.consume("test-queue", "consume-tag").onReceived(callback).onError([](const char *message){ std::cout << "订阅 test-queue 队列消息失败: " << message << std::endl;exit(0); });// 9.启动底层网络通信框架 -> 开启IOev_run(loop, 0);return 0;
}

3.makefile

all: publish consume
publish: publish.ccg++ -o $@ $^ -lamqpcpp -lev -std=c++17
consume: consume.ccg++ -o $@ $^ -lamqpcpp -lev -std=c++17.PHONY:clean
clean:rm publish consume

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

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

相关文章

教培机构如何向知识付费转型

在数字化时代&#xff0c;知识付费已成为一股不可忽视的潮流。面对这一趋势&#xff0c;教育培训机构必须积极应对&#xff0c;实现向知识付费的转型&#xff0c;以在新的市场环境中立足。 一、教培机构应明确自身的知识定位。 在知识付费领域&#xff0c;专业性和独特性是关键…

VUE前后端分离毕业设计题目项目有哪些,VUE程序开发常见毕业论文设计推荐

目录 0 为什么选择Vue.js 1 Vue.js 的主要特点 2 前后端分离毕业设计项目推荐 3 后端推荐 4 总结 0 为什么选择Vue.js 使用Vue.js开发计算机毕业设计是一个很好的选择&#xff0c;因为它不仅具有现代前端框架的所有优点&#xff0c;还能让你专注于构建高性能、高可用性的W…

Matlab实现白鲸优化算法优化回声状态网络模型 (BWO-ESN)(附源码)

目录 1.内容介绍 2部分代码 3.实验结果 4.内容获取 1内容介绍 2部分代码 %% 清空环境变量 warning off % 关闭报警信息 close all % 关闭开启的图窗 clear % 清空变量 clc % 清空命令行 tic load bwand %%…

CC2530定时器1中断实现定时1-3

源码 #include "iocc2530.h"//引用CC2530头文件int t1_Count0; //定时器1溢出次数计数void Init_Led(void){ /*******************LED1初始化部分******************/P1SEL &~ 0x01; //设置P1_0口为通用I/O口P1DIR | 0x01; //设置P1_0口为输出口P…

软考越来越难了,2024年软考究竟还值不值得考?

最近不少同学沟通&#xff0c;聊到软考现在越来越难了&#xff0c;考了两三次都没过&#xff0c;也有不少新同学咨询软考考试的一些福利政策&#xff0c;投入大量的物力&#xff0c;财力&#xff0c;精力&#xff0c;那么到底软考值不值得考呢&#xff1f; 01 / 关于软考 软考…

Leetcode 10. 正则表达式匹配

1.题目基本信息 1.1.题目描述 给你一个字符串 s 和一个字符规律 p&#xff0c;请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。 ‘.’ 匹配任意单个字符‘*’ 匹配零个或多个前面的那一个元素 所谓匹配&#xff0c;是要涵盖 整个 字符串 s 的&#xff0c;而不是部分…

k8s的控制节点不能访问node节点容器的ip地址

master控制node服务器添加容器后,访问不了该node服务器容器的ip,只能在node服务器访问 排查后发现是k8s的master服务器和node节点的网址网段和k8s初始化时提示的ip网段不一致 我之前是192.168.137.50, 实际上master主机期望的是192.168.1.50 解决方案: 1.删除服务器后重建ma…

python爬虫 - 进阶requests模块

&#x1f308;个人主页&#xff1a;https://blog.csdn.net/2401_86688088?typeblog &#x1f525; 系列专栏&#xff1a;https://blog.csdn.net/2401_86688088/category_12797772.html 目录 前言 一、SSL证书问题 &#xff08;一&#xff09;跳过 SSL 证书验证 &#xff0…

Vue3中提到的Tree-shaking

我们知道&#xff0c;Vue3中提到一个叫Tree-shaking的东西&#xff0c;其实也并不是一个新的东西&#xff0c;有人称之为"摇树优化"&#xff0c;什么意思&#xff1f; 按照作者的原话解释&#xff0c;Tree-shaking其实就是&#xff1a;把无用的模块进行“剪枝”&…

【Linux】进程间通信——System V消息队列和信号量

一、消息队列 1.1 概念 进程间通信的原理是让不同进程看到同一份资源&#xff0c;资源种类的不同就决定了通信方式的差异。如果用管道通信&#xff0c;则资源是文件缓冲区&#xff1b;如果用共享内存&#xff0c;则资源是内存块 消息队列是由操作系统提供的资源&#xff0c;…

postman自动化实战总结

Postman实战总结 简介 本次实战内容主要包括如下几点&#xff1a; l 背景介绍 l Postman使用&#xff0c;侧重于自动化实现&#xff0c;基础使用不做介绍 l 可视化Newman介绍 l 框架特色 l 实战中的坑 背景 随着国内软件技术的高速发展&#xff0c;越来越多的手工测试…

解决谷歌浏览器在安卓手机上的常见问题

在使用安卓手机浏览网页时&#xff0c;谷歌浏览器无疑是许多用户的首选。然而&#xff0c;在使用过程中&#xff0c;用户可能会遇到一些常见问题&#xff0c;如搜索图片困难、缓存积累过多导致浏览器卡顿&#xff0c;以及无法下载视频等。本文将针对这些问题&#xff0c;提供详…

【Linux】详解Linux下的工具(内含yum指令和vim指令)

文章目录 前言1. Linux下软件安装的方式2. yum2.1 软件下载的小知识2.2 在自己的Linux系统下验证yum源的存在2.3 利用yum指令下载软件2.4 拓展yum源&#xff08;针对于虚拟机用户&#xff09; 3. vim编辑器3.1 vim是什么&#xff1f;3.2 如何打开vim3.2 vim各模式下的讲解3.2.1…

【C语言】猜数字小游戏

&#x1f602;个人主页: 起名字真南 &#x1f923;个人专栏:【数据结构初阶】 【C语言】 【C】 目录 1 随机数的生成1.1 rand1.2 srand1.3 time1.4 设置随机数范围 2 猜数字游戏实现 前言&#xff1a;我们学习完前面的循环以后可以写一个猜数字小游戏 1 随机数的生成 想要完成…

新生培训 day1 C语言基础 顺序 分支 循环 数组 字符串 函数

比赛地址 b牛客竞赛_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ C语言数据类型 字符 整型数 int 2e9 long long 9e18 浮点数 代码示例 /** Author: Dduo * Date: 2024-10-8* Description: 新生培训day1 */ #include <stdio.h>int main() {// 定义变量in…

QT-空窗口主窗口对话框

1. QMainWindow QMainWindow 用来创建主窗口 主窗口包含&#xff1a; 标题栏&#xff08;Window title&#xff09;、菜单栏&#xff08;MenuBar&#xff09;、工具栏&#xff08;ToolBar&#xff09;、状态栏&#xff08;StatusBar&#xff09;、停靠部件&#xff08;DockWid…

Ansible学习之ansible-pull命令

想要知道ansible-pull是用来做什么的&#xff0c;就需要了解Ansible的工作模&#xff0c;Ansible的工作模式有两种&#xff1a; push模式 push推送&#xff0c;这是Ansible的默认模式&#xff0c;在主控机上编排好playbook文件&#xff0c;push到远程主机上来执行。pull模式 p…

RISC-V知识点目录

分支预测 分支预测概述https://blog.csdn.net/zhangshangjie1/article/details/136947089?sharetypeblogdetail&sharerId136947089&sharereferPC&sharesourcezhangshangjie1&spm1011.2480.3001.8118分支指令的方向预测https://blog.csdn.net/zhangshangjie1/a…

如何革新源代码保密?七大方法教你应对!

在数字化时代&#xff0c;源代码的安全保密对于企业而言至关重要&#xff0c;它不仅关系到企业的核心竞争力&#xff0c;还涉及到知识产权的保护。源代码一旦泄露&#xff0c;可能会给企业带来无法估量的损失。因此&#xff0c;采取有效的源代码保密措施&#xff0c;是每个企业…

【电路】1.3 电功率和能量

1.3 电功率和能量 电是一种能量存在形式。 1.3.1 电压的定义 将单位正电荷由A点移动至B点&#xff0c;电场力所做的功是 w w w&#xff0c;则 u A B d w d q u_{AB}\frac{dw}{dq} uAB​dqdw​&#xff0c; w w w是功&#xff0c; q q q是电荷量从A到B&#xff0c;沿着任意路…