DPDK实现的用户态协议栈(UDP)

news2024/10/6 0:35:17

DPDK实现的用户态协议栈

  • 背景
  • NIC与DPDK的比较
  • 环境配置
  • Windowe下配置静态IP表
  • 代码实现
  • 总结

背景

DPDK接管NIC之后,接收到的数据都是原始数据,要实现一个协议栈就必须解析协议包和打包协议包,DPDK提供了丰富的API可以使用。
以UDP协议为例,一个UDP协议包就包含了以太网头、ip头、udp头,之后才是内容。
以太网头由14个字节组成,包含了:源MAC地址(smac,占6字节)、目的MAC地址(dmac,占6字节)、协议类型(占2字节)。

    6 bytes      6 bytes   2 bytes
+------------+------------+--------+
|  smac      |   dmac     |  type  |
+------------+------------+--------+

IP头包含源ip、目的ip等,总共20字节。

NIC与DPDK的比较

DPDK相比NIC,它可以最大程度的使用网卡的性能。

NICDPDK
不支持huge page,主要是4K的page支持huge page,最高可达到4G
中断方式接收数据,适合少量数据情况轮询的方式接收数据,适合接收大数据
CPU参与数据拷贝通过DMA的方式零拷贝数据

所以,DPDK可以使网卡性能最大,而NIC不可以的原因:NIC不支持huge page,以4K的页利用中断的方式触发接收数据,数据传输时需要CPU参与拷贝,这是比不上DPDK的。

环境配置

(1)导出dpdk环境变量。

cd dpdk路径
# 如 dpdk/dpdk-stable-19.08.2/
# 切换root权限
sudo su 
export RTE_SDK=dpdk路径
export RTE_TARGET=x86_64-native-linux-gcc

(2)配置dpdk。

./usertools/dpdk-setup.sh

依次执行:
43(加载DPDK UIO 模块,即插入driver)
44(加载VFIO模块,也是一种driver)
45(加载KNI模块,将一些数据写回内核)
46(设置巨页,可以不需要频繁页交换,512)
47(设置巨页,可512)
49(执行之前需要eth0 down掉,执行sudo ifconfig eth0 down,使绑定dpdk)pci地址=对应eth0的(如0000:03:00.0)
60(退出)

Windowe下配置静态IP表

需要管理员权限。
(1)查看要加入的静态表接口

arp -a

示例结果如下,可以看到0x13这个位置,后面步骤用到。

接口: 192.168.2.130 --- 0x13
  Internet 地址         物理地址              类型
  192.168.0.20          00-17-16-07-b1-14     动态
  192.168.0.25          00-00-74-f8-0f-65     动态
  192.168.0.60          00-1e-67-6e-d4-c8     动态
  192.168.0.62          00-15-5d-00-29-01     动态
  192.168.0.80          00-00-5e-00-01-82     动态
  192.168.0.116         04-d4-c4-8f-03-d7     动态
  192.168.0.120         90-09-d0-0a-39-8b     动态
  192.168.0.128         18-c0-4d-5e-30-05     动态
  192.168.0.150         90-23-b4-b8-62-63     动态
  192.168.0.152         b8-cb-29-b1-82-5b     动态
  192.168.0.180         0c-c4-7a-79-21-8a     动态
  192.168.2.42          30-5a-3a-5a-63-cd     动态
  192.168.2.154         00-0e-c6-5c-39-34     动态
  192.168.2.227         18-c0-4d-de-e8-9d     动态
  192.168.3.111         30-b4-9e-76-e6-60     动态
  192.168.3.166         2c-56-dc-dc-d5-45     动态
  192.168.4.191         d8-5e-d3-20-7a-53     动态
  192.168.5.0           18-c0-4d-9b-65-fb     动态
  192.168.7.31          fc-aa-14-a2-e7-4a     动态
  192.168.7.98          18-c0-4d-de-dd-be     动态
  192.168.7.146         00-0c-29-39-a8-c4     动态
  192.168.7.234         18-c0-4d-cc-b7-da     动态
  192.168.7.248         d4-5d-64-d2-b7-23     动态
  192.168.7.253         50-81-40-f3-ed-90     动态
  192.168.8.1           70-8c-b6-ee-02-12     动态
  192.168.8.11          00-11-04-01-19-4d     动态
  192.168.8.17          00-11-04-01-01-c5     动态
  192.168.11.12         d4-5d-64-3c-5c-fa     动态
  192.168.11.21         e0-70-ea-f1-0b-77     动态
  192.168.11.45         0c-9d-92-85-52-d4     动态
  192.168.11.92         40-8d-5c-a8-08-00     动态
  192.168.11.95         04-42-1a-eb-b5-00     动态
  192.168.11.138        00-0e-c6-80-04-fa     动态
  192.168.11.202        98-29-a6-65-c9-2c     动态
  192.168.11.225        18-c0-4d-57-59-58     动态
  192.168.16.124        18-c0-4d-50-1e-da     动态
  192.168.17.140        d8-5e-d3-2a-56-78     动态
  192.168.17.174        70-5a-0f-4d-c7-e8     动态
  192.168.17.196        00-24-1d-9c-f2-15     动态
  192.168.17.199        38-d5-47-1c-5c-fb     动态
  192.168.20.188        e4-e7-49-ff-f0-9c     动态
  192.168.255.255       ff-ff-ff-ff-ff-ff     静态
  224.0.0.2             01-00-5e-00-00-02     静态
  224.0.0.22            01-00-5e-00-00-16     静态
  224.0.0.251           01-00-5e-00-00-fb     静态
  224.0.0.252           01-00-5e-00-00-fc     静态
  224.0.1.60            01-00-5e-00-01-3c     静态
  224.0.6.151           01-00-5e-00-06-97     静态
  224.100.100.100       01-00-5e-64-64-64     静态
  224.200.200.200       01-00-5e-48-c8-c8     静态
  229.111.112.12        01-00-5e-6f-70-0c     静态
  233.233.233.233       01-00-5e-69-e9-e9     静态
  234.200.200.200       01-00-5e-48-c8-c8     静态
  239.102.144.50        01-00-5e-66-90-32     静态
  239.192.152.143       01-00-5e-40-98-8f     静态
  239.193.3.64          01-00-5e-41-03-40     静态
  239.193.4.69          01-00-5e-41-04-45     静态
  239.193.5.133         01-00-5e-41-05-85     静态
  239.193.21.194        01-00-5e-41-15-c2     静态
  239.193.21.222        01-00-5e-41-15-de     静态
  239.193.21.241        01-00-5e-41-15-f1     静态
  239.255.102.18        01-00-5e-7f-66-12     静态
  239.255.255.250       01-00-5e-7f-ff-fa     静态
  239.255.255.251       01-00-5e-7f-ff-fb     静态
  239.255.255.253       01-00-5e-7f-ff-fd     静态
  239.255.255.254       01-00-5e-7f-ff-fe     静态

(2)查看适配器

netsh i i show in

示例结果如下:

Idx     Met         MTU          状态                名称
---  ----------  ----------  ------------  ---------------------------
  1          75  4294967295  connected     Loopback Pseudo-Interface 1
 19          35        1500  connected     以太网 2
  5          35        1500  connected     VMware Network Adapter VMnet1
 15          35        1500  connected     VMware Network Adapter VMnet8
 39          35        1500  connected     VMware Network Adapter VMnet2

可以看到,上面的0x13=19对应的网络接口是以太网。
(3)新添静态IP

netsh -c i i add neighbors 19 192.168.7.199 38-d5-47-1c-5c-fb 

注意要确定MAC地址的正确性。
(4)检查是否添加成功

arp -a

(5)如果需要清除静态表,执行:

netsh i i delete neighbors 接口号
# 比如18就是接口号

代码实现

示例代码主要是DPDK实现了UDP协议栈收发,发送的数据即为收到的数据。
(dpdk_udp.c)

#include <rte_eal.h>
#include <rte_ethdev.h>
#include <rte_mbuf.h>

#include <stdio.h>
#include <arpa/inet.h>


#define ENABLE_SEND	1

#define NUM_MBUFS (4096-1)

#define BURST_SIZE	32

int gDpdkPortId = 0; //

static const struct rte_eth_conf port_conf_default = {
	.rxmode = {.max_rx_pkt_len = RTE_ETHER_MAX_LEN }
};


#if ENABLE_SEND

// sender 
static uint32_t gSrcIp;
static uint32_t gDstIp;

static uint16_t gSrcPort;
static uint32_t gDstPort;

static uint8_t gSrcMac[RTE_ETHER_ADDR_LEN];
static uint8_t gDstMac[RTE_ETHER_ADDR_LEN];


#endif

//
static void ng_init_port(struct rte_mempool *mbuf_pool) {

	//1 count avail
	uint16_t nb_sys_ports= rte_eth_dev_count_avail(); //
	if (nb_sys_ports == 0) {
		rte_exit(EXIT_FAILURE, "No Supported eth found\n");
	}

	//1 
	struct rte_eth_dev_info dev_info;
	rte_eth_dev_info_get(gDpdkPortId, &dev_info); //

	//1 
	const int num_rx_queues = 1;
	const int num_tx_queues = 1;
	struct rte_eth_conf port_conf = port_conf_default;
	rte_eth_dev_configure(gDpdkPortId, num_rx_queues, num_tx_queues, &port_conf);

	//1 rx queue setup
	if (rte_eth_rx_queue_setup(gDpdkPortId, 0 , 1024, rte_eth_dev_socket_id(gDpdkPortId),NULL, mbuf_pool) < 0) {

		rte_exit(EXIT_FAILURE, "Could not setup RX queue\n");

	}

#if ENABLE_SEND
	struct rte_eth_txconf txq_conf = dev_info.default_txconf;
	txq_conf.offloads = port_conf.rxmode.offloads;
	if (rte_eth_tx_queue_setup(gDpdkPortId, 0 , 1024, 
		rte_eth_dev_socket_id(gDpdkPortId), &txq_conf) < 0) {

		rte_exit(EXIT_FAILURE, "Could not setup TX queue\n");

	}

#endif

	//1 start
	if (rte_eth_dev_start(gDpdkPortId) < 0 ) {
		rte_exit(EXIT_FAILURE, "Could not start\n");
	}

	rte_eth_promiscuous_enable( gDpdkPortId);

}


#if ENABLE_SEND

static int ng_encode_udp_pkt(uint8_t *msg, unsigned char *data, uint16_t total_len) {

	// encode 

	// 1 ethhdr
	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);
	

	// 2 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(total_len - sizeof(struct rte_ether_hdr));
	ip->packet_id = 0;
	ip->fragment_offset = 0;
	ip->time_to_live = 64; // ttl = 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);

	// 3 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;
	uint16_t udplen = total_len - sizeof(struct rte_ether_hdr) - sizeof(struct rte_ipv4_hdr);
	udp->dgram_len = htons(udplen);

	rte_memcpy((uint8_t*)(udp+1), data, udplen);

	udp->dgram_cksum = 0;
	udp->dgram_cksum = rte_ipv4_udptcp_cksum(ip, udp);

	struct in_addr addr;
	addr.s_addr = gSrcIp;
	printf(" --> src: %s:%d, ", inet_ntoa(addr), ntohs(gSrcPort));

	addr.s_addr = gDstIp;
	printf("dst: %s:%d\n", inet_ntoa(addr), ntohs(gDstPort));

	return 0;
}


static struct rte_mbuf * ng_send(struct rte_mempool *mbuf_pool, uint8_t *data, uint16_t length) {

	// mempool --> mbuf

	const unsigned total_len = length + 42;

	struct rte_mbuf *mbuf = rte_pktmbuf_alloc(mbuf_pool);
	if (!mbuf) {
		rte_exit(EXIT_FAILURE, "rte_pktmbuf_alloc\n");
	}
	mbuf->pkt_len = total_len;
	mbuf->data_len = total_len;

	uint8_t *pktdata = rte_pktmbuf_mtod(mbuf, uint8_t*);

	ng_encode_udp_pkt(pktdata, data, total_len);

	return mbuf;

}



#endif

int main(int argc, char *argv[]) {

	if (rte_eal_init(argc, argv) < 0) {
		rte_exit(EXIT_FAILURE, "Error with EAL init\n");
		
	}

	struct rte_mempool *mbuf_pool = rte_pktmbuf_pool_create("mbuf pool", NUM_MBUFS,
		0, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
	if (mbuf_pool == NULL) {
		rte_exit(EXIT_FAILURE, "Could not create mbuf pool\n");
	}

	ng_init_port(mbuf_pool);

	while (1) {

		struct rte_mbuf *mbufs[BURST_SIZE];
		unsigned num_recvd = rte_eth_rx_burst(gDpdkPortId, 0, mbufs, BURST_SIZE);
		if (num_recvd > BURST_SIZE) {
			rte_exit(EXIT_FAILURE, "Error receiving from eth\n");
		}

		unsigned i = 0;
		for (i = 0;i < num_recvd;i ++) {

			struct rte_ether_hdr *ehdr = rte_pktmbuf_mtod(mbufs[i], struct rte_ether_hdr*);
			if (ehdr->ether_type != rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) {
				continue;
			}

			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) {

				struct rte_udp_hdr *udphdr = (struct rte_udp_hdr *)(iphdr + 1);

#if ENABLE_SEND		// echo

				// mac exchange
				rte_memcpy(gDstMac, ehdr->s_addr.addr_bytes, RTE_ETHER_ADDR_LEN);
				rte_memcpy(gSrcMac, ehdr->d_addr.addr_bytes, RTE_ETHER_ADDR_LEN);
				// ip exchange

				rte_memcpy(&gSrcIp, &iphdr->dst_addr, sizeof(uint32_t));
				rte_memcpy(&gDstIp, &iphdr->src_addr, sizeof(uint32_t));
				// port exchange

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

#endif

				uint16_t length = ntohs(udphdr->dgram_len);
				*((char*)udphdr + length) = '\0';

				struct in_addr addr;
				addr.s_addr = iphdr->src_addr;
				printf("src: %s:%d, ", inet_ntoa(addr), udphdr->src_port);

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

#if ENABLE_SEND

				struct rte_mbuf *txbuf = ng_send(mbuf_pool, (unsigned char*)(udphdr+1), length);
				rte_eth_tx_burst(gDpdkPortId, 0, &txbuf, 1);

#endif

				rte_pktmbuf_free(mbufs[i]);
			} else {

			}
			
		}

	}

}

Makefle:

# binary name
APP = dpdk_udp

# all source are stored in SRCS-y
SRCS-y := dpdk_udp.c

# Build using pkg-config variables if possible
ifeq ($(shell pkg-config --exists libdpdk && echo 0),0)

all: shared
.PHONY: shared static
shared: build/$(APP)-shared
        ln -sf $(APP)-shared build/$(APP)
static: build/$(APP)-static
        ln -sf $(APP)-static build/$(APP)

PKGCONF=pkg-config --define-prefix

PC_FILE := $(shell $(PKGCONF) --path libdpdk)
CFLAGS += -O3 $(shell $(PKGCONF) --cflags libdpdk)
LDFLAGS_SHARED = $(shell $(PKGCONF) --libs libdpdk)
LDFLAGS_STATIC = -Wl,-Bstatic $(shell $(PKGCONF) --static --libs libdpdk)

build/$(APP)-shared: $(SRCS-y) Makefile $(PC_FILE) | build
        $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_SHARED)

build/$(APP)-static: $(SRCS-y) Makefile $(PC_FILE) | build
        $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_STATIC)

build:
        @mkdir -p $@

.PHONY: clean
clean:
        rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared
        test -d build && rmdir -p build || true

else

ifeq ($(RTE_SDK),)
$(error "Please define RTE_SDK environment variable")
endif

# Default target, detect a build directory, by looking for a path with a .config
RTE_TARGET ?= $(notdir $(abspath $(dir $(firstword $(wildcard $(RTE_SDK)/*/.config)))))

include $(RTE_SDK)/mk/rte.vars.mk

总结

  1. DPDK不保证数据不丢失,网卡有什么数据DPDK就接收什么数据,组织了什么数据就发送什么数据;DPDK是不能保证数据传输过程中的是否丢包问题(链路路由器等的问题);数据丢失与否由协议决定。
  2. DPDK关于内存利用率的考量,DPDK主要是为大量IO吞吐量设计的内存池,通过牺牲内存来提升包的吞吐性能,达到大的吞吐量;DPDK是为提升网卡性能而生的。
  3. 一般对内存池的设计,至少要有两种以上的方案,因为要考虑某些情况下的取舍问题,这也是一种设计理念(对用途有一定充分的思考)。

在这里插入图片描述

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

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

相关文章

redis分布式集群

文章目录一、redis持久化1.1.RDB持久化1.1.1.执行时机1.1.2.RDB原理1.1.3.小结1.2.AOF持久化1.2.1.AOF原理1.2.2.AOF配置1.2.3.AOF文件重写1.2.4.小结1.3.RDB与AOF对比二、Redis主从集群2.1.集群结构2.2.准备实例和配置2.3.启动2.4.开启主从关系2.5.测试2.6.主从数据同步原理2.…

MMLAB学习笔记-DAY1

一、机器学习 1.机器学习的典型范式 监督学习&#xff1a;数据是由人工标注的&#xff0c;数据之间存在某种映射关系&#xff0c;目的是让机器学习到数据和标签之间的关系无监督学习&#xff1a;数据是没有标签的&#xff0c;通过对数据分析&#xff0c;运用聚类等方法探索出…

六、循环语句

一、while循环 1.语法 while 条件:条件成⽴重复执⾏的代码1条件成⽴重复执⾏的代码2.....2.应用 #偶数累加 i 1 resualt 0while i<100:if i % 2 0:resualt ii1print(resualt)3.break和continue 说明&#xff1a; 举例&#xff1a;⼀共吃5个苹果&#xff0c;吃完第⼀个&…

如何又快又好实现 Catalog 系统搜索能力?火山引擎 DataLeap 这样做

摘要 DataLeap 是火山引擎数智平台 VeDI 旗下的大数据研发治理套件产品&#xff0c;帮助用户快速完成数据集成、开发、运维、治理、资产、安全等全套数据中台建设&#xff0c;降低工作成本和数据维护成本、挖掘数据价值、为企业决策提供数据支撑。 火山引擎 DataLeap 的 Data…

Spring Boot + WebSocket 实时监控异常

本文已经收录到Github仓库&#xff0c;该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点&#xff0c;欢迎star~ Github地址&#xff1a;https://github.com/…

【笔记】容器基础-隔离与限制

Docker 项目的核心原理&#xff1a;为待创建的用户进程 1.启用 Linux Namespace 配置&#xff1a;修改进程视图 2.设置指定的 Cgroups 参数&#xff1a;为进程设置资源限制 3.切换进程的根目录&#xff08;Change Root&#xff09;&#xff1a; 容器的隔离与限制 1.启用 Linux…

MySQL性能优化四 MySQL索引优化实战一

一 查询案例 示例表 CREATE TABLE employees (id int(11) NOT NULL AUTO_INCREMENT,name varchar(24) NOT NULL DEFAULT COMMENT 姓名,age int(11) NOT NULL DEFAULT 0 COMMENT 年龄,position varchar(20) NOT NULL DEFAULT COMMENT 职位,hire_time timestamp NOT NULL DEF…

王凤英,能治好何小鹏的技术“自恋”吗?

1月30日&#xff0c;小鹏官宣一手打造长城汽车(601633)SUV战略转型的前二号人物——王凤英&#xff0c;加盟小鹏出任CEO一职。 虽然这则消息已风传多日&#xff0c;但正式公布的一刻还是在汽车圈内炸开了锅&#xff0c;主要原因有两点&#xff1a;一是王凤英刚刚加入小鹏就被委…

2-1JVM内存分析

一、java类的生命周期 1.加载(把class文件的数据加载到jvm内存的元空间) 2.连接验证 验证语法是否正确准备 给静态变量做内存分配和默认值分配识别 解析常量池 3.初始化静态变量赋初始值静态代码块执行 4.使用(被jvm使用) 5.卸载(如果在程序中没有再使用到这个类,这个类会被从…

跳槽前恶补面试题,成功上岸华为,拿到33k的测开offer

不知不觉间&#xff0c;时间过得真快啊。作为一名程序员&#xff0c;应该都清楚每年的3、4月份和9、10月份都是跳槽的黄金季&#xff0c;各大企业在这段时间会大量招聘人才。在这段时间里&#xff0c;有人欢喜有人悲。想必各位在跳槽前都会做好充足的准备&#xff0c;同样做足了…

还是你厉害啊,用 Python 下载高清视频真速度

今天我们来进行 Python 爬虫实战&#xff0c;学以致用嘛&#xff0c;这也是咱们不断学习的动力&#xff01; 我们要爬取的网站是YY直播&#xff0c;不知道有多少朋友知道&#xff0c;反正小编以前是不知道的&#xff0c;真的不知道~ 那么为什么我们选择这个网站呢&#xff0…

【5.1】Nacos注册中心--认识和安装Nacos/快速入门

【5.1】Nacos注册中心--认识和安装Nacos/快速入门1 认识Nacos2 安装Nacos3 服务注册到Nacos4 总结1 认识Nacos Nacos是阿里巴巴的产品&#xff0c;现在是SpringCloud中的一个组件。相比Eureka功能更加丰富&#xff0c;在国内受欢迎程度较高。 2 安装Nacos 建议大家下载Typora之…

Grafana 系列文章(五):Grafana Explore 查询管理

&#x1f449;️URL: https://grafana.com/docs/grafana/latest/explore/query-management/ &#x1f4dd;Description: Explore 中的查询管理 为了帮助调试查询&#xff0c;Explore 允许你调查查询请求和响应&#xff0c;以及查询统计数据&#xff0c;... Explore 中的查询管理…

CTFshow_萌新--密码篇

一、萌新认证进群大喊萌新码&#xff0c;即可获得。。。。。二、萌新密码1密文&#xff1a;53316C6B5A6A42684D3256695A44566A4E47526A4D5459774C5556375A6D49324D32566C4D4449354F4749345A6A526B4F48303D并给上了一下工具包。①密文首先Hex解码得到串&#xff1a;S1lkZjBhM2Vi…

项目管理工具——Maven

目录儿一、Maven简介二、下载与安装环境配置三、Maven基础概念3.1 仓库3.2 坐标在中央仓库网获取依赖坐标3.3 本地仓库配置3.4 远程仓库配置一、Maven简介 Maven是用java语言编写的。Maven的本质是一个项目管理工具&#xff0c;将项目开发和管理过程抽象成一个项目对象模型(PO…

商城项目的表设计

零、前言 1、优惠卷设计 电商项目中的优惠券系统这样设计&#xff0c;同事直呼 666 &#xff01; 2、SPU和SKU的定义及他们之间的关系 SPU全称Standard Product Unit&#xff0c;即标准化产品单元。 简单理解就是某一种产品。 SKU全称Stock Keeping Unit&#xff0c;即库存量…

PHP多进程(三) 理解多进程

本篇是一个过渡篇 ( 重在理解多进程 以及进程执行过程和进程执行后的数据 ) 废话不多说直接上代码 运行下面代码前 可以先想想有几个进程以及$count 是多少? <?php// 开始多进程 $count 10; for ($i0;$i<2;$i){epd(我是循环记数值.$i);$pid pcntl_fork(); // fork…

使用DBeaver 编辑链接达梦数据库

1、点击 “数据库”&#xff0c;选择“驱动管理器” 2、选择“新建” 3、 设置驱动 驱动名称&#xff1a;自定义 这里命名为“DM” 类名&#xff1a; dm.jdbc.driver.DmDriver URL模板&#xff1a;jdbc:dm://{host}:{port} 或jdbc:dm://{host}[:{port}]/[{database}] 默认端…

根据java反射-手写springIoC

我们都知道&#xff0c;Spring框架的IOC是基于Java反射机制实现的&#xff0c;下面我们先回顾一下java反射。 回顾Java反射 Java反射机制是在运行状态中&#xff0c;对于任意一个类&#xff0c;都能够知道这个类的所有属性和方法&#xff1b;对于任意一个对象&#xff0c;都能…