AODV代码实现详解——原理与源码分析(一)

首先来几个标准参考:
RFC 3561
RFC 3561 中文翻译
一个博客
挺好的另一个博客
事件?
字段长度?

事件驱动
各种定时器
状态转移图?

AODV协议

基本概念

AODV(Ad hoc On-Demand Distance Vector)是一种基于距离向量路由算法的自适应路由协议,用于无线自组织网络(MANET)中的路由选择。AODV 协议的实现流程如下:

  1. 节点广播 RREQ(Route Request)消息:当节点需要发送数据包到某个目的地时,它会广播一个 RREQ 消息来查找到达目的地的路由。这个 RREQ 消息包含源节点地址、目的节点地址和一个唯一的序列号,以防止消息循环。

  2. 节点响应 RREQ 消息:当一个节点收到 RREQ 消息时,它会检查自己的路由表,如果没有到达目的地的路由,则向其邻居节点转发 RREQ 消息。如果节点已经有到达目的地的路由,则向源节点发送 RREP(Route Reply)消息。

  3. 节点广播 RREP 消息:当目的节点收到 RREQ 消息时,它会向源节点发送 RREP 消息,其中包含到达源节点的最短路径。每个节点在转发 RREP 消息之前都会将其缓存起来,以便以后使用。

  4. 节点维护路由表:每个节点都会维护一个路由表,用于存储到达其他节点的路由信息。每当一个节点收到一个 RREP 消息时,它会更新自己的路由表并向其邻居节点广播更新的路由信息。

  5. 节点周期性发送 HELLO 消息:节点会周期性地发送 HELLO 消息以检测邻居节点是否仍然存在。如果一个节点连续几次未收到邻居节点的 HELLO 消息,则认为该节点已经离线,并更新其路由表。

AODV 协议的主要优点是其适应性和效率。由于 AODV 协议仅在需要时才会建立路由,因此它可以有效地减少网络中的控制流量。此外,AODV 协议具有自适应性,可以根据网络拓扑和流量负载动态调整路由。

AODV 协议的核心思想是通过按需路由方式来减少网络中的路由开销。当源节点需要向目的节点发送数据时,它会向周围节点发出路由请求(RREQ)。如果某个节点知道如何到达目的节点,它会向源节点发出路由响应(RREP)。源节点将使用路由响应中的信息来向目的节点发送数据。在这个过程中,每个节点会维护一张路由表,记录到达目的节点的最短路径和下一跳节点。

AODV 协议的一些实现细节:
image.png|400

  1. 路由请求(RREQ):
    当源节点需要向目的节点发送数据时,它会向周围节点发出路由请求(RREQ)。每个节点将检查自己的路由表来查找到达目的节点的最短路径。如果找到了,节点将向源节点发送路由响应,否则节点将广播路由请求。

路由请求包括 RREQ ID、目的 ID、目的序列号、源 ID、源序列号、跳数和洪泛标识。洪泛标识用于避免重复转发路由请求,每个节点只能转发一次具有相同 RREQ ID 的路由请求。

  1. 路由响应(RREP):
    如果某个节点知道如何到达目的节点,它会向源节点发出路由响应(RREP)。路由响应包含源节点的地址、目的节点的地址、目的节点的序列号和到达目的节点的最短路径。源节点将使用路由响应中的信息来向目的节点发送数据。

路由响应包括目的 ID、目的序列号、源 ID、序列号、跳数和路径。路径描述了到达目的节点的最短路径,包括每个节点的地址和序列号。

  1. 路由维护:
    每个节点都会维护一张路由表,记录到达目的节点的最短路径和下一跳节点。节点还会周期性地发送心跳(Hello)消息来保持与周围节点的连接。如果某个节点无法与下一跳节点通信,它会向源节点发送路由错误(RERR)消息,通知源节点重新计算路径。

  2. 序列号:
    为了防止路由环路和路由不稳定,AODV 协议使用序列号来标识每个节点和每个路由。每个节点都具有唯一的序列号,每个路由的序列号递增。当节点收到路由响应时,它将比较目的节点的序列号和自己的路由表中的序列号,如果目的节点的序列号更大,则更新路由表中的信息。

如何解决环路问题?

AODV协议使用了两种方法来避免路由环路的问题,分别是序列号和反向路径检查。

  1. 序列号:
    AODV协议引入了序列号的概念,用于标识每个节点和每个路由。每个节点都具有唯一的序列号,每个路由的序列号递增。当一个节点收到路由请求或路由响应时,它会比较消息中的序列号和自己的路由表中的序列号。如果消息中的序列号较大,则说明该消息是更新的,节点会更新自己的路由表;如果消息中的序列号较小,则说明该消息已经过期,节点会丢弃该消息。

  2. 反向路径检查:
    当一个节点转发路由请求时,它会将自己的地址添加到请求的路由记录中。当一个节点收到路由响应时,它会检查响应中的路由记录,以确定响应是否经过了自己。如果响应经过了自己,则说明存在路由环路,节点会丢弃该响应。

通过使用反向路径检查,AODV协议可以及时发现路由环路,避免数据包在环路中循环。另外,如果某个节点收到了一个来自下一跳节点的数据包,但是该数据包不是该节点向下一跳节点发送的,则说明存在路由环路,该节点会向源节点发送路由错误消息,通知源节点重新计算路径。

代码整体框架

如此长的代码,必须提纲挈领地先了解基本架构,再进行具体地逐函数的了解

主要功能

  1. 报文类的定义(RREQ、RREP、RERR等),用于封装和解析AODV控制报文。

  2. 路由表和寻路队列的管理,用于维护和查找路由信息。

  3. 定时器的实现,用于路由发现、路由维护等定时功能。

  4. 控制报文(如RREQ、RREP、RERR等)的创建、转发和处理。

  5. Hello报文的生成和处理,用于邻居发现和链路检测。

  6. 本地链路管理,用于检测和响应链路断开。

  7. 数据包转发和路由错误处理。

  8. 事件驱动的架构,通过事件队列实现异步处理。

  9. 参数配置,支持启用不同的机制如本地链路修复、扩展环搜索等。

  10. 统计信息收集,用于计算延迟、跳数等性能指标。

  11. 模块接口定义,与外部协议交互。

主要函数与数据结构大致信息

1. 整体理解

报文类有:BaseMessage、RREQ、RREP、RERR、MyBusiness这些类实现了各种AODV控制报文和业务报文的封装,包含报文的定义、解析和组装功能。具体而言:

  • BaseMessage是基类,定义了报文的公共字段如类型、源地址、目的地址等。
  • RREQ类封装了路由请求报文,包含跳数、请求id等AODV特有字段。
  • RREP类封装了路由回复报文。
  • RERR类封装了路由错误报文。
  • MyBusiness类封装了业务数据报文。
    以上这些类通过操作报文字段实现了报文的组装和解析功能。

2. 事件类 MyEvent

封装了一个事件,包含事件处理函数、触发时间、数据等。主要用于事件驱动框架。

// 抽象事件的构造函数
MyEvent::MyEvent(event_func_t f, void* d) :time(-1), handler(f), data(d), id(-1) {}bool MyEvent::operator<(const MyEvent& t) const
{if (this->time < t.time) {return true;}return t.time == this->time && this->id < t.id;
}bool MyEvent::operator==(const MyEvent& t)const
{return t.time == this->time && t.handler == this->handler && t.data == this->data;
}

定义了一个名为MyEvent的类,该类表示一个抽象事件。该类包含一个构造函数和两个运算符重载。
构造函数MyEvent接受两个参数:一个是函数指针,表示事件d 处理函数;另一个是void指针,可以存储事件处理函数需要的数据。构造函数还初始化了类的一些成员变量,包括时间戳、事件处理函数、事件数据和事件ID。

运算符重载了小于号(<)和等于号(==)。其中,小于号根据事件的时间戳和ID比较事件的先后顺序,用于在事件队列中排序;等于号判断两个事件对象是否相等,如果它们的时间戳、事件处理函数和事件数据都相等,则认为它们相等。这两个是利用了C++面向对象的重载特性,方便了后续对事件队列的操作。

3. 路由表类rt_table_t

保存一条路由表项,包含目的地址、下一跳、跳数等信息。

rt_table_t::rt_table_t(int dest_addr, int next, int hops, unsigned int seqno,bool _state, int _flags) :dest(dest_addr), nx_hop(next), hcnt(hops), dest_seq(seqno),//lifetime(-1), state(_state), flags(_flags),rt_timer_timeout(nullptr, nullptr),hello_timer_timeout(nullptr, nullptr) {}
  • dest:目的地址
  • nx_hop:下一跳地址
  • hcnt:跳数
  • dest_seq:目的地址的序列号
  • state:状态标志
  • flags:路由表项的标志
  • rt_timer_timeout:指向路由表定时器超时处理函数的函数指针和函数参数的指针
  • hello_timer_timeout:指向hello定时器超时处理函数的函数指针和函数参数的指针

4. 寻路队列类seek_list_t

保存一个寻路请求相关信息,包含目的地址、序列号、寻路计时器等。

5. 事件队列相关函数

  • eq_insert/eq_erase/eq_set_time/eq_reset_time 用于向事件队列中添加/删除/设置/重置事件。
  • eq_pop 执行队首事件的处理函数;eq_front_time() 返回队首事件的发生时间
  • msg_delete 用于释放报文占用的内存。
  • AodvOutputBuf_push/OutputBuf_push/clear 向发送/接收缓冲区添加业务;或者清空缓冲区。
  • packet_queue_clear/ packet_queue_add 数据包发送缓存队列,清空或者添加消息。packet_queue_set_verdict 对队列里所有包的处理工作,因为当某些包可以发送的时候还是要及时把它发出去的。这个函数比较重要,需要仔细看看

packet_queue_set_verdict

函数设置一个对队列里所有包的处理工作。接受三个参数:

  • dest:目标地址
  • verdict:处理结果,可以是PQ_SENDPQ_DROP,分别表示发送或丢弃该数据包
  • curtime:当前时间,用于计算数据包在队列中的等待时间
    局部变量,包括:
  • count:计数器,用于记录处理了多少个数据包
  • pkt_queue_size:队列长度,用于遍历整个队列
  • rt:指向目标地址对应的路由表项的指针,如果没有对应的路由表项则为nullptr
    函数的主要功能是遍历一个数据包队列,将其中等待目标地址的数据包进行处理,如为他们查找路由等。具体实现如下:
    首先判断如果要发送数据包却没有对应的路由表项,则返回-1表示异常。

然后循环遍历整个数据包队列,从队列头开始处理每个数据包。对于每个数据包,首先判断它在队列中等待的时间是否超过了最大等待时间,如果超过了则释放该数据包的内存,并继续处理下一个数据包。

如果这个数据包不是等待目标地址的数据包,则将其放回队列中,并继续处理下一个数据包。

如果这个数据包是等待目标地址的数据包,且处理结果为丢弃,则释放该数据包的内存,并继续处理下一个数据包。

如果这个数据包是等待目标地址的数据包,且处理结果为发送,则根据目标地址查找路由表,找到下一跳地址,并设置该数据包的源地址为本节点ID,下一跳地址为路由表中找到的地址。如果该数据包是最后一个数据包(即没有下一跳地址),则更新本节点的最后转发数据包时间。如果没有使用二层协议,则释放该数据包的内存;如果使用了二层协议,则将该数据包放入输出缓冲区中。

最后,如果目标地址对应的路由表项存在且有效,并且处理结果为发送,则更新该路由表项和下一跳路由表项的超时时间。
函数返回值是处理了多少个数据包。

6. 定时器相关函数

route_discovery_timeout

处理路由发现请求超时的定时器的回调函数。实际上是实现了协议的部分功能,有些定时器与状态机混用的隐患。

函数输入参数:

  • arg:指向seek_list_t类型结构的指针,该结构记录了路由请求的相关信息
  • curtime:当前时间

函数的主要功能是处理路由请求的超时情况。如果路由请求还未达到最大重试次数,则重新发送路由请求,并更新超时时间。如果路由请求达到了最大重试次数,则表示无法找到目标节点的路由,从seek_list中删除该请求,并尝试使用本地修复机制进行修复。

局部变量:

  • seek_entry:指向seek_list_t类型结构的指针,该结构记录了路由请求的相关信息
  • rt:指向目标节点的路由表项的指针
  • repair_rt:指向要修复的路由表项的指针
  • flooding_flage:泛洪标志,用于区分路由请求和路由响应消息
  • ttl:路由请求消息的TTL值

首先判断seek_entry是否为空,如果为空则直接返回。然后从seek_entry中获取TTL值和重试次数,如果重试次数还未达到最大值,则根据不同的情况更新TTL和超时时间,并重新发送路由请求消息。如果重试次数已经达到最大值,则无法找到目标节点的路由,从seek_list中删除该请求,并尝试使用本地修复机制进行修复。

在重新发送路由请求消息之前,如果目标节点的路由表项存在且其删除时间与当前时间的差小于两倍的网络遍历时间,则更新该路由表项的删除时间。

最后,如果seek_entry已经达到最大重试次数,则从seek_list中删除该请求,并尝试使用本地修复机制进行修复。在尝试本地修复之前,先查找目标节点的路由表项,如果存在并且其状态为需要修复,则调用local_repair_timeout函数进行本地修复。

route_expire_timeout

处理路由表项过期的定时器的回调函数。

  • arg:指向rt_table_t类型结构的指针,该结构记录了路由表项的信息
  • curtime:当前时间

函数的主要功能是处理路由表项的过期情况。如果该路由表项只有一个相邻节点,并且没有接收到该节点的Hello消息,则认为该相邻节点已经离开,需要进行链路断开处理;否则,将该路由表项标记为无效,并清空其前驱节点列表。

首先判断路由表是否为空,如果为空则直接返回。接着判断该路由表项的相邻节点个数hcnt是否为1,如果是,则说明该路由表项只有一个相邻节点,并且没有接收到该节点的Hello消息,即该相邻节点已经离开,需要进行链路断开处理。在链路断开处理中,调用neighbo_link_break函数来删除该相邻节点,同时更新该节点的前驱节点列表。

如果hcnt不为1,则将该路由表项标记为无效,并清空其前驱节点列表。在标记为无效时,调用rt_table_invalidate函数来设置路由表项的状态为无效,并更新其超时时间;在清空前驱节点列表时,调用precursor_list_destroy函数来删除前驱节点列表中的所有节点。

route_delete_timeout 路由删除定时器

如果路由表项指针 rt 为空,则直接返回。
如果路由表项指针 rt 不为空,则调用函数 rt_table_delete(rt) 删除该路由表项。
通过定时器函数删除过期的路由表项,以保证路由表中只保存最新的可用路由信息。

local_repair_timeout 本地修复定时器函数

  1. 指向路由表项的指针 rt,指向 RERR(Route Error)数据包的指针 rerr。
  2. 如果路由表项指针 rt 为空,则直接返回。
  3. 将 RERR 目的地址 rerr_dest 初始化为广播,将路由表项中的 RT_REPAIR 标志位清零。
  4. 如果路由表项中的前驱节点数量不为 0:
    • 调用函数 rerr_create 创建一个 RERR 数据包,包含目的地址、目的序列号以及当前时间等信息。
    • 如果路由表项中的前驱节点数量为 1,则将 RERR 目的地址设置为该前驱节点地址。
    • 将 RERR 数据包发送。
  5. 调用函数 precursor_list_destroy 销毁路由表项中的前驱节点列表。
  6. 将路由表项中的 rt_timer_timeout.handler 成员函数设置为 route_delete_timeout
  7. 调用函数 rt_table_update_timeout 更新该路由表项的定时器,将其设置为 DELETE_PERIOD(路由删除周期),以便在该周期后删除该路由表项。

发生路由故障时,通过本地修复机制对该路由进行修复,以维护路由表中的正确信息。创建一个 RERR 数据包并发送出去,同时将路由表项设置为待删除状态,并在一段时间后删除该路由。

hello_timeout hello 定时器函数

  1. 如果路由表项指针 rt 为空,则直接返回。
  2. 如果路由表项指针 rt 不为空,并且该路由表项状态为 VALID(即有效状态),则执行以下操作:
    • 如果启用了局部修复机制 local_repair,并且该路由表项的 hop count 值小于等于最大修复 TTL(MAX_REPAIR_TTL),则将该路由表项的标志位设置为 RT_REPAIR,表示该路由表项需要进行修复。
    • 调用函数 neighbo_link_break(rt, curtime),将该路由表项中与邻居节点相关的信息进行处理,将其标记为失效状态。

通过定时器函数检查邻居节点是否还处于活动状态,并对失效的邻居节点进行相应的处理,以维护路由表中的正确信息。

echo_timeout

7. 路由表相关函数

  • rt_table_clear 路由表清空
  • rt_table_insert_gateway
  • rt_table_insert_gateway
  • rt_table_update_gateway 添加新表项
  • rt_table_update_timeout
  • rt_table_update_route_timeouts
  • rt_table_find
  • rt_table_invalidate
  • rt_table_delete

8. 寻路队列相关函数

  • seek_list_insert_gateway/remove/find/clear 用于向寻路队列中添加、删除和查找条目。

9. 控制报文处理

rreq相关

rrep相关

rrer相关

10. Hello报文相关函数

用于生成、转发和处理Hello报文,实现邻居发现和链路检测。

11. 本地链路管理函数

  • neighbor_add 处理接收到的报文,更新邻居表。
  • neighbo_link_break 检测链路断开,生成RERR。

12. 业务报文处理

  • business_process 处理最终到达的业务报文。
  • genBusiness_opt 生成业务报文后的处理。
  • recvMessage 将input中的函数注册成为事件
  • processMessage 报文处理函数。重要!!!!

13. 模块接口函数

  • process 接收外部信息,交给内部处理。
  • getAODVrt_table 输出当前的路由表。
  • subNet_rt_table_insert 在网关处添加子网路由表项。

事件驱动

事件驱动思想的整体代码架构

  1. 当有业务数据需要发送时,首先查找路由表。如果有可用路由,则直接封装报文转发。

  2. 如果没有可用路由,则将数据包缓存,并通过route_discovery发起路由请求RREQ。

  3. RREQ通过网络泛洪,当到达目的地或中间节点有新的可用路由时,会触发响应RREP。

  4. 源节点收到RREP后就获得了路由,取出缓存的数据包并发送。

  5. 整个过程中,需要使用seek_list进行路由请求的发起和监控。

  6. 路由表中的路由都有超时时间,超时后会失效。

  7. Hello报文定期发送用于探测链路状态。

  8. 链路断开时,协议定义了在一定时间内,可以通过local_repair尝试进行局部修复。

  9. 局部修复失败后,通过neighbo_link_break检测到链路断开,发送RERR通知其他节点。其他节点收到RERR会使相关路由失效。

  10. 新进入的节点,通过接收业务报文和Hello报文学习邻居和本地链路信息。不会“主动地”探索网络拓扑。

  11. 业务数据报文通过process和genBusiness_opt函数进行转发。

  12. 模块接口将外部消息转入内部输入队列,内部输出转移出模块。

void AODV::process()

核心函数,用于生成各类型事件。对于每个节点来说,看不到其它节点的事件,只需要关心自己的各种到达业务、控制报文。

void AODV::process(std::vector<std::string>& RT_send, const std::vector<std::string>& RT_recv,const std::queue<Business>& BusinessBufferInter, bool isGateway)
{// 将数据转移到inputfromBusinessBufToInput(BusinessBufferInter); // 网关接收到的跨网业务fromRTrecvBufToInput(RT_recv);// 处理input中的数据recvMessage();while (!events.empty() && eq_front_time() <= this->timer * SLOT_LEN){eq_pop();}// 将处理过程中生成的数据转移到RT_sendfromOutputToRTSendBuf(RT_send);++this->timer;
}

“将数据转移到input”部分,是在将数据打包成业务。
recvMessage()是接收
我们再看while中的判断,!events.empty()是为了将事件队列全部轮一遍(这个process()每时隙运行一次),eq_front_time() <= this->timer * SLOT_LEN是为了判断运行总时间是否超过了时隙,如果超过了,就暂停。eq_pop()就是将里面的事件一个一个处理了。
最后fromOutputToRTSendBuf(RT_send)

虽然不是最标准的状态机事件驱动,但是也算是核心的架构,如同汽车的发动机一般,这个while循环一圈一圈的转动,驱动着整个工程的运行。

void AODV::fromBusinessBufToInput()

void AODV::fromBusinessBufToInput(const std::queue<Business>& BusinessBuffer) {if (BusinessBuffer.empty())return;std::queue<Business> tmpBuffer=BusinessBuffer;while(tmpBuffer.size()){Business bs = tmpBuffer.front();tmpBuffer.pop();int src_msg = bs._sr;double recvtime = bs._arrtime;double gentime = bs._gentime;for (auto dest_msg : bs._dr){MyBusiness* mbs = new MyBusiness(INIT_ADDR, INIT_ADDR, gentime, recvtime, dest_msg, src_msg);this->input.push(mbs);}}
}

函数目的:将 BusinessBuffer 中的每个 Business 对象转换为 MyBusiness 对象,并将它们添加到 input 队列中。
首先检查 BusinessBuffer 是否为空。再将 BusinessBuffer 复制到一个临时队列 tmpBuffer 中。然后对tmpBuffer 中的每个 Business 对象 bs,将其源地址 _sr、到达时间 _arrtime 和生成时间 _gentime 分别赋值给变量。接着,对于 bs 中的每个目标地址 _dr,创建一个 MyBusiness 对象 mbs,并将其添加到 input 队列中。
整个函数大量运用了stl库中的操作,这个可以了解下。

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

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

相关文章

门禁系统忘记登入密码,现在更换电脑如何迁移旧电脑门禁系统的数据

环境&#xff1a; ivms-4200 v3.10.0.6_c 问题描述&#xff1a; 门禁系统忘记登入密码,现在更换电脑如何迁移旧电脑门禁系统的数据&#xff0c;旧电脑记住密码&#xff0c;忘了密码和密保了 解决方案&#xff1a; 1.前往海康官网下载4200客户端&#xff0c;在新电脑上安装 …

使用锐捷RG-EG210G-E路由器实现两个IP地址冲突的局域网互通

需求背景&#xff1a; 之前写过一篇博文使用路由器实现三个不同网段局域网内的计算机相互访问&#xff0c;链接如下 https://blog.csdn.net/agang1986/article/details/131862160 当前的需求又发生了变更&#xff0c;有两个独立的局域网&#xff0c;内部的计算机个数和配置的IP…

HTTPS 握手过程

HTTPS 握手过程 HTTP 通信的缺点 通信使用明文&#xff0c;内容可能被窃听(重要密码泄露)不验证通信方身份&#xff0c;有可能遭遇伪装(跨站点请求伪造)无法证明报文的完整性&#xff0c;有可能已遭篡改(运营商劫持) HTTPS 握手过程 客户端发起 HTTPS 请求 用户在浏览器里…

go vet中的那些检测项

go vet 是 Go 语言自带的一个工具&#xff0c;用于分析 Go 代码中的常见错误和潜在问题。它可以检查代码中可能存在的各种问题&#xff0c;例如&#xff1a; 未使用的变量、函数或包 可疑的函数调用 错误的函数签名 程序中的竞态条件 错误的类型转换等 本文意图指令当前go vet所…

如何编译打包OpenSSH 9.4并实现批量升级

1 介绍 openssh 9.4版本已于8月10号发布&#xff0c;安全团队又催着要赶紧升级环境里的ssh版本&#xff0c;本文主要介绍Centos5、Centos6、Centos7下openssh 9.4源码编译rpm包以及批量升级服务器openssh版本的方法。关注公众号后台回复ssh可获取本文相关源码文件。 https://w…

QT 消息对话框按钮显示

前言 搞QT嘛&#xff0c;大多数都是军工。都要国产化&#xff0c;而且消息对话框的按钮的英文也不是很得劲&#xff0c;所以需要汉化。使用静态函数的按钮就是显示英文&#xff0c;汉化的代码如下。 void Widget::on_pushButton_clicked() {QMessageBox box(QMessageBox::Inf…

ES基础操作

1.创建索引 在 Postman 中&#xff0c;向 ES 服务器发 PUT 请求 &#xff1a; http://127.0.0.1:9200/shopping 后台日志 重复发送 PUT 请求添加索引 &#xff1a; http://127.0.0.1:9200/shopping &#xff0c;会返回错误信息 : 2.获取单个索引相关信息 在 Postman 中&#…

Springboot+mybatis-plus+dynamic-datasource+Druid 多数据源 分布式事务

Springbootmybatis-plusdynamic-datasourceDruid 多数据源事务&#xff0c;分布式事务 文章目录 Springbootmybatis-plusdynamic-datasourceDruid 多数据源事务&#xff0c;分布式事务0.前言1. 基础介绍ConnectionFactoryAbstractRoutingDataSource 动态路由数据源的抽象类 Dyn…

CSS学习笔记01

CSS笔记01 什么是CSS CSS&#xff08;Cascading Style Sheets &#xff09;&#xff1a;层叠样式表&#xff0c;也可以叫做级联样式表&#xff0c;是一种用来表现 HTML 或 XML 等文件样式的计算机语言。字体&#xff0c;颜色&#xff0c;边距&#xff0c;高度&#xff0c;宽度…

5 群起集群

1.在启动集群之前&#xff0c;先配置workers,有几个节点就配置几个 [atguiguhadoop102 hadoop]$ vim /opt/module/hadoop-3.1.3/etc/hadoop/workers在该文件中增加如下内容&#xff1a; hadoop102 hadoop103 hadoop104 注意&#xff1a;该文件中添加的内容结尾不允许有空格&a…

成都瀚网科技:抖店如何经营?

作为热门的短视频分享平台&#xff0c;抖音不仅是一种娱乐工具&#xff0c;更是一个蕴藏着无限商机的电商平台。开店、抖音下单成为很多人的选择。那么&#xff0c;抖音如何开店、下单呢&#xff1f; 1、如何在抖音上开店和下单&#xff1f; 注册账号&#xff1a;首先&#xff…

vue 后台管理系统登录 记住密码 功能(Cookies实现)

安装插件 import Cookies from js-cookie 组件引入 import Cookies from js-cookie; 存值&#xff1a; Cookies.set(username, state.account, { expires: 30 }); // username 存的值的名字&#xff0c;state.account 存的值 expires 存储的时间&#xff0c;30天Cookies…

mysql sql_mode数据验证检查

sql_mode 功能 sql_mode 会影响MySQL支持的sql语法以及执行的数据验证检查。通过设置sql_mode ,可以完成不同严格程度的数据校验&#xff0c;有效地保障数据准确性 sql_mode 严格模式 VS 宽松模式 宽松模式 比如&#xff0c;插入的数据不满足 表的数据类型&#xff0c;也可能…

2023年高教社杯 国赛数学建模思路 - 案例:ID3-决策树分类算法

文章目录 0 赛题思路1 算法介绍2 FP树表示法3 构建FP树4 实现代码 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 算法介绍 FP-Tree算法全称是FrequentPattern Tree算法&#xff0c;就是频繁模…

opencv 车牌号的定位和识别+UI界面识别系统

目录 一、实现和完整UI视频效果展示 主界面&#xff1a; 识别结果界面&#xff1a;&#xff08;识别车牌颜色和车牌号&#xff09; 查看历史记录界面&#xff1a; 二、原理介绍&#xff1a; 车牌检测->图像灰度化->Canny边缘检测->膨胀与腐蚀 边缘检测及预处理…

Vue3(开发h5适配)

在开发移动端的时候需要适配各种机型&#xff0c;有大的&#xff0c;有小的&#xff0c;我们需要一套代码&#xff0c;在不同的分辨率适应各种机型。 因此我们需要设置meta标签 <meta name"viewport" content"widthdevice-width, initial-scale1.0">…

第十四课:采用 Qt 开发翻页/分页/多页窗体组件

功能描述&#xff1a;采用 Qt 开发一个翻页/分页/多页的窗体组件&#xff0c;封装为 QWidget 的子类&#xff0c;在你的应用程序中可直接使用。 一、最终演示效果 本次制作的翻页/分页/多页窗体组件是基于 Qt 开发&#xff0c;整个程序封装成 PageWidget 类&#xff0c;继承于…

【微信红包】Axure聊天发红包原型图,含流程图和PRD产品文档

作品概况 页面数量&#xff1a;共 60 页 兼容软件&#xff1a;Axure RP 9/10&#xff0c;不支持低版本 应用领域&#xff1a;聊天软件、社交软件 作品申明&#xff1a;页面内容仅用于功能演示&#xff0c;无实际功能 作品特色 本作品为「发红包」的原型设计图&#xff0c…

Linux_4_文本处理工具和正则表达式

目录 1文本编辑工具之神VIM1.1 vi和vim简介1.2使用vim1.2.1 vim 命令格式1.2.2三种主要模式和转换 1.3扩展命令模式1.3.1扩展命令模式基本命令1.3.2 地址定界1.3.3查找并替换1.3.4定制vim的工作特性1.3.4.1行号1.3.4.2忽略字符的大小写1.3.4.3白动缩进1.3.4.4复制粘贴保留格式1…

商城-学习整理-集群-K8S-集群环境部署(二十四)

目录 一、MySQL集群1、mysql集群原理2、Docker安装模拟MySQL主从复制集群1、下载mysql镜像2、创建Master实例并启动3、创建 Slave 实例并启动4、为 master 授权用户来同步数据1、进入 master 容器2、进入 mysql 内部 &#xff08;mysql –uroot -p&#xff09;3、查看 master 状…