DPDK:UDP 协议栈的实现

news2024/9/30 7:32:55

文章目录

    • 1、DPDK 原理
      • 1.1、用户态驱动 IO
      • 1.2、内存池管理
    • 2、DPDK 启动设置
    • 3、DPDK:UDP 协议栈
      • 3.1、代码实现
      • 3.2、设置静态 arp
    • 4、DPDK:KNI
      • 4.1、代码实现
      • 4.2、程序测试

文章参考<零声教育>的C/C++linux服务期高级架构系统教程学习: 服务器高级架构体系
云技术的出现代表着网络功能虚拟化(NFV)共享硬件成为趋势,NFV的定义是通过标准的服务器、标准交换机实现各种传统的或新的网络功能。越来越多的网络设备基础架构开始逐步向基于通用处理器平台的方向发展。NFV 使得网络在变得更加可控制和成本更低的同时,也需要支持大规模用户或应用程序的性能需求,以及具备对大规模数据的处理能力。

传统的基于内核的报文处理方式通过中断将报文分发至内核,当服务器处理大规模报文时会产生频繁的中断,造成大量的性能开销。当内核协议栈处理报文完毕后,还涉及到将报文一次拷贝到用户层的操作。以上问题成为服务器处理大规模报文的阻碍。

高性能网络报文处理框架的理念是服务器上软件的方式进行优化,利用CPU多核对网络负载处理、依靠软件的方式旁路内核、通过轮询屏蔽中断,在用户态处理网络报文和业务。目前运行在用户态的高性能网络报文处理框架中,Intel 的 DPDK (数据平面开发套件,Data Plane Development Kit) 最早开源投入商用,应用也最为广泛,除此以外还有 wind、Netmap、PF_Ring、NBA、Snap 等一系列高性能报文处理框架。

DPDK 技术采用数据层与控制层分离的设计,在用户空间处理数据包、管理内存、调度处理器,而内核仅负责部分控制指令的处理。

1、DPDK 原理

DPDK 舍弃了内核中断,提供全用户态的驱动,拥有高效的内存管理机制,报文直接通过直接内存存取(DMA, direct memory access)传输到用户态处理,减少内存拷贝次数。

DPDK 旁路原理

在这里插入图片描述

左边:传统方式,数据从网卡 -> 驱动 -> 协议栈 -> socket 接口 -> 业务

右边:DPDK 方式,基于 UIO 旁路数据。数据从网卡 -> DPDK轮询模式-> DPDK基础库 -> 业务

DPDK 特点

  • UIO:用户态驱动 IO,将报文拷贝到用户空间
  • hugepage:巨页,通过大内存页提高内存使用效率。
  • cpu affinity:将线程绑定到 cpu 上,这样在线程执行过程中,就不会被随意调度,一方面减少了线程间的频繁切换带来的开销,另一方面避免了 CPU 缓存的局部失效性,增加了 CPU 缓存的命中率。
  • zero copy:减少数据拷贝次数

1.1、用户态驱动 IO

为减少中断开销,DPDK 抛弃了传统的内核中断,采用轮询模式驱动 (poll mode driver, PMD) 的方式直接操作网卡的接收和发送队列,将报文直接拷贝到用户空间,不再经过内核协议栈。

DPDK 的用户态 IO(user space I/O, UIO)驱动技术为 PMD 提供了支持。其主要功能是拦截中断,并重设中断回调行为,从而绕过内核协议栈的后续处理流程。

UIO 技术使得内核空间与用户空间的内存交互不用进行拷贝,而是只做控制权转移,减少了报文的拷贝过程。即具有零拷贝,无系统调用的好处,同步处理也减少了上下文切换带来的 cache miss。从而中断与拷贝中节省的资源和时延,有效地运行在报文处理流程中,提高了报文的处理、转发效率。

UIO 设备的实现机制其实是对用户空间暴露文件接口,比如当注册一个 UIO 设备 uioX,就会出现文件 /dev/uioX,对该文件的读写就是对设备内存的读写。除此之外,对设备的控制还可以通过 /sys/class/uio 下的各个文件的读写来完成。

在这里插入图片描述

1.2、内存池管理

Linux 使用 4kB 大小的分页来管理内存,而 DPDK 使用 2MB 或 1GB 的巨页来管理内存,一个页表缓存 TLB 表项可以指向更大的内存区域,从而减少了 TLB miss。

DPDK 对网络报文的内存操作对象是 Mbuf,Mbuf 存储在内存池 Mempool 中。内存池中的内存从巨页中提前分配,并且同时预先分配好指定大小的 Mbuf 对象。内存池采用双环形缓冲区来管理网络报文的生命处理周期,当一个网络报文被网卡接收后,DPDK 在 Mbuf 的环形缓冲中创建一个 Mbuf 对象。对网络报文的所有操作都集中在 Mbuf 对象中,仅在必要时对实际网络报文进行访问。也就是说,内核空间和用户空间的内存交互不进行拷贝,只做控制权转移。

2、DPDK 启动设置

环境监测

# 1、查询网卡信息,并检查是否能 ping 通
# 2、查看系统是否支持多队列网卡
cat /proc/interrupts | grep eth0

dpdk 启动设置

# 1、设置 dpdk 环境变量
export RTE_SDK=/home/king/share/dpdk/dpdk-stable-19.08.2
export RTE_TARGET=x86_64-native-linux-gcc

# 2、执行usertools的启动设置
./usertools/dpdk-setup.sh

# 选择 43,Insert IGB UIO module,选择网卡为 vmxnet3 会加载此模块
# 选择 44,Insert VFIO module,选择网卡为 e1000 会加载此模块
# 选择 45,Insert KNI module,写回内核

# 选择 46,Setup hugepage,512
# 选择 47,Setup hugepage,512

# 选择 49,绑定 igb_uio 模块
# 1、先宕掉 eth0 网卡
sudo ifconfig eth0 down 
# 2、绑定 pci

在这里插入图片描述

3、DPDK:UDP 协议栈

这里实现一个简单的 udp 协议栈,对网卡传递来的 udp 数据包做 echo 处理。

3.1、代码实现

dpdk 环境初始化

int main(int argc, char *argv[]) {
	// 1、eal 初始化
	if (rte_eal_init(argc, argv) < 0) {
		rte_exit(EXIT_FAILURE, "Error\n");
	}

	// 2、创建 mbuf 内存池
	// 内存池分配 MBUF_NUMBER 个 mbuf,mbuf 大小默认固定为 2048(data_room_size指定),牺牲内存空间提高吞吐量 
	struct rte_mempool *mbuf_pool = rte_pktmbuf_pool_create("mbufpool", MBUF_NUMBER, 0, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
	if (!mbuf_pool) {
        rte_exit(EXIT_FAILURE, "mbuf Error\n");
	}

	// 3、配置网卡设备
	// 设置接收和发送队列数量
	uint16_t nb_rx_queues = 1;
	uint16_t nb_tx_queues = 1;
	
	// 创建网卡配置文件
	const struct rte_eth_conf port_conf_default = {
		.rxmode = {.max_rx_pkt_len = RTE_ETHER_MAX_LEN }
	};

	// 配置网卡设备
	rte_eth_dev_configure(gDpdkPortId, nb_rx_queues, nb_tx_queues, &port_conf_default);

	// 创建网卡接收队列
	rte_eth_rx_queue_setup(gDpdkPortId, 0, 128, rte_eth_dev_socket_id(gDpdkPortId), NULL, mbuf_pool);
	// 创建网卡发送队列
	rte_eth_tx_queue_setup(gDpdkPortId, 0, 1024, rte_eth_dev_socket_id(gDpdkPortId), NULL);
	
	// 启动网卡设备
	rte_eth_dev_start(gDpdkPortId);
    ...
}

封装 udp 数据包

static struct rte_mbuf *alloc_udp_pkt(struct rte_mempool *pool, uint8_t *data, uint16_t length) {
	// 内存池分配一块固定大小的 mbuf,2048
	struct rte_mbuf *mbuf = rte_pktmbuf_alloc(pool);
	if (!mbuf) {
		rte_exit(EXIT_FAILURE, "rte_pktmbuf_alloc error\n");
	}
	// mbuf 包的长度:udp 包的长度 + ip 首部 + 以太网帧首部
	mbuf->pkt_len = length + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_ether_hdr);
	// mbuf 包的数据长度
	mbuf->data_len = length + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_ether_hdr);

	uint8_t *msg = rte_pktmbuf_mtod(mbuf, uint8_t*);
	
	// ether_hdr
	struct rte_ether_hdr *eth = (struct rte_ether_hdr *)msg;
	rte_memcpy(eth->s_addr.addr_bytes, gSrcMac, RTE_ETHER_ADDR_LEN);
	rte_memcpy(eth->d_addr.addr_bytes, gDstMac, RTE_ETHER_ADDR_LEN);
	eth->ether_type = htons(RTE_ETHER_TYPE_IPV4);

	// iphdr
	struct rte_ipv4_hdr *ip = (struct rte_ipv4_hdr *)(msg + sizeof(struct rte_ether_hdr));
	ip->version_ihl = 0x45;
	ip->type_of_service = 0;
	ip->total_length = htons(length + sizeof(struct rte_ipv4_hdr));
	ip->packet_id = 0;
	ip->fragment_offset = 0;
	ip->time_to_live = 64;
	ip->next_proto_id = IPPROTO_UDP;
	ip->src_addr = gSrcIp;
	ip->dst_addr = gDstIp;
	ip->hdr_checksum = 0;
	ip->hdr_checksum = rte_ipv4_cksum(ip);

	// udphdr
	struct rte_udp_hdr *udp = (struct rte_udp_hdr *)(msg + sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr));
	udp->src_port = gSrcPort;
	udp->dst_port = gDstPort;
	udp->dgram_len = htons(length);
	rte_memcpy((uint8_t*)(udp+1), data, length-sizeof(struct rte_udp_hdr));
	udp->dgram_cksum = 0;
	udp->dgram_cksum = rte_ipv4_udptcp_cksum(ip, udp);

	return mbuf;
}

从以太网 uio 网卡接收 udp 数据包,并转发回去。dpdk 的接收发送数据操作 burst 是内存操作,不存在 io 的阻塞问题。

int main(int argc, char *argv[]) {
	...
	// 业务逻辑:echo udp 数据包
	while (1) {
		unsigned num_recvd = 0;
		unsigned i = 0;
        
         // 逻辑 2:从以太网网卡读取数据,若是 udp 数据,则执行 echo 操作
		// 使用 mbuf 操作
		struct rte_mbuf *mbufs[MBUF_SIZE];
		// 从网卡接收队列接收数据:直接操作内存
		num_recvd = rte_eth_rx_burst(gDpdkPortId, 0, mbufs, MBUF_SIZE);
		if (num_recvd > MBUF_SIZE) {
			rte_exit(EXIT_FAILURE, "rte_eth_rx_burst Error\n");
		}
		
		for (i = 0; i < num_recvd; i++) {
			// 取出以太网帧头(返回 mbuf 结构体)
			struct rte_ether_hdr *ehdr = rte_pktmbuf_mtod(mbufs[i], struct rte_ether_hdr *);
			// 非 ip 数据包,丢弃处理
			if (ehdr->ether_type != rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) {
				continue;
			}

			// 取出 ip 首部(返回 mbuf 结构体偏移位置)
			struct rte_ipv4_hdr *iphdr = rte_pktmbuf_mtod_offset(mbufs[i], struct rte_ipv4_hdr *, sizeof(struct rte_ether_hdr));
			if (iphdr->next_proto_id == IPPROTO_UDP) {
				// 返回 udp 首部(iphdr + 1,偏移一个 ip 首部,即返回 udp 首部)
				struct rte_udp_hdr* udphdr = (struct rte_udp_hdr*)(iphdr + 1);	
				// 返回 udp 包的长度,避免长度为 0
                uint16_t length = ntohs(udphdr->dgram_len);
                *((char*) udphdr + length) = '\0';
                
				// 打印数据包的源 ip 和目的 ip
                struct in_addr addr;
                addr.s_addr = iphdr->src_addr;
                printf("src: %s:%d, ", inet_ntoa(addr), ntohs(udphdr->src_port));

                addr.s_addr = iphdr->dst_addr;
                printf("dst: %s:%d, %s\n", inet_ntoa(addr), ntohs(udphdr->dst_port), (char *)(udphdr + 1));

				// 转发 udp 数据包,实现 echo
				rte_memcpy(gSrcMac, ehdr->d_addr.addr_bytes, RTE_ETHER_ADDR_LEN);
				rte_memcpy(gDstMac, ehdr->s_addr.addr_bytes, RTE_ETHER_ADDR_LEN);
				
				rte_memcpy(&gSrcIp, &iphdr->dst_addr, sizeof(uint32_t));
				rte_memcpy(&gDstIp, &iphdr->src_addr, sizeof(uint32_t));

				rte_memcpy(&gSrcPort, &udphdr->dst_port, sizeof(uint16_t));
				rte_memcpy(&gDstPort, &udphdr->src_port, sizeof(uint16_t));

				// 封装 udp 数据包,udphdr+1 返回 udp 数据包的数据
				struct rte_mbuf *mbuf = alloc_udp_pkt(mbuf_pool, (uint8_t*)(udphdr + 1), length);

				// 发送 mbuf 数据
				rte_eth_tx_burst(gDpdkPortId, 0, &mbuf, 1);	
			} 
		}
	}
}

在测试 udp echo 时,由于自定义的协议栈没有实现 arp 协议的处理方法,需要对网卡设置静态 arp,才能进行 echo 测试。

3.2、设置静态 arp

# 以管理员的身份运行cmd
# 1、寻找要绑定主机的 ip 和 mac
arp -a
# 2、查看要进行 arp 绑定的网卡 idx 编号
netsh i i show in
# 3、arp 绑定:netsh -c i i add neighbors idx IP MAC
netsh -c i i add neighbors 10 192.168.0.104 00-0c-29-18-ef-9d

# arp 解除绑定:netsh -c i i delete neighbors idx
netsh -c i i delete neighbors 10

查看要进行 arp 绑定的网卡编号的方法如下:

在这里插入图片描述

若代码中实现 KNI,则无需配置静态 arp。

4、DPDK:KNI

在 udp 协议栈的基础上实现功能,其他类型数据包通过 KNI(内核网卡接口,Kernel NIC Interface)交给内核协议栈进行处理,并将内核协议栈处理后的结果返回给以太网网卡,发送出去。

在这里插入图片描述

这里存在两条数据流向

  • 从以太网网卡读取数据,若是 udp 数据,则执行 echo 操作;否则,转发到内核协议栈处理
  • 从内核协议栈读取数据,转发到以太网网卡接口,发送数据

4.1、代码实现

// 网卡配置接口
static int g_config_network_if(uint16_t port_id, uint8_t if_up) {
	if (!rte_eth_dev_is_valid_port(port_id)) {
		return -EINVAL;
	}

	int ret = 0;
	if (if_up) {
		rte_eth_dev_stop(port_id);
		ret = rte_eth_dev_start(port_id);
	} 
	else {
		rte_eth_dev_stop(port_id);
	}

	if (ret < 0) {
		printf("Failed to start port : %d\n", port_id);
	}

	return 0;
}

int main(int argc, char *argv[]) {
	...
	// 初始化 kni
	if (rte_kni_init(gDpdkPortId) == -1) {
		rte_exit(EXIT_FAILURE, "kni init failed\n");
	}

	// 定义 kni 配置
	struct rte_kni_conf conf;
	memset(&conf, 0, sizeof(conf));
	// 定义 kni 网卡名字,通过 ifconfig -a 命令查看 
	snprintf(conf.name, RTE_KNI_NAMESIZE, "vEth%d", gDpdkPortId);
	conf.group_id = gDpdkPortId;
	conf.mbuf_size = RTE_MBUF_DEFAULT_BUF_SIZE;
	// 获取以太网网卡 mac 地址
	rte_eth_macaddr_get(gDpdkPortId, (struct rte_ether_addr*)conf.mac_addr);
	// 获取以太网 mtu
	rte_eth_dev_get_mtu(gDpdkPortId, &conf.mtu);

	// 定义 kni 操作
	struct rte_kni_ops ops;
	memset(&ops, 0, sizeof(ops));
	ops.port_id = gDpdkPortId;
	ops.config_network_if = g_config_network_if;

	// 创建 kni 网卡接口,通过 mmap 映射到内存
	global_kni = rte_kni_alloc(mbuf_pool, &conf, &ops);
	
    ...
	// 业务逻辑:接收发送数据
	while (1) {
		// 逻辑 1:从内核协议栈读取数据,转发到以太网网卡接口,发送数据
		struct rte_mbuf *kni_burst[MBUF_SIZE];
		// 读取 kni 返回的内核协议栈读取的数据
		num_recvd = rte_kni_rx_burst(global_kni, kni_burst, MBUF_SIZE);
		if (num_recvd > MBUF_SIZE) {
			rte_exit(EXIT_FAILURE, "rte_kni_rx_burst Error\n");
		}

		// 向以太网网卡发送读取到的 kni 的数据
		unsigned nb_tx = rte_eth_tx_burst(gDpdkPortId, 0, kni_burst, num_recvd);
		
		// 未处理完的数据包的处理方式,可以再次转发,这里选择直接丢弃
		if (nb_tx < num_recvd) {
			// 将未转发的数据包释放掉
			for (i = nb_tx; i < num_recvd; i++) {
				rte_pktmbuf_free(kni_burst[i]);
				kni_burst[i] = NULL;
			}
		}
        ...
            
        // 逻辑2:从以太网网卡读取数据,判断数据类型
        // 若是 udp 数据,则执行 echo 操作;否则,转发到内核协议栈处理
		for (i = 0;i < num_recvd;i ++) {
			...
             // 其他类型的数据发送到 kni,交由内核协议栈处理(向内核写数据)
			else {
				rte_kni_tx_burst(global_kni, &mbufs[i], 1);
			}
		}
		// 处理 kni 请求
		rte_kni_handle_request(global_kni);
	}
}

4.2、程序测试

首先,为保证 kni 可以运行,需要开启向内核写入数据

echo 1 > /sys/devices/virtual/net/lo/carrier

为避免干扰,解除静态 arp 绑定

netsh -c i i delete neighbors 10

启动程序,ifconfig -a 可以查看 kni 网卡信息,名字为 vEth0

vEth0     Link encap:Ethernet  HWaddr 00:0c:29:18:ef:9d  
          BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:2 errors:0 dropped:2 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:120 (120.0 B)  TX bytes:0 (0.0 B)

为 vEth0 配置 ip 地址

ifconfig vEth0 192.168.0.105 up

捕获发送到 vEth0 (内核协议栈)的数据

tshark -i vEth0 icmp

远端执行 ping 命令,捕获到 kni 的 icmp 数据包,说明其他协议成功交给内核处理,并返回。

192.168.0.106 → 192.168.0.105 ICMP 74 Echo (ping) request  id=0x0001, seq=23/5888, ttl=64
192.168.0.105 → 192.168.0.106 ICMP 74 Echo (ping) reply    id=0x0001, seq=23/5888, ttl=64 (request in 57)

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

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

相关文章

缓存工作模型以及使用Redis为查询接口添加缓存

什么是缓存 缓存就是数据交换的缓冲区&#xff0c;是存储数据的临时地方&#xff0c;一般读写性能较高。在以前CPU需要将内存或磁盘中读到数据放寄存器才可以做运算&#xff0c;正是因此计算机运算的能力受到限制。为了解决&#xff0c;人们在CPU中设计了缓存&#xff0c;将一…

Word处理控件Aspose.Words功能演示:从 Java 中的 Word DOC 中提取图像

Aspose.Words 是一种高级Word文档处理API&#xff0c;用于执行各种文档管理和操作任务。API支持生成&#xff0c;修改&#xff0c;转换&#xff0c;呈现和打印文档&#xff0c;而无需在跨平台应用程序中直接使用Microsoft Word。此外&#xff0c; Aspose API支持流行文件格式处…

Minecraft 1.19.2 Fabric模组开发 01.eclipse 开发包构建教程

1.19.2的离线开发包附文末 我们本次来进行Minecraft 1.19.2 Fabric模组开发教程的介绍&#xff0c;首先我们需要下载eclipse和openJDK eclipse下载 eclipse官网下载最新版 openJDK下载 下载最新版JDK 17.028 1.19.2 mdk下载 下载1.19.2MDK 注&#xff1a;1.19.2需要使…

OpenCV实战(6)——OpenCV策略设计模式

OpenCV实战&#xff08;6&#xff09;——OpenCV策略设计模式0. 前言1. 策略设计模式颜色识别1.1 颜色比较1.2 策略设计模式1.3 实现颜色比较1.4 ColorDetector 类1.4 计算两个颜色向量之间的距离2. 使用 OpenCV 函数3. 函子或函数对象4. OpenCV 算法的基类小结系列链接0. 前言…

一文带你攻克JDK新特性

1.Java8 新特性介绍 1.1 实验介绍 在国内&#xff0c;Java8 是当之无愧的普及率最高的 JDK 版本&#xff0c;从笔者工作开始&#xff0c; 就一直使用的是 JDK8 的版本&#xff0c;虽然现在 JDK19 即将面世&#xff0c;但是似乎依旧无法 动摇 JDK8 在国内的地位。这里面最主要…

leetcode-10:还原排列的最少操作步数

原题描述如下&#xff1a; 给你一个偶数 n​​​​​​ &#xff0c;已知存在一个长度为 n 的排列 perm &#xff0c;其中 perm[i] i​&#xff08;下标 从 0 开始 计数&#xff09;。 一步操作中&#xff0c;你将创建一个新数组 arr &#xff0c;对于每个 i &#xff1a; 如…

蓝桥杯-地宫取宝

算法分类&#xff1a; 动态规划 dp 问题描述 X 国王有一个地宫宝库&#xff0c;是 nm 个格子的矩阵&#xff0c;每个格子放一件宝贝&#xff0c;每个宝贝贴着价值标签。 地宫的入口在左上角&#xff0c;出口在右下角。 小明被带到地宫的入口&#xff0c;国王要求他只能向右…

使用DBeaver 连接时序数据库TDengine

介绍 TDengine 是一款高性能、分布式、支持 SQL 的时序数据库 (Database)。 DBeaver 是一款流行、开源的数据库管理工具以及 SQL 客户端&#xff0c;其功能强大&#xff0c;并且支持任何拥有 JDBC-Driver 的数据库&#xff08;这意味着几乎所有数据库都支持&#xff09;。 只…

Java面试常见问题-JVM篇

JVM面试问题汇总①什么是字节码&#xff0c;采用字节码的好处是什么⭐java类加载器有哪些⭐双亲委派模型⭐⭐GC是如何判断对象可以被回收总结了目前主流平台中常见的面试题&#xff0c;标⭐为重点&#xff01; 第一次更新节点&#xff1a;2023.1.8 什么是字节码&#xff0c;采用…

用nvidia-smi查看GPU的状态时,能耗pwr显示为ERR!

用nvidia-smi查看GPU的状态时&#xff0c;能耗pwr显示为ERR&#xff01; 解决方式&#xff1a; 以下代码查看具体的报错: dmesg -l err 如果有: NVRM:***说明硬件问题&#xff0c;需要更换 如果无错误&#xff0c;则参考如下&#xff1a; 1. 将你的工作站或者服务器报错的…

车载激光雷达赛道「新窗口」

车载激光雷达的降本逻辑&#xff0c;除了前装量产规模的加速&#xff0c;还有背后核心供应链的驱动。这也被视为激光雷达新周期的核心竞争力。 Lumotive是一家由微软创始人比尔盖茨投资的初创公司&#xff0c;为激光雷达公司提供基于波束转向技术的核心元器件&#xff0c;采用液…

【寒假每日一题】DAY1.水仙花数

一、题目描述 求0&#xff5e;100000之间的所有“水仙花数”并输出。 什么是水仙花数&#xff1a; “水仙花数”是指一个n位数&#xff0c;其各位数字的n次方之和确好等于该数本身&#xff0c;如:153&#xff1d;1^3&#xff0b;5^3&#xff0b;3^3&#xff0c;则153是一个“水…

前端vue中ts无法识别引入的vue文件,提示找不到xxx.vue模块的解决【引入新建页面或者通过router引入时报错】

一、文章引导 #mermaid-svg-7KdCeocZ1DbrWrKC {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-7KdCeocZ1DbrWrKC .error-icon{fill:#552222;}#mermaid-svg-7KdCeocZ1DbrWrKC .error-text{fill:#552222;stroke:#55222…

联合证券|“仰望”概念爆发,多股涨停!人气龙头股罕见“炸板”

今日涨停股中&#xff0c;以封单金额核算&#xff0c;今飞凯达、联泓新科、灵通动力等涨停板封单资金最多。 涨停家数回落 1月6日&#xff0c;沪深两市股价收盘涨停的有37只&#xff0c;跌停股有14只。下午盘面震动&#xff0c;炸板数量增多&#xff0c;兔宝宝、中远海科、久其…

64. 锚框

1. 锚框 锚框是用来预测真实的边缘框。 2. loU-交并比 ioU用来计算两个框之间的相似度 0表示无重叠&#xff0c;1表示重合 这是Jacquard指数的一个特殊情况 给定两个集合A和B&#xff1a; 杰卡德系数&#xff08;Jaccard&#xff09;可以衡量两组之间的相似性。 给定集合A和…

D. Boris and His Amazing Haircut(线段树)

传送门题意&#xff1a;给定长度为 n 的数组 A &#xff0c;代表 Boris 现在的头发长度&#xff0c;和一个长度为 n 的数组 B &#xff0c;代表他希望的发型的头发长度。理发师手里有 m 把剪刀&#xff0c;每个都只能用一次&#xff0c;剪刀的所剪的高度用 xi 给出。 对于每一把…

ASO优化之苹果和安卓的优化思路

大家都知道&#xff0c;ASO优化是指应用商店的搜索优化&#xff0c;其目的就是通过关键词的覆盖&#xff0c;让APP的搜索排名靠前&#xff0c;带来更多的曝光和用户下载量。 安卓ASO优化的思路: 1&#xff0c;关键词的覆盖。由于现在大部分的安卓商店为了商业化,所以后台几乎…

【C语言进阶】指针经典笔试题

作者:匿名者Unit 目录指针笔试题eg1.eg2.eg3.eg4.指针笔试题 eg1. 我们先来看第一题: int main() {int a[4] { 1, 2, 3, 4 };int *ptr1 (int *)(&a 1);int *ptr2 (int *)((int)a 1);printf( "%x,%x", ptr1[-1], *ptr2);return 0; }我们先来分析ptr1&…

初级篇Nginx笔记

第一章、Nginx的目录结构以及运行原理Nginx的目录结构[rootlocalhost ~]# tree /usr/local/nginx/usr/local/nginx├── client_body_temp # POST 大文件暂存目录├── conf # Nginx所有配置文件的目录│ ├── fastcgi.conf # fastcgi相关参数的配置文件│ ├── fastcgi.…

在maven项目当中创建第一个jdbc程序

大家好&#xff0c;今天给大家分享在Maven环境中创建jdbc程序 这是Maven项目的创建方式 跟着一步一步做就可以了 然后&#xff0c;创建好的Maven项目应该是这样的 导入相关的依赖 <dependencies><!-- https://mvnrepository.com/artifact/mysql/mysql-connector-ja…