C++小白实习日记——Pollnet,Efvi,UDP,数据类型转换(下)

内容太多了,这篇记录UDP接收端

一,UDP接收端接收数据

有了pollnet这个开源项目的支持,接收端的步骤为:1)初始化硬编码的参数:接口,IP和端口

2)创建接收文件.csv

3)读取UDP包并将其写入文件中

std::atomic<bool> active = {true};
int main(int argc, char *argv[]) {//硬编码的参数:接口、IP和端口const char* interface = "enp8s0f1";  // 网口const char* local_ip = "10.100.100.161";  // 本地IP地址int local_port = 12346;  // 本地端口
//  const char* sub_ip = "10.100.100.162";  // 可选的订阅IPEfviUdpReceiver receiver;if (!receiver.init(interface, local_ip, local_port)) {cout << receiver.getLastError() << endl;return 1;}std::string fname1 = "接收文件.csv";//bool ret1 = isFileExists_ifstream(fname1);if (ret1){//文件存在cout<<"File is exit\n"<<endl;} else {std::ofstream outFile1(fname1);if (outFile1){std::cout <<"Files created successful.\n" << std::endl;}else {std::cout <<"Failed to create files.\n" << std::endl;return 1;}//关闭文件outFile1.close();}ofstream outFile1(fname1, ios::out);outFile1 << "DataTimeStamp"<<endl;while (active.load(std::memory_order_acquire)) {receiver.read(outFile1, outFile2, outFile3, outFile4);}return 0;
}

声明一个原子布尔类型的变量 active,并初始化为 true。这是为了控制接收器的运行状态。使用 std::atomic 类型确保在多线程环境下对 active 的操作是安全的。 

std::atomic<bool> active = {true};

 c++的标准流函数,用于检查指定路径的文件是否存在

bool isFileExists_ifstream(string& name) {ifstream f(name.c_str());return f.good();
}
  • ofstream outFile1(fname1, ios::out);:使用 ofstream 打开 fname1 文件,并准备写入数据。ios::out 表示以输出模式打开文件。
  • outFile1 << "DataTimeStamp" << endl;:写入一个标题 DataTimeStamp 到文件,之后换行。这为后续的接收数据做准备。
ofstream outFile1(fname1, ios::out);
outFile1 << "DataTimeStamp" << endl;

输出模式(ios::out)的含义:

  • ios::out 表示文件被以写入模式打开,也就是用于向文件写入数据。
  • 如果文件已经存在,ios::out 会清空文件内容(覆盖文件)。
  • 如果文件不存在,ios::out 会创建一个新文件。
  • ios::in:表示以输入模式打开文件,用于从文件读取数据。
  • ios::app:表示以追加模式打开文件,写入的数据将被添加到文件末尾,而不是覆盖原文件内容。
  • ios::ate:表示打开文件后,文件指针将移动到文件的末尾,但仍然可以进行读写操作。
  • ios::trunc:表示打开文件时,如果文件已经存在,则清空文件内容(这个模式是 ios::out 的默认行为)。
  • ios::binary:表示以二进制模式打开文件,而不是文本模式。

二,pollnet项目

这个项目是git的一个开源项目:https://github.com/MengRao/pollnet/tree/master(MIT许可证)

包括一些TCP,UDP的发送接收的封装类,Efvi与Socket的发送方式demo等等

我主要学习了Efvi.h这个库文件,其中包含EfviUdpSender和EfviReceiver两个大类,其中EfviReceiver包括EfviUdpReceiver,EfviEthReceiver,可以看到这个库函数主要是封装了UDP通信的发送接收类。

1,EfviUdpSender

这个类中主要有公有函数init,write,close,私有函数getMacFromARP,getGW,init_udp_pkt,ci_ip4_hdr_init,saveError,hexchartoi等等

1)init

函数 init 的目的是初始化网络接口、配置地址和端口,设置虚拟接口并准备内存以便发送数据。它的主要功能是准备好一个高效的网络通信环境,具体来说是通过 EFVi 驱动(一个与高速网络适配器或类似硬件接口的驱动相关的工具)进行 UDP 数据包的发送。

struct sockaddr_in local_addr;
struct sockaddr_in dest_addr;
uint8_t local_mac[6];
uint8_t dest_mac[6];
local_addr.sin_port = htons(local_port);
inet_pton(AF_INET, local_ip, &(local_addr.sin_addr));
dest_addr.sin_port = htons(dest_port);
inet_pton(AF_INET, dest_ip, &(dest_addr.sin_addr));
  • local_addrdest_addr 是用于本地和目标地址的结构体(sockaddr_in);
  • 使用 inet_pton 将 IP 地址(字符串)转换为二进制格式,存储到 sin_addr 中;
  • 端口号使用 htons 函数转换为网络字节序。
if ((0xff & dest_addr.sin_addr.s_addr) < 224) {char dest_mac_addr[64];if (!getMacFromARP(interface, dest_ip, dest_mac_addr)) {char gw[64];if (!getGW(dest_ip, gw) || !getMacFromARP(interface, gw, dest_mac_addr)) {saveError("Can't find dest ip from arp cache, please ping dest ip first", 0);return false;}}if (strlen(dest_mac_addr) != 17) {saveError("invalid dest_mac_addr", 0);return false;}for (int i = 0; i < 6; ++i) {dest_mac[i] = hexchartoi(dest_mac_addr[3 * i]) * 16 + hexchartoi(dest_mac_addr[3 * i + 1]);}
}
  • 检查目标地址是否为单播地址(dest_ip 的最后一个字节是否小于 224,如果小于则是单播地址)。
  • 如果是单播地址,调用 getMacFromARP 函数获取目标 IP 的 MAC 地址。如果 ARP 表中没有目标 IP 的 MAC 地址,尝试从网关获取。
  • hexchartoi 是一个将十六进制字符转换为整数的辅助函数。
if ((0xff & dest_addr.sin_addr.s_addr) < 224) {char dest_mac_addr[64];if (!getMacFromARP(interface, dest_ip, dest_mac_addr)) {char gw[64];if (!getGW(dest_ip, gw) || !getMacFromARP(interface, gw, dest_mac_addr)) {saveError("Can't find dest ip from arp cache, please ping dest ip first", 0);return false;}}if (strlen(dest_mac_addr) != 17) {saveError("invalid dest_mac_addr", 0);return false;}for (int i = 0; i < 6; ++i) {dest_mac[i] = hexchartoi(dest_mac_addr[3 * i]) * 16 + hexchartoi(dest_mac_addr[3 * i + 1]);}
}
  • 如果目标地址是组播地址(即目标地址的第一字节大于等于 224),则根据组播地址生成目标 MAC 地址。
  • 组播地址的 MAC 地址计算规则是:以 01:00:5e 开头,后面三字节根据 IP 地址最后三个字节生成。
else {dest_mac[0] = 0x1;dest_mac[1] = 0;dest_mac[2] = 0x5e;dest_mac[3] = 0x7f & (dest_addr.sin_addr.s_addr >> 8);dest_mac[4] = 0xff & (dest_addr.sin_addr.s_addr >> 16);dest_mac[5] = 0xff & (dest_addr.sin_addr.s_addr >> 24);
}
  • 打开驱动程序并初始化虚拟接口句柄(dh)。ef_driver_open 是一个操作系统或驱动接口的函数,用来初始化虚拟接口。
  • ef_pd_alloc_by_name 用于分配一个数据包描述符(Packet Descriptor,简称 PD),该描述符用于管理网络数据包。
int rc;
if ((rc = ef_driver_open(&dh)) < 0) {saveError("ef_driver_open failed", rc);return false;
}
if ((rc = ef_pd_alloc_by_name(&pd, dh, interface, EF_PD_DEFAULT)) < 0) {saveError("ef_pd_alloc_by_name failed", rc);return false;
}
  • 配置虚拟接口的标志和能力,检查虚拟接口是否支持 CTPIO(Contiguous Transmission Protocol Input/Output)。如果支持 CTPIO,则在虚拟接口标志中启用相应的标志。
int vi_flags = EF_VI_FLAGS_DEFAULT;
int ifindex = if_nametoindex(interface);
unsigned long capability_val = 0;
if (ef_vi_capabilities_get(dh, ifindex, EF_VI_CAP_CTPIO, &capability_val) == 0 && capability_val) {use_ctpio = true;vi_flags |= EF_VI_TX_CTPIO;
}
  • 使用 ef_vi_alloc_from_pd 从数据包描述符分配虚拟接口(VI)。该接口用于管理虚拟网络设备并发送/接收数据包。
  • ef_vi_get_mac 获取分配的虚拟接口的 MAC 地址。
size_t alloc_size = N_BUF * PKT_BUF_SIZE;
buf_mmapped = true;
pkt_bufs = (uint8_t*)mmap(NULL, alloc_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_HUGETLB, -1, 0);
if (pkt_bufs == MAP_FAILED) {buf_mmapped = false;rc = posix_memalign((void**)&pkt_bufs, 4096, alloc_size);if (rc != 0) {saveError("posix_memalign failed", -rc);return false;}
}
  • 为网络数据包分配内存缓冲区。首先尝试使用 mmap 将内存映射到进程地址空间,如果失败则使用 posix_memalign 来对齐内存。
if ((rc = ef_memreg_alloc(&memreg, dh, &pd, dh, pkt_bufs, alloc_size)) < 0) {saveError("ef_memreg_alloc failed", rc);return false;
}
  • 使用 ef_memreg_alloc 为缓冲区注册内存,确保其可以通过网络适配器访问
for (int i = 0; i < N_BUF; i++) {struct pkt_buf* pkt = (struct pkt_buf*)(pkt_bufs + i * PKT_BUF_SIZE);pkt->post_addr = ef_memreg_dma_addr(&memreg, i * PKT_BUF_SIZE) + sizeof(ef_addr);init_udp_pkt(&(pkt->eth), local_addr, local_mac, dest_addr, dest_mac);
}
  • 为每个数据包初始化缓冲区,并填充 UDP 包的相关信息,如源地址、目标地址和 MAC 地址。
uint16_t* ip4 = (uint16_t*)&((struct pkt_buf*)pkt_bufs)->ip4;
ipsum_cache = 0;
for (int i = 0; i < 10; i++) {ipsum_cache += ip4[i];
}
ipsum_cache = (ipsum_cache >> 16u) + (ipsum_cache & 0xffff);
ipsum_cache += (ipsum_cache >> 16u);
  • 计算数据包的校验和(ipsum_cache),确保数据在传输过程中的完整性。

2)write

  bool write(const void* data, uint32_t size) {// 为缓冲区分配内存
//    uint8_t* pkt_bufs = new uint8_t[N_BUF * PKT_BUF_SIZE];struct pkt_buf* pkt = (struct pkt_buf*)(pkt_bufs + buf_index_ * PKT_BUF_SIZE);struct ci_ether_hdr* eth = &pkt->eth;struct ci_ip4_hdr* ip4 = (struct ci_ip4_hdr*)(eth + 1);struct ci_udp_hdr* udp = (struct ci_udp_hdr*)(ip4 + 1);uint16_t iplen = htons(28 + size);// 假设IP头部为28字节ip4->ip_tot_len_be16 = iplen;udp->udp_len_be16 = htons(8 + size);// 将 data 数据复制到数据包中
//    memcpy(pkt + 1, data, size);// 将数据复制到数据包中memcpy(reinterpret_cast<void*>(pkt + 1), data, size);  // 正确的内存拷贝uint32_t frame_len = 42 + size;int rc;if (use_ctpio) {uint32_t ipsum = ipsum_cache + iplen;ipsum += (ipsum >> 16u);ip4->ip_check_be16 = ~ipsum & 0xffff;// 使用CTPIO方式发送数据ef_vi_transmit_ctpio(&vi, &pkt->eth, frame_len, frame_len);rc = ef_vi_transmit_ctpio_fallback(&vi, pkt->post_addr, frame_len, buf_index_);}else {rc = ef_vi_transmit(&vi, pkt->post_addr, frame_len, buf_index_);}// 更新缓冲区索引buf_index_ = (buf_index_ + 1) % N_BUF;// 处理传输事件ef_event evs[EF_VI_EVENT_POLL_MIN_EVS];ef_request_id ids[EF_VI_TRANSMIT_BATCH];int events = ef_eventq_poll(&vi, evs, EF_VI_EVENT_POLL_MIN_EVS);for (int i = 0; i < events; ++i) {if (EF_EVENT_TYPE_TX == EF_EVENT_TYPE(evs[i])) {ef_vi_transmit_unbundle(&vi, &evs[i], ids);}}// 释放内存
//    delete[] pkt_bufs;return true;}
  • 通过 pkt_bufsbuf_index_ 来访问一个缓冲区中的数据包。pkt_bufs 是之前分配的一个内存区域,buf_index_ 是当前正在使用的缓冲区索引。
  • pkt 是一个指向当前缓冲区的指针,类型是 pkt_buf,它包含以太网头、IP头和UDP头。

  • 通过指针 ethip4udp 来访问数据包中的以太网头、IP头和UDP头。
  • iplen 计算IP头部和数据部分的总长度。这里假设IP头部是28字节,数据部分的大小是 size
  • ip_tot_len_be16udp_len_be16 分别是IP和UDP头中的长度字段,它们需要以网络字节序(大端序)来表示。
  • 将传入的 data 数据复制到数据包中的适当位置(在以太网头、IP头和UDP头之后的位置)。这里 pkt + 1 是指向数据部分的指针。
  • memcpy 将原始数据复制到数据包中的正确位置。
  • frame_len 计算整个数据包的长度,42字节是以太网头、IP头和UDP头的总长度。
  • 这里分为两种发送模式:

    • CTPIO模式:使用 CTPIO 方式发送数据。这是一种直接传输模式,通过 ef_vi_transmit_ctpio 发送数据。如果 CTPIO 发送失败,则会使用回退方式 ef_vi_transmit_ctpio_fallback
    • 普通发送模式:如果不使用 CTPIO,则直接通过 ef_vi_transmit 发送数据包。
  • ipsum_cache 是一个缓存的校验和,用来计算IP头部的校验和。校验和计算方法是将所有IP头的16位部分加起来,并进行取反操作,确保数据的完整性。

  • 在发送完当前数据包后,更新 buf_index_,使得缓冲区索引在 0N_BUF-1 之间循环。

  • 使用 ef_eventq_poll 检查是否有传输事件。这通常是在传输过程中可能发生的事件(如传输成功、失败等)。
  • 如果是发送事件(EF_EVENT_TYPE_TX),则通过 ef_vi_transmit_unbundle 处理事件,并返回请求ID。

2,EfviUdpReceiver

包含init函数与read函数

1)Init

  bool init(const char* interface, const char* dest_ip, uint16_t dest_port, const char* subscribe_ip = "") {if (!EfviReceiver::init(interface)) {return false;}udp_prefix_len = 64 + ef_vi_receive_prefix_len(&vi) + 14 + 20 + 8;int rc;ef_filter_spec filter_spec;struct sockaddr_in sa_local;sa_local.sin_port = htons(dest_port);inet_pton(AF_INET, dest_ip, &(sa_local.sin_addr));ef_filter_spec_init(&filter_spec, EF_FILTER_FLAG_NONE);if ((rc = ef_filter_spec_set_ip4_local(&filter_spec, IPPROTO_UDP, sa_local.sin_addr.s_addr, sa_local.sin_port)) <0) {std::cerr << "ef_filter_spec_set_ip4_local failed" << rc << std::endl;return false;}if ((rc = ef_vi_filter_add(&vi, dh, &filter_spec, NULL)) < 0) {std::cerr << "ef_vi_filter_add failed" << rc << std::endl;return false;}if (subscribe_ip[0]) {if ((subscribe_fd_ = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {std::cerr << "socket failed" << -errno << std::endl;return false;}struct ip_mreq group;inet_pton(AF_INET, subscribe_ip, &(group.imr_interface));inet_pton(AF_INET, dest_ip, &(group.imr_multiaddr));if (setsockopt(subscribe_fd_, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&group, sizeof(group)) < 0) {std::cerr << "setsockopt IP_ADD_MEMBERSHIP failed" << -errno << std::endl;return false;}}return true;}

 初始化网络通信,配置和设置了接收UDP数据包的过滤器并加入多播组。

  • interface:指定要用于接收数据包的网络接口。
  • dest_ip:目标 IP 地址,数据包的目的地址。
  • dest_port:目标端口,接收数据的 UDP 端口号。
  • subscribe_ip:可选的多播地址。如果设置了此参数,函数将加入到指定的多播组。
udp_prefix_len = 64 + ef_vi_receive_prefix_len(&vi) + 14 + 20 + 8;

 udp_prefix_len 是 UDP 数据包的前缀长度,包含:

  • 64:可能是与硬件或特定网络库相关的长度。

  • ef_vi_receive_prefix_len(&vi):获取网络接口接收前缀长度。

  • 14:以太网头部长度(Ethernet header)。

  • 20:IPv4头部长度(IP header)。

  • 8:UDP头部长度。

ef_filter_spec filter_spec;
struct sockaddr_in sa_local;
sa_local.sin_port = htons(dest_port);
inet_pton(AF_INET, dest_ip, &(sa_local.sin_addr));
ef_filter_spec_init(&filter_spec, EF_FILTER_FLAG_NONE);
  • 创建一个 filter_spec 过滤器规格对象,和一个 sockaddr_in 地址结构体 sa_local,它用来设置目标 IP 和端口。

  • inet_pton 将目标 IP 地址转换为网络字节序的地址格式。

  • htons 将目标端口号转换为网络字节序。

if ((rc = ef_filter_spec_set_ip4_local(&filter_spec, IPPROTO_UDP, sa_local.sin_addr.s_addr, sa_local.sin_port)) < 0) {std::cerr << "ef_filter_spec_set_ip4_local failed" << rc << std::endl;return false;
}
  • 设置过滤器,指定目标协议为 UDP,目标 IP 和端口为 dest_ipdest_port
  • 如果配置过滤器失败,输出错误并返回 false
if ((rc = ef_vi_filter_add(&vi, dh, &filter_spec, NULL)) < 0) {std::cerr << "ef_vi_filter_add failed" << rc << std::endl;return false;
}
  • 将刚刚配置好的过滤器添加到接收网络接口中。vi 是接收接口对象,dh 是设备句柄,filter_spec 是过滤器规格。
  • 如果添加失败,输出错误并返回 false
if (subscribe_ip[0]) {if ((subscribe_fd_ = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {std::cerr << "socket failed" << -errno << std::endl;return false;}struct ip_mreq group;inet_pton(AF_INET, subscribe_ip, &(group.imr_interface));inet_pton(AF_INET, dest_ip, &(group.imr_multiaddr));if (setsockopt(subscribe_fd_, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&group, sizeof(group)) < 0) {std::cerr << "setsockopt IP_ADD_MEMBERSHIP failed" << -errno << std::endl;return false;}
}
  • 如果传入了 subscribe_ip 参数,表示需要加入一个多播组。
  • 创建一个 socket 用于接收多播数据。
  • 使用 inet_pton 转换多播地址和本地网络接口的 IP 地址。
  • 使用 setsockopt 函数将本地接口加入到指定的多播组。
  • 如果多播加入失败,输出错误并返回 false

2) read

这个和原来的有一些修改:

  • ef_eventq_poll:从事件队列中获取接收到的数据包。
  • pkt_buf:指向接收到的网络数据包的缓冲区。
  • printPacket:打印数据包的内容。
  void read(ofstream &outFile1, ofstream &outFile2, ofstream &outFile3, ofstream &outFile4) {ef_event evs;if (ef_eventq_poll(&vi, &evs, 1) == 0) return;int id = EF_EVENT_RX_RQ_ID(evs);pkt_buf = (struct pkt_buf*)(pkt_bufs + id * PKT_BUF_SIZE);if (EF_EVENT_TYPE(evs) == EF_EVENT_TYPE_RX) {const char* buf = (const char*)pkt_buf + 64;const int bufLen = EF_EVENT_RX_BYTES(evs);if(bufLen != 0){// memcpy(udp_pkt_data, buf, bufLen);//TODO: 此处是否可以直接从DMA缓冲区里读取?TORALEV2API::CTORATstpLev2MarketDataField temp_MarketDataField = {0};//快照TORALEV2API::CTORATstpLev2OrderDetailField temp_OrderDetailField = {0};//逐笔委托TORALEV2API::CTORATstpLev2TransactionField temp_TransactionField = {0};//逐笔成交TORALEV2API::CTORATstpLev2XTSmergeField temp_SmergeField = {0};//合并主笔PARASE_CTORATstpLev2MarketDataField(buf+42, temp_MarketDataField, outFile1);
//            PARASE_CTORATstpLev2OrderDetailField(buf+42, temp_OrderDetailField, outFile2);
//            //行情快照  662
//            if (buf[42]==0x30&&buf[43]==0x00&&buf[44]==0x00&&buf[45]==0x05) {
//                PARASE_CTORATstpLev2MarketDataField(buf+42, temp_MarketDataField, outFile1);
//            }
//            //逐笔委托  110bytes
//            if (buf[42]==0x03&&buf[43]==0x00&&buf[44]==0x00&&buf[45]==0x08){
//                PARASE_CTORATstpLev2OrderDetailField(buf+42, temp_OrderDetailField, outFile2);
//            }
//            //逐笔成交  118bytes
//            if (buf[42]==0x03&&buf[43]==0x00&&buf[44]==0x00&&buf[45]==0x07){
//                PARASE_CTORATstpLev2TransactionField(buf+42, temp_TransactionField, outFile3);
//            }
//            //合并主笔  118bytes
//            if (buf[42]==0x30&&buf[43]==0x00&&buf[44]==0x00&&buf[45]==0x13){            //固定填值:0x13000030
//                PARASE_CTORATstpLev2XTSmergeField(buf+42, temp_SmergeField, outFile4);
//            }printPacket(buf, bufLen);}}ef_vi_receive_post(&vi, pkt_buf->post_addr, id);return;}private:int udp_prefix_len;struct pkt_buf* pkt_buf;int subscribe_fd_ = -1;};
if (ef_eventq_poll(&vi, &evs, 1) == 0) return;

 这一行从网络事件队列中获取事件,vief_vi 设备实例,evs 存储事件信息。ef_eventq_poll 的返回值为 0 时表示没有事件可处理。

我曾在接收端修改了缓冲区,在write函数中手动new了pkt_bufs,直接release了pkt_bufs,导致在接收部分ef_eventq_poll 的返回值为 0 时表示没有事件可处理。

pkt_buf = (struct pkt_buf*)(pkt_bufs + id * PKT_BUF_SIZE);

 根据事件的 id,从 pkt_bufs 数组中获取对应的接收缓冲区。

const char* buf = (const char*)pkt_buf + 64;
const int bufLen = EF_EVENT_RX_BYTES(evs);
if (bufLen != 0) {TORALEV2API::CTORATstpLev2MarketDataField temp_MarketDataField = {0};TORALEV2API::CTORATstpLev2OrderDetailField temp_OrderDetailField = {0};TORALEV2API::CTORATstpLev2TransactionField temp_TransactionField = {0};TORALEV2API::CTORATstpLev2XTSmergeField temp_SmergeField = {0};PARASE_CTORATstpLev2MarketDataField(buf+42, temp_MarketDataField, outFile1);
}
  • buf 指向接收到的数据包,从第 64 字节开始(可能是去除掉了 Ethernet 和 IP 头部),然后通过 EF_EVENT_RX_BYTES 获取数据包长度。之后,解析不同的字段数据并将其写入对应的输出流(例如 outFile1)。

printPacket(buf, bufLen);

 调用 printPacket 函数,打印数据包的内容,通常用于调试。

ef_vi_receive_post(&vi, pkt_buf->post_addr, id);

 调用 ef_vi_receive_post 完成数据包的处理,并将接收缓冲区重新发布。

 

 

 

 

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

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

相关文章

【ROS2】坐标TF入门

1、简介 1)定义: TF(TransForm)是用于坐标系管理的工具,它提供了快速、高效的坐标变换和时间同步功能。 2)坐标系 坐标系:在机器人系统中,存在多个坐标系,如基坐标系(Base Frame)、世界坐标系(World Frame)、工具坐标系、工件坐标系等。这些坐标系之间的关系在机…

剑指Offer 03比特位计数

只是记录 题目链接 题目链接 自己想出来的 第一种解法 思路简述 遍历[0,n]之间的数字&#xff0c;对于每一个数字按照二进制的方式展开&#xff0c;判断最低位置是否为1&#xff0c;若为1则1&#xff0c;反之不加&#xff0c;直到该数字等于0就停止。 public static int[] …

某j 全局控制输入框不能输入表情符

在FormItem.vue文件中的function handleRules()添加两处表情正则校验&#xff0c;代码如下 效果&#xff1a; function handleRules(): ValidationRule[] {const { rules: defRules [], component, rulesMessageJoinLabel, label, dynamicRules, required } props.schema;if…

java中File类

1、介绍 File类定义了一些与平台无关的方法来操作文件&#xff0c;可以通过调用File类中的方法&#xff0c;实现创建、删除、重命名文件等操作。File类的对象主要用来获取文件本身的一些信息&#xff0c;如文件所在的目录、文件长度、文件读写权限等。数据流可以将数据写入到文…

Linux高性能服务器编程 | 读书笔记 | 10. 高性能I/O框架库Libevent

10. 高性能I/O框架库Libevent Linux服务器程序必须处理三类事件&#xff08;I/O、信号和定时事件&#xff09;&#xff0c;在处理这三类事件时需要考虑以下问题&#xff1a; **统一事件源。**统一处理这三类事件既能使代码简单易懂&#xff0c;又能避免一些潜在的逻辑错误。实…

Javaweb web后端maven介绍作用安装

自动导入到这个项目 src是源代码 main主程序&#xff0c;核心代码 java是Java源代码 resources是项目配置文件 test测试相关的 maven概述 介绍 依赖在本地仓库查找&#xff0c;如果本地仓库有&#xff0c;用本地仓库的依赖&#xff0c;本地没有&#xff0c;连接中央仓库&…

wazuh-modules-sca-scan

sca模块主函数wm_sca_main -> wm_sca_start 检查policy文件中的每一个项目wm_sca_check_policy static int wm_sca_check_policy(const cJSON * const policy, const cJSON * const checks, OSHash *global_check_list) {if(!policy) {return 1;}const cJSON * const id c…

基于单片机的智能窗帘(论文+源码)

1.系统设计 本课题智能窗帘系统的设计主要包括STM32单片机主控模块&#xff0c;光照检测模块&#xff0c;窗帘控制模块&#xff0c;键盘控制模块&#xff0c;显示模块和时钟模块等几个部分。总体设计框图如图2.1所示&#xff0c;其可以实现对当前光照强度的实时检测&#xff0…

Fastdfs V6.12.1集群部署(arm/x86均可用)

文章目录 一、 Fastdfs 介绍二、部署 信息三、步骤tracker/storage 机器的 compose 内容storage 机器的 composetracker 与 storage 启动目录层级与配置文件测试测试集群扩容与缩减注意事项 一、 Fastdfs 介绍 FastDFS 是一款高性能的分布式文件系统&#xff0c;特别适合用于存…

零基础开始学习鸿蒙开发-基础页面的设计

目录 1.样例图 2.逐项分析 2.1 头顶布局分析&#xff1a;首先我们要把第一行的图标绘制出来&#xff0c;一个左一个右&#xff0c;很明显&#xff0c;需要放在一个Row容器中&#xff0c;具体代码如下&#xff1a; 2.2 和头像同一行的布局&#xff0c;需要注意的是&#xff0c…

如何用细节提升用户体验?

前端给用户反馈是提升用户体验的重要部分&#xff0c;根据场景选择不同的方式可以有效地提升产品的易用性和用户满意度。以下是常见的方法&#xff1a; 1. 视觉反馈 用户执行了某些操作后&#xff0c;需要即时确认操作结果。例如&#xff1a;按钮点击、数据提交、页面加载等。…

[数据结构#2] 图(1) | 概念 | 邻接矩阵 | 邻接表 | 模拟

图是由顶点集合及顶点间的关系&#xff08;边&#xff09;组成的数据结构&#xff0c;可用 G ( V , E ) G(V,E) G(V,E)表示&#xff0c;其中&#xff1a; 顶点集合 V V V: V { x ∣ x ∈ 某数据对象集 } V\{x|x\in\text{某数据对象集}\} V{x∣x∈某数据对象集}&#xff0c;…

学习maven(maven 项目模块化,继承,聚合)

前言 本篇博客的核心&#xff1a;理解maven 项目模块化&#xff0c;继承&#xff0c;聚合 的含义 maven 项目模块化 含义 maven项目模块化&#xff1a;使用maven 构建项目&#xff0c;管理项目的方式&#xff0c;我们可以将maven项目根据内在的关系拆分成很多个小项目【模块】…

【OJ题解】最长回文子串

个人主页: 起名字真南的CSDN博客 个人专栏: 【数据结构初阶】 &#x1f4d8; 基础数据结构【C语言】 &#x1f4bb; C语言编程技巧【C】 &#x1f680; 进阶C【OJ题解】 &#x1f4dd; 题解精讲 目录 **题目链接****解题思路****1. 初步判断****2. 回文子串性质****3. 判断是…

EMQX 可观测性最佳实践

EMQX 介绍 EMQX 是一款开源、高度可伸缩、高可用的分布式 MQTT 消息服务器&#xff0c;同时也支持 CoAP/LwM2M 等一站式 IoT 协议接入。以下是 EMQX 的一些主要特点和功能&#xff1a; 海量连接与高并发&#xff1a;EMQX 能够处理千万级别的并发客户端&#xff0c;支持大规模…

kubeadm_k8s_v1.31高可用部署教程

kubeadm_k8s_v1.31高可用部署教程 实验环境部署拓扑图**署架构方案****Load Balance****Control plane node****Worker node****资源分配&#xff08;8台虚拟机&#xff09;**集群列表 前置准备关闭swap开启ipv4转发更多设置 1、Verify the MAC address and product_uuid are u…

mysql flink cdc 实时数据抓取

背景 通过监控mysql日志&#xff0c;获取表字段更新&#xff0c;用来做实时展示。 使用技术&#xff1a;Flink CDC Flink CDC 基于数据库日志的 Change Data Caputre 技术&#xff0c;实现了全量和增量的一体化读取能力&#xff0c;并借助 Flink 优秀的管道能力和丰富的上下游…

element plus el-select修改后缀图标

<el-selectv-model"value"placeholder"请选择工点"size"large":teleported"false":suffix-icon"CaretBottom"style"width: 100px"><el-optionv-for"item in options":key"item.value&quo…

自动驾驶控制与规划——Project 2: 车辆横向控制

目录 零、任务介绍一、环境配置二、算法三、代码实现四、效果展示 零、任务介绍 补全src/ros-bridge/carla_shenlan_projects/carla_shenlan_stanley_pid_controller/src/stanley_controller.cpp中的TODO部分。 一、环境配置 上一次作业中没有配置docker使用gpu&#xff0c;…

Qt6开发自签名证书的https代理服务器

目标&#xff1a;制作一个具备类似Fiddler、Burpsuit、Wireshark的https协议代理抓包功能&#xff0c;但是集成到自己的app内&#xff0c;这样无需修改系统代理设置&#xff0c;使用QWebengineview通过自建的代理服务器&#xff0c;即可实现https包的实时监测、注入等自定义功能…