CH343 使用USB转串口发送CAN报文

文章目录

    • 原启
    • UART 走CAN收发器
    • CH343 模拟CAN发送
    • CPP ASIO SocketCAN
    • VXCAN
    • Github Link

原启

早些年自动驾驶激光雷达还不支持PTP之类的时间同步, 很多都是用PPS时间同步, 激光雷达一般装的离控制器或者GNSS天线较远, 车上的线束一般数据电源各种都包在一起的, 如果3.3V直接从域控制器出, 信号将惨不忍睹, 为了解决长距离3.3V PPS传输受干扰的问题, 各种骚操作都整出来:

  • 同轴线, 走SMA或者FAKRA接口出
  • 高电压大电流, 最简单的拿个三极管反相, 12V出, 接收端再反过来, 同时增大传输线中的电流, 抗干扰效果不错

直到看到某家域控制器居然是拿CANFD收发器来传输PPS, 顿时三观炸裂, 直呼卧槽.

CAN收发器通俗讲只是电平转换, 和串口分TTL, RS232, RS485类似, 协议大体相似, 只是传输电平不同. 传PPS自然没有问题. 那大胆一点, UART远距离传输直接走CAN收发器是不是也行. 毕竟国产的CAN收发器价格上和RS232的电平转换芯片也相差不多了.

UART 走CAN收发器

那就这样测试一下

在这里插入图片描述

实物图

在这里插入图片描述

果然可以

在这里插入图片描述

需要注意的地方:

  • UART收发独立, 是全双工的, 如果收发不能时间上错开, 那收发引脚就各自接一片CAN收发器

那再扩展一下, B站上都能拿CH340来当音频设备放歌了, 这CH343能到6Mbps, 模拟下500Kbit/s的CAN发送, 和CAN分析仪通信好像问题不大.

CH343 模拟CAN发送

串口通常的帧格式为 空闲高, 1低电平起始位 + 5到9数据位 + 0或1校验位 + 1到2停止位

在这里插入图片描述
在这里插入图片描述

以CAN标准数据帧为例, 刚好也是空闲高电平, 1bit帧起始(0), 11bitID, 1bit远程帧, 1bit扩展帧, 1bit占位符(0), 4位的数据长度代码, 0~8字节的数据, 15+1bit的CRC, 1+1bit的ACK(01), 7bit帧结束(1111111), 7bit帧间隔(1111111), 来源 Introduction to the Controller Area Network (CAN) (Rev. B)

在这里插入图片描述

还要注意填充位: 在相同逻辑电平的五个连续位之后, 需要插入1bit相反电平, 不然会被认为错误帧.

逻辑分析仪抓出来的波形:

在这里插入图片描述

CH343模拟CAN发送 的原理分析:

  • 串口助手上可以设置数据位5 6 7 8, 校验位None Even Mark Odd Space, 停止位 1 1.5 2, 也就是串口一个字节最少是1起始位+5数据位+0校验位+1停止位, 也就是7bit, 最多1-8-1-2, 也就是14bit
  • 这里500Kbit/s进行CAN测试, CAN或CANFD的位的采样点一般在 75% ~ 87.5%, 串口的起始位和停止位控制不了, 所以要尽量让CAN的采样点落在串口的数据位, 把数据位搞的多多的, 停止位和校验位搞的少少的
  • 给串口4Mbps, 要想串口一字节(一帧的意思)对应500Kbit/s的CAN的一位, 那串口一个字节8bit, 对应 1起始位 + 0停止位 + 1停止位 搞到最小, 剩下最多的 6bit 都给数据, CAN采样点 80% 会落在 8 * 80% = 6.4, 刚好在数据的最后1bit, 能控制就挺好

上面逻辑分析仪抓出来的CAN Bits行数据 000100100011000001010101011111001101110010001011111111

1bit膨胀成UART的1帧(字节), 直接翻译成十六进制串口数据, 0 翻译成00, 1翻译成全1, 6bit数据对应0b111111, 也就是0x3F, 这里测试发现直接写成0xFF也没有问题

00 00 00 FF 00 00 FF 00 00 00 FF FF 00 00 00 00 00 FF 00 FF 00 FF 00 FF 00 FF FF FF FF FF 00 00 FF FF 00 FF FF FF 00 00 FF 00 00 00 FF 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF

因为串口空闲也是高电平, 所以最后的FF都不发了, ACK是其它收发器回的, 也不用发, 最终发的是

00 00 00 FF 00 00 FF 00 00 00 FF FF 00 00 00 00 00 FF 00 FF 00 FF 00 FF 00 FF FF FF FF FF 00 00 FF FF 00 FF FF FF 00 00 FF 00 00 00 FF

连线
在这里插入图片描述

实物

在这里插入图片描述

先打开CAN分析仪 500Kbit/s, 80%采样点, 开启内部终端电阻.

再打开逻辑分析仪, 给通道同时配置UART和CAN协议解析.

最后打开串口调试助手, 4Mbps, 数据位6, 校验位None, 停止位1, 十六进制定时发送, 周期10ms

在这里插入图片描述

在CAN分析仪上位机中可以看到收到了数据(指示灯有几秒一次的闪红错误帧, 大概有1/19~1/20的错误帧)

在这里插入图片描述

逻辑分析仪的抓取

在这里插入图片描述

放大一下

在这里插入图片描述

功能上好像还点效果的. 至于偶发的错误帧, 大概是下图这样某帧发送32字节后, 没有连续发送的情形, 导致CAN的CRC算出来不正确, 可能是串口4M高速率的整包被USB截断了?

在这里插入图片描述

串口定时发送周期从 10ms 改成 100ms 后, 错误帧出现的概率依然不太变化, 情况并没有改善.

如果不用USB的包传输, 直接使用MCU的串口模拟CAN发送, 应该不会出现这样的问题? 但也要注意MCU的串口一般16字节FIFO, 可能要用DMA来搬数据, 不要出现传16字节断一会的情况.

换成 2Mbps, 数据位6, 校验位None, 停止位1, 十六进制定时发送, 周期10ms, 对应的CAN速率250kbit/s, 效果好了很多, 几乎不出现错误帧了:

在这里插入图片描述

下面用 Cpp Asio SocketCAN 写下任意帧发送的代码试试, 在WSL里面测试一下.

CPP ASIO SocketCAN

先把CH343挂载进WSL里面:

在这里插入图片描述

下面的代码先把SocketCAN的数据的每1bit, 扩充成串口的每一个字节, bit 0对应0x00, bit1 对应0xFF, 然后计算CRC15, 最后插入填充位, 进行发送:

#include <linux/can.h>#include <asio.hpp>
#include <chrono>
#include <cstdbool>
#include <cstdint>
#include <iostream>
#include <thread>
#include <vector>int main(int argc, char *argv[]) {asio::io_context iocxt;// serial, 1 start bit, 8 data bits, 1 stop bit, no parity, 2.5M baud// CAN 100K, Serial 1M, 1 start bit, 8 data bits, 1 stop bit, no parity// CAN 250K, Serial 2M, 1 start bit, 6 data bits, 1 stop bit, no parity// CAN 500K, Serial 4M, 1 start bit, 6 data bits, 1 stop bit, no parityasio::serial_port ser(iocxt, "/dev/ttyACM0");ser.set_option(asio::serial_port::baud_rate(2000000));ser.set_option(asio::serial_port::character_size(6));ser.set_option(asio::serial_port::stop_bits(asio::serial_port::stop_bits::one));ser.set_option(asio::serial_port::parity(asio::serial_port::parity::none));ser.set_option(asio::serial_port::flow_control(asio::serial_port::flow_control::none));if (!ser.is_open()) {std::cerr << "Failed to open serial port" << std::endl;return -1;}constexpr uint8_t Bit0 = 0x00;constexpr uint8_t Bit1 = 0xFF;// can crc15 calculationauto crc15 = [&](const std::vector<uint8_t> &data) {bool crc[15] = {0};for (int i = 0; i < data.size(); i++) {bool inv = (data[i] == Bit1) ^ crc[14];crc[14] = crc[13] ^ inv;crc[13] = crc[12];crc[12] = crc[11];crc[11] = crc[10];crc[10] = crc[9] ^ inv;crc[9] = crc[8];crc[8] = crc[7] ^ inv;crc[7] = crc[6] ^ inv;crc[6] = crc[5];crc[5] = crc[4];crc[4] = crc[3] ^ inv;crc[3] = crc[2] ^ inv;crc[2] = crc[1];crc[1] = crc[0];crc[0] = inv;}uint16_t res = 0;for (int i = 0; i < 15; i++) {res |= crc[i] << i;}return res;};// fill stuff bitsauto fsb_insert = [&](std::vector<uint8_t> &data) {uint8_t count = 0;bool last = true;std::vector<uint8_t> newdata;for (auto it = data.begin(); it != data.end(); it++) {bool current = *it == Bit0 ? false : true;newdata.push_back(*it);if (current == last) {count++;} else {count = 1;}if (count == 5) {newdata.push_back(current ? Bit0 : Bit1);count = 1;last = !current;} else {last = current;}}return newdata;};// socketcan frame to serial frameauto can2ser = [&](const can_frame &frame) {std::vector<uint8_t> data;uint32_t id = 0;bool is_extended = frame.can_id & CAN_EFF_FLAG ? true : false;bool is_remote = frame.can_id & CAN_RTR_FLAG;uint8_t dlc = frame.len;data.push_back(Bit0);  // SOF, Start of frameif (is_extended) {id = frame.can_id & CAN_EFF_MASK;// High 11 bits idfor (uint8_t i = 0; i < 11; i++) {uint8_t tmp = ((uint8_t)(id >> (28 - i)) & 0x01) ? Bit1 : Bit0;data.push_back(tmp);}data.push_back(Bit1);  // SRR, Substitute remote requestdata.push_back(Bit1);  // IDE, Identifier extension// Low 18 bits idfor (uint8_t i = 0; i < 18; i++) {uint8_t tmp = ((uint8_t)(id >> (17 - i)) & 0x01) ? Bit1 : Bit0;data.push_back(tmp);}// RTRif (is_remote) {data.push_back(Bit1);} else {data.push_back(Bit0);}data.push_back(Bit0);  // RB1, reserved bit 1} else {id = frame.can_id & CAN_SFF_MASK;for (uint8_t i = 0; i < 11; i++) {uint8_t tmp = ((uint8_t)(id >> (10 - i)) & 0x01) ? Bit1 : Bit0;data.push_back(tmp);}// RTRif (is_remote) {data.push_back(Bit1);} else {data.push_back(Bit0);}data.push_back(Bit0);  // IDE, Identifier extension}data.push_back(Bit0);  // RB0, reserved bit 0// 4 bits DLCfor (uint8_t i = 0; i < 4; i++) {uint8_t tmp = ((uint8_t)(dlc >> (3 - i)) & 0x01) ? Bit1 : Bit0;data.push_back(tmp);}// 0~64 bits datafor (uint8_t i = 0; i < frame.len; i++) {for (uint8_t j = 0; j < 8; j++) {uint8_t tmp = ((frame.data[i] >> (7 - j)) & 0x01) ? Bit1 : Bit0;data.push_back(tmp);}}// CRC15uint16_t crc = crc15(data);for (uint8_t i = 0; i < 15; i++) {uint8_t tmp = ((crc >> (14 - i)) & 0x01) ? Bit1 : Bit0;data.push_back(tmp);}data.push_back(Bit1);  // CRC Delimiter// ACK, ACK Delimiter, EOF, IFS will not be caredreturn data;};// test can2serauto test = [&]() {static uint8_t cnt = 0;can_frame frame;// use 0x92345678, 8 bytes of 0x3C to test fill stuff bitsframe.can_id = 0x12345678 | CAN_EFF_FLAG;frame.len = 8;frame.__pad = 0;frame.__res0 = 0;frame.len8_dlc = 0;for (uint8_t i = 0; i < 8; i++) {frame.data[i] = cnt + i;}cnt++;auto data0 = can2ser(frame);auto data = fsb_insert(data0);// asio::write(ser, asio::buffer(data));asio::async_write(ser, asio::buffer(data),[&](const asio::error_code &ec, std::size_t bytes_transferred) {if (ec) {std::cerr << "Write error: " << ec.message() << std::endl;}});};// timerauto ms = std::chrono::milliseconds(10);asio::steady_timer t(iocxt, ms);std::function<void(const asio::error_code &)> timer =[&](const asio::error_code &ec) {if (ec) {std::cerr << "Timer error: " << ec.message() << std::endl;} else {test();}t.expires_at(t.expiry() + ms);t.async_wait(timer);};t.async_wait(timer);iocxt.run();return 0;
}

对应的cmake文件

cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
project(chcan LANGUAGES CXX)add_executable(${PROJECT_NAME} main.cpp)# https://github.com/cpm-cmake/CPM.cmake
include(CPM.cmake)# https://github.com/chriskohlhoff/asio
CPMAddPackage("gh:chriskohlhoff/asio#asio-1-28-2@1.28.2")
find_package(Threads REQUIRED)
if(asio_ADDED)add_library(asio INTERFACE)target_include_directories(asio SYSTEM INTERFACE ${asio_SOURCE_DIR}/asio/include)target_compile_definitions(asio INTERFACE ASIO_STANDALONE ASIO_NO_DEPRECATED)target_link_libraries(asio INTERFACE Threads::Threads)
endif()target_link_libraries(${PROJECT_NAME} PRIVATE asio
)
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)

编译测试:

$ mkdir build && cd build
$ cmake ..
$ make
$ sudo ./chcan

如图

在这里插入图片描述

VXCAN

为了更实用一点, 如直接使用can_utilscansend等命令, 用VXCAN虚拟出一对SocketCAN:

#!/bin/sh
sudo modprobe can_raw
sudo modprobe vxcanif ip link show can0 > /dev/null 2>&1; thensudo ip link delete dev can0 type vxcan
fisudo ip link add dev can0 type vxcan
sudo ip link set up can0
sudo ip link set dev vxcan0 up

运行检测:

$ chmod 777 vxcan.sh
$ ./vxcan.sh
$ ifconfig

下面编写 chvxcan, 使用对内的vxcan0进行收发. (这里在Ubuntu20测试, 里面 frame.len 写成了 frame.can_dlc)

#include <linux/can.h>
#include <linux/can/raw.h>#include <asio.hpp>
#include <chrono>
#include <cstdbool>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <thread>
#include <vector>int main(int argc, char *argv[]) {asio::io_context iocxt;// serial, 1 start bit, 8 data bits, 1 stop bit, no parity, 2.5M baud// CAN 100K, Serial 1M, 1 start bit, 8 data bits, 1 stop bit, no parity// CAN 250K, Serial 2M, 1 start bit, 6 data bits, 1 stop bit, no parity// CAN 500K, Serial 4M, 1 start bit, 6 data bits, 1 stop bit, no parityasio::serial_port ser(iocxt, "/dev/ttyACM0");ser.set_option(asio::serial_port::baud_rate(2000000));ser.set_option(asio::serial_port::character_size(6));ser.set_option(asio::serial_port::stop_bits(asio::serial_port::stop_bits::one));ser.set_option(asio::serial_port::parity(asio::serial_port::parity::none));ser.set_option(asio::serial_port::flow_control(asio::serial_port::flow_control::none));if (!ser.is_open()) {std::cerr << "Failed to open serial port" << std::endl;return -1;}// canint dev = socket(PF_CAN, SOCK_RAW, CAN_RAW);if (dev < 0) {std::cerr << "Failed to open can" << std::endl;return -1;}struct ifreq ifr;std::strcpy(ifr.ifr_name, "vxcan0");if (ioctl(dev, SIOCGIFINDEX, &ifr) < 0) {std::cerr << "Failed to ioctl can" << std::endl;return -1;}struct sockaddr_can addr;addr.can_family = AF_CAN;addr.can_ifindex = ifr.ifr_ifindex;int enable_canfd = 1;if (setsockopt(dev, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &enable_canfd,sizeof(enable_canfd)) < 0) {std::cerr << "Failed to setsockopt canfd" << std::endl;return -1;}if (bind(dev, (struct sockaddr *)&addr, sizeof(addr)) < 0) {std::cerr << "Failed to bind can" << std::endl;return -1;}asio::posix::stream_descriptor can(iocxt, dev);constexpr uint8_t Bit0 = 0x00;constexpr uint8_t Bit1 = 0xFF;// can crc15 calculationauto crc15 = [&](const std::vector<uint8_t> &data) {bool crc[15] = {0};for (int i = 0; i < data.size(); i++) {bool inv = (data[i] == Bit1) ^ crc[14];crc[14] = crc[13] ^ inv;crc[13] = crc[12];crc[12] = crc[11];crc[11] = crc[10];crc[10] = crc[9] ^ inv;crc[9] = crc[8];crc[8] = crc[7] ^ inv;crc[7] = crc[6] ^ inv;crc[6] = crc[5];crc[5] = crc[4];crc[4] = crc[3] ^ inv;crc[3] = crc[2] ^ inv;crc[2] = crc[1];crc[1] = crc[0];crc[0] = inv;}uint16_t res = 0;for (int i = 0; i < 15; i++) {res |= crc[i] << i;}return res;};// fill stuff bitsauto fsb_insert = [&](std::vector<uint8_t> &data) {uint8_t count = 0;bool last = true;std::vector<uint8_t> newdata;for (auto it = data.begin(); it != data.end(); it++) {bool current = *it == Bit0 ? false : true;newdata.push_back(*it);if (current == last) {count++;} else {count = 1;}if (count == 5) {newdata.push_back(current ? Bit0 : Bit1);count = 1;last = !current;} else {last = current;}}return newdata;};// socketcan frame to serial frameauto can2ser = [&](const can_frame &frame) {std::vector<uint8_t> data;uint32_t id = 0;bool is_extended = frame.can_id & CAN_EFF_FLAG ? true : false;bool is_remote = frame.can_id & CAN_RTR_FLAG;uint8_t dlc = frame.can_dlc;data.push_back(Bit0);  // SOF, Start of frameif (is_extended) {id = frame.can_id & CAN_EFF_MASK;// High 11 bits idfor (uint8_t i = 0; i < 11; i++) {uint8_t tmp = ((uint8_t)(id >> (28 - i)) & 0x01) ? Bit1 : Bit0;data.push_back(tmp);}data.push_back(Bit1);  // SRR, Substitute remote requestdata.push_back(Bit1);  // IDE, Identifier extension// Low 18 bits idfor (uint8_t i = 0; i < 18; i++) {uint8_t tmp = ((uint8_t)(id >> (17 - i)) & 0x01) ? Bit1 : Bit0;data.push_back(tmp);}// RTRif (is_remote) {data.push_back(Bit1);} else {data.push_back(Bit0);}data.push_back(Bit0);  // RB1, reserved bit 1} else {id = frame.can_id & CAN_SFF_MASK;for (uint8_t i = 0; i < 11; i++) {uint8_t tmp = ((uint8_t)(id >> (10 - i)) & 0x01) ? Bit1 : Bit0;data.push_back(tmp);}// RTRif (is_remote) {data.push_back(Bit1);} else {data.push_back(Bit0);}data.push_back(Bit0);  // IDE, Identifier extension}data.push_back(Bit0);  // RB0, reserved bit 0// 4 bits DLCfor (uint8_t i = 0; i < 4; i++) {uint8_t tmp = ((uint8_t)(dlc >> (3 - i)) & 0x01) ? Bit1 : Bit0;data.push_back(tmp);}// 0~64 bits datafor (uint8_t i = 0; i < frame.can_dlc; i++) {for (uint8_t j = 0; j < 8; j++) {uint8_t tmp = ((frame.data[i] >> (7 - j)) & 0x01) ? Bit1 : Bit0;data.push_back(tmp);}}// CRC15uint16_t crc = crc15(data);for (uint8_t i = 0; i < 15; i++) {uint8_t tmp = ((crc >> (14 - i)) & 0x01) ? Bit1 : Bit0;data.push_back(tmp);}data.push_back(Bit1);  // CRC Delimiter// ACK, ACK Delimiter, EOF, IFS will not be caredreturn data;};// read socketcan frame and write to serialuint8_t can_buffer[1024];std::function<void(const asio::error_code &, std::size_t)> can2ser_read =[&](const asio::error_code &ec, std::size_t bytes_transferred) {if (ec) {std::cerr << "Read error: " << ec.message() << std::endl;} else {if (bytes_transferred == CAN_MTU) {struct can_frame frame;std::memcpy(&frame, can_buffer, sizeof(can_frame));auto data0 = can2ser(frame);auto data = fsb_insert(data0);asio::async_write(ser, asio::buffer(data),[&](const asio::error_code &ec, std::size_t bytes_transferred) {if (ec) {std::cerr << "Write error: " << ec.message() << std::endl;}});}}can.async_read_some(asio::buffer(can_buffer), can2ser_read);};can.async_read_some(asio::buffer(can_buffer), can2ser_read);iocxt.run();return 0;
}

测试如图

在这里插入图片描述

到这里, 就可以用你喜欢的语言, 如Python, Rust, C等对can0进行发送了.

Github Link

domain_controller_orin_x2_tc397/ch343_can at main · weifengdq/domain_controller_orin_x2_tc397 (github.com)

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

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

相关文章

使用maven打生产环境可执行包

一、程序为什么要打包 程序打包的主要目的是将项目的源代码、依赖库和其他资源打包成一个可执行的文件或者部署包&#xff0c;方便程序的发布和部署。以下是一些打包程序的重要理由&#xff1a; 方便部署和分发&#xff1a;打包后的程序可以作为一个独立的实体&#xff0c;方便…

leetCode刷题 13. 罗马数字转整数

目录 题目&#xff1a; 1. 思路 2. 解题方法 3. 复杂度 4. Code 题目&#xff1a; 罗马数字包含以下七种字符: I&#xff0c; V&#xff0c; X&#xff0c; L&#xff0c;C&#xff0c;D 和 M。 字符 数值 I 1 V 5 X 10 L …

Vmware虚拟机使用过程中断电后无法重启处理

背景 今天在用新装的虚拟机进行测试的过程中&#xff0c;忽然笔记本关机了&#xff08;没插电源线&#xff09;&#xff0c;重启电脑后发现虚拟机提示“正在使用中“&#xff0c;具体如下所示&#xff1a; 解决 在相关虚拟机文件夹内查找以 .lck 结尾的文件&#xff0c;名称一…

java中Volatile关键字的原理

Volitile的主要作用就是保持内存可见性和防止指令重排序。我分别说一下这两个作用的实现原理 1.保持内存可见性的实现原理 volatile内存可见性主要通过lock前缀指令实现的&#xff0c;它会锁定当前内存区域的缓存&#xff0c;并且立即将当前缓存的数据写入到主内存&#xff0…

记录dockers中Ubuntu安装python3.11

参考&#xff1a; docker-ubuntu 安装python3.8,pip3_dockerfile ubuntu22 python3.8-CSDN博客

解释“RNN encode-decode”

“RNN encode-decode” 涉及使用循环神经网络&#xff08;Recurrent Neural Network&#xff0c;RNN&#xff09;来执行编码和解码操作。这种结构常用于处理序列数据&#xff0c;例如自然语言处理、语音识别和时间序列预测等任务。 以下是 “RNN encode-decode” 的一般概念&a…

西门子PLC常用底层逻辑块分享_单/双输出电磁阀

文章目录 前言一、功能概述二、单输出电磁阀程序编写1.创建自定义数据类型2.创建FB功能块“单输出电磁阀”3.编写程序 三、双输出电磁阀程序编写1.创建自定义数据类型2.创建FB功能块“双输出电磁阀”3.编写程序 前言 本文分享一个自己编写的电磁阀控制逻辑块。 一、功能概述 …

【代码随想录】【二叉树】补day21:二叉搜索树的最小绝对差 、二叉搜索树中的众数 、二叉树的最近公共祖先

最小绝对差 1.申请一个数组&#xff0c;比较两两之间最小的差值 def getresult3(self,node:TreeNode):self.nums[]self.getMinimumDifference(node)mindifferencefloat(inf)for i in range(len(self.nums)-1):mindself.nums[i1]-self.nums[i]if mindifference>mind:mindiff…

流程控制 JAVA语言基础

任何简单或复杂的算法都可以由三种基本结构组成&#xff1a;顺序结构&#xff0c;选择结构&#xff0c;循环结构。 顺序结构 比较一般的结构&#xff0c;程序从上到下执行。 选择结构 我们从最简单的单路选择开始&#xff0c;符合条件的进入语句序列&#xff0c;不符合条件的…

【C++map和set容器:AVL树、红黑树详解并封装实现map和set】

[本节目标] map和set底层结构 AVL树 红黑树 红黑树模拟实现STL中的map和set 1.底层结构 前面对map/multimap/set/multiset进行了简单的介绍&#xff0c;在其文档介绍中发现&#xff0c;这几个容器有个 共同点是&#xff1a;其底层都是按照二叉搜索树来实现的&#xff0c;但…

高并发缓存策略大揭秘:面试必备的缓存更新模式解析

在高并发场景中&#xff0c;缓存能抵挡大量数据库查询&#xff0c;减少数据库压力&#xff0c;对于缓存更新通常有以下几种模式可以选择&#xff1a; cache asideread/write throughwrite behind caching cache aside模式 Cache-aside模式是一种常用的用于管理缓存的模式。它…

RocketMQ学习笔记四(黑马)

课程地址&#xff1a; 1.Rocket第二章内容介绍_哔哩哔哩_bilibili &#xff08;视频35~88&#xff0c;搭建了一个电商项目&#xff09; 待学&#xff0c;待完善。

瑞_23种设计模式_策略模式

文章目录 1 策略模式&#xff08;Strategy Pattern&#xff09;★1.1 介绍1.2 概述1.3 策略模式的结构1.4 策略模式的优缺点1.5 策略模式的使用场景 2 案例一2.1 需求2.2 代码实现 3 案例二3.1 需求3.2 代码实现 4 JDK源码解析&#xff08;Comparator&#xff09; &#x1f64a…

mysql中用逗号隔开的某字段,如何判断其他表的字段值是否在这个字段中

因为要增加需求&#xff0c;需要将线上表中老数据&#xff0c;修改为新数据的规则。 线上两张表&#xff0c;sequence_number中is_use有3作废、2到期状态&#xff0c;需要根据这个状态和school_ai_authorization中的is_deleted修改新增的state字段。 sequence_number表结构&…

温暖如初春:新生儿脸红小贴士

引言&#xff1a; 新生儿脸红是许多父母热切期待的瞬间之一。这种可爱的现象不仅令人陶醉&#xff0c;还可能是宝宝良好健康的标志。然而&#xff0c;在欣喜之余&#xff0c;父母也需要留意脸红背后的可能原因和注意事项&#xff0c;以确保宝宝的舒适和健康。 1. 探寻原因&…

通天星CMSV6 车载视频监控平台 信息泄露漏洞

漏洞描述 通天星CMSV6车载视频监控平台 StandardLoginAction getAlser.acion接口处存在信息泄露漏洞 fofa语句 body"/808gps/" 漏洞复现 打开页面 构造payload POST /808gps/StandardLoginAction_getAllUser.action HTTP/1.1 Host: User-Agent: Mozilla/5.0 (…

ASP.NET区域检验云LIS平台源码 标本全生命周期管理

目录 一、云LIS系统功能亮点 二、收费项目管理 三、检验项目管理 系统功能 云LIS系统源码是一款全面的实验室信息管理系统源码&#xff0c;其主要功能包括样本管理、检测项目管理、质控管理、报告管理、数据分析、两癌筛查等多个方面。具有独立的配套SaaS模式运维管理系统&…

轻量级内网穿透服务-nps

1. NPS概述&#xff1a; NPS&#xff08;内网穿透代理服务器&#xff09;是一款由TalentYoung开发的轻量级、高性能的内网穿透代理服务器。它的设计目标是简单易用、功能强大&#xff0c;可以帮助用户在公网上访问内网服务。 NPS支持TCP、UDP、HTTP等多种协议&#xff0c;并提…

翻转时钟效果

时分秒三个部分结构功能完全一致&#xff0c;均有四块构成&#xff0c;上下各两块。 正面可见&#xff0c;背面不可见&#xff0c;同时需要调整翻转过程中的z-index。 初始状态card2为已经翻转状态。 calendar.html <!DOCTYPE html> <html lang"en">&…

非常有用的Python 20个单行代码

有用的 Python 单行代码片段&#xff0c;只需一行代码即可解决特定编码问题&#xff01; 在本文中&#xff0c;云朵君将分享20 个 Python 一行代码&#xff0c;你可以在 30 秒或更短的时间内轻松学习它们。这种单行代码将节省你的时间&#xff0c;并使你的代码看起来更干净且易…