2.8 基于DPDK的UDP用户态协议栈实现

news2024/11/17 16:36:45

目录

  • 一、网络协议栈
    • 1、**网络通信过程**
    • 2、**dpdk**
  • 二、dpdk环境
    • 1、dpdk环境开启
    • 2、Windowe下配置IP和MAC地址的映射
  • 三、实现用户态协议栈ustack
    • 1、UDP协议
    • 2、代码
  • 四、dpdk一些基本函数接口
    • rte_eal_init()
    • rte_pktmbuf_pool_create()
    • rte_socket_id()
    • rte_eth_dev_configure()
    • rte_eth_rx_queue_setup()
    • rte_eth_tx_queue_setup ()
    • rte_eth_dev_socket_id()
    • rte_eth_dev_start()
    • rte_eth_rx_burst()
    • rte_eth_tx_burst()
    • rte_pktmbuf_mtod_offset()
    • rte_pktmbuf_mtod()
    • rte_pktmbuf_alloc()
    • rte_memcpy()
    • rte_eth_dev_info_get()
    • 以太网协议的头部 rte_ether_hdr
    • IPv4 协议的头部 rte_ipv4_hdr
    • UDP协议的头部 rte_udp_hdr
    • 小问题


一、网络协议栈

如何实现网络协议栈,首先需要拿到网络数据,有以下几种方式
1)原生socket
2)netmap
3)dpdk

1、网络通信过程

在这里插入图片描述
物理网卡将模拟信号转化为数据信号包;
NIC为网卡过来的数据包分配一个数据结构sk_buffer,
指出数据包中以太网头、IP头等信息的位置;
协议栈根据sk_buffer解析、处理数据包;
VFS作为接口(如socket),便于应用对数据包进行读、写等操作。在这里插入图片描述

2、dpdk

dpdk本质是接管网卡到驱动的通信,交由dbdk自己处理。
有两种处理方式:1、基于dbdk实现自己的用户态协议栈;2、将数据继续交与内核协议栈处理。

1)dpdk是否有助于提高网络并发量?
答案:否。回顾之前做百万并发的时候,主要在于网络连接的优化。因此并发主要跟协议栈有关。
2)能否通过dpdk解决低延迟的问题?
答案:否。延迟主要是业务引起的。

二、dpdk环境

1、dpdk环境开启

cd dpdk路径
sudo su
#export RTE_SDK=dpdk路径
export RTE_SDK=/home/king/share/dpdk/dpdk-stable-19.08.2/   
export RTE_TARGET=x86_64-native-linux-gcc
./usertools/dpdk-setup.sh

依次执行:
43(加载IGB UIO module,是一种drive,dpdk接管网卡的方式)
44(加载VFIO module,是一种driver,dpdk接管网卡的方式)
45(加载KNI module,是内核网络接口,将数据写回内核协议栈)
46(设置巨页,可以不需要频繁页交换),可输入512
47(设置巨页),可输入512
49(执行之前需要eth0 down掉,执行sudo ifconfig eth0 down)pci地址=对应eth0的(如0000:03:00.0)
60(退出)

至此,dpdk接管了物理网卡。

2、Windowe下配置IP和MAC地址的映射

以管理员权限运行cmd
1)查看静态表接口

arp -a

2)查看适配器

netsh i i show in

关注结果中的以太网对应得Idx=12

Idx     Met         MTU          状态                名称
---  ----------  ----------  ------------  ---------------------------
  1          75  4294967295  connected     Loopback Pseudo-Interface 1
 14          40        1500  connected     WLAN
 22          25        1500  disconnected  本地连接* 9
 17          25        1500  disconnected  本地连接* 10
 11          65        1500  disconnected  蓝牙网络连接
  2          35        1500  connected     VMware Network Adapter VMnet1
 21          35        1500  connected     VMware Network Adapter VMnet8
 12          25        1500  connected     以太网 3

3)新添静态IP

netsh -c i i add neighbors Idx IP地址 Mac地址
// netsh -c i i add neighbors 12 192.168.42.133 00-0c-29-54-10-bb

4)通过arp -a检查是否添加成功
5)清除静态表

netsh i i delete neighbors Idx

三、实现用户态协议栈ustack

接下来编写ustack,大致流程是
1)初始化:rte_eal_init()
2)创建一个创建内存池mbuf_pool:rte_pktmbuf_pool_create()
3)配置以太网设备,包括配置队列的个数、接口的配置信息:rte_eth_dev_configure()
4)分配并设置以太网设备的接收队列:rte_eth_rx_queue_setup()
5)分配并设置以太网设备的发送队列:rte_eth_tx_queue_setup()
6)启动以太网设备:rte_eth_dev_start()
7)从以太网接口接收数据包:rte_eth_rx_burst()

1、UDP协议

用户层经过网络各层,分别会在数据包前面添加
(UDP)协议首部,用于支持UDP的无连接、高效传输
IP首部:用于使数据能在互联网中传输,也就是能被路由器转发
以太网帧首部:用于使帧能够在一段链路或者网络上传输,被相应的目的主机接收。
在这里插入图片描述

							dp pkt
-------------------------------------------------------------------
| rte_ether_hdr | rte_ipv4_hdr | rte_udp_hdr |        data        |

2、代码

在这里插入图片描述



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

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

//UDP协议发送
#define ENABLE_SEND		1  


#define NUM_MBUFS (4096-1)

#define BURST_SIZE	32


#if ENABLE_SEND

static uint32_t gSrcIp; //源 IP 地址
static uint32_t gDstIp; //目的 IP 地址

static uint8_t gSrcMac[RTE_ETHER_ADDR_LEN]; //源 MAC 地址
static uint8_t gDstMac[RTE_ETHER_ADDR_LEN];

static uint16_t gSrcPort;	//UDP 数据包的源端口号
static uint16_t gDstPort;

#endif


int gDpdkPortId = 0;


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

//初始化以太网设备
static void ng_init_port(struct rte_mempool *mbuf_pool) {

    //返回可用的 Ethernet 设备数量
	uint16_t nb_sys_ports= rte_eth_dev_count_avail(); 
	if (nb_sys_ports == 0) {
		rte_exit(EXIT_FAILURE, "No Supported eth found\n");
	}

    //获取指定 Ethernet 设备的设备信息
	struct rte_eth_dev_info dev_info;
	rte_eth_dev_info_get(gDpdkPortId, &dev_info); //
	
    //配置以太网设备
	const int num_rx_queues = 1;    //接收队列数量
	const int num_tx_queues = 1;    //发送队列数量 
	struct rte_eth_conf port_conf = port_conf_default;	//struct rte_eth_conf 结构体包含了各种网卡相关的配置参数和属性
	rte_eth_dev_configure(gDpdkPortId, num_rx_queues, num_tx_queues, &port_conf);

	//分配并设置以太网设备的接收队列
	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; //struct rte_eth_txconf 结构体中包含了各种发送队列相关的配置参数和属性
	txq_conf.offloads = port_conf.rxmode.offloads; //使用 port_conf.rxmode.offloads 来初始化 txq_conf.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

	//启动以太网设备。
	if (rte_eth_dev_start(gDpdkPortId) < 0 ) {
		rte_exit(EXIT_FAILURE, "Could not start\n");
	}

}

/*将负载数据封装成UDP数据包 */
static int ng_encode_udp_pkt(uint8_t *msg, unsigned char *data, uint16_t total_len) {

	// 以太网头
	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);
	

	// IPV4 头
	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);

	// UDP 头
	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);
	
	//要先填充 UDP 报文,再进行校验和计算
	rte_memcpy((uint8_t*)(udp+1), data, udplen);
	udp->dgram_cksum = 0;
	udp->dgram_cksum = rte_ipv4_udptcp_cksum(ip, udp);


	printf("````````````````````send````````````````````\n");

	struct in_addr addr;
	addr.s_addr = gSrcIp;
	printf("源IP和端口: %s:%d, ", inet_ntoa(addr), ntohs(gSrcPort));

	addr.s_addr = gDstIp;
	printf("目的IP和端口: %s:%d\n", inet_ntoa(addr), ntohs(gDstPort));

	return 0;
}

/*将待发送的负载数据封装成UDP数据包,存入rte_mbuf
参数;内存池、负载数据的指针、负载数据的大小 */
static struct rte_mbuf * ng_send(struct rte_mempool *mbuf_pool, uint8_t *data, uint16_t length) {

	//计算 UDP 数据包的总长度 = 以太网头部(14字节)+ IPv4头部(20字节) + UDP头部(8字节) + 负载数据
	// const unsigned total_len = length + 42;
	uint16_t total_len = length + sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_udp_hdr);

	//分配一个 rte_mbuf 数据包缓冲区
	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;

	//将 mbuf 转换成一个 uint8_t* 类型的指针变量pktdata,并返回缓冲区数据的起始地址
	uint8_t *pktdata = rte_pktmbuf_mtod(mbuf, uint8_t*);

	//将负载数据封装成UDP数据包
	ng_encode_udp_pkt(pktdata, data, total_len);

	return mbuf;

}


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

	//初始化EAL环境
	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);

	//rte_eth_macaddr_get(gDpdkPortId, (struct rte_ether_addr *)gSrcMac);

	while (1) {

		//从以太网接口的接收队列中读取数据包,存入mbufs
		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 ++) {
			/*
							udp pkt

				-------------------------------------------------------------------
				| rte_ether_hdr | rte_ipv4_hdr | rte_udp_hdr |        data        |
				
			*/
			//获取以太网帧头部
			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)) { //判断以太网帧中的网络层协议是否为IPv4协议
				continue;
			}

			//获取IP头
			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,IPPROTO_UDP是一个常量,代表传输控制协议(UDP)的协议编号

				//获取udp头
				struct rte_udp_hdr *udphdr = (struct rte_udp_hdr *)(iphdr + 1);

				//在负载数据末尾插入了一个字符串结束符 '\0'
				uint16_t length = ntohs(udphdr->dgram_len);
				*((char*)udphdr + length) = '\0'; 

				printf("````````````````````receive````````````````````\n");

				struct in_addr addr;
				addr.s_addr = iphdr->src_addr; //源IP地址
				printf("源IP和端口: %s:%d, ", inet_ntoa(addr), ntohs(udphdr->src_port)); 

				addr.s_addr = iphdr->dst_addr; //目的IP地址
				printf("目的IP和端口: %s:%d,  接收数据: %s\n", inet_ntoa(addr), ntohs(udphdr->dst_port), 
					(char *)(udphdr+1));

#if ENABLE_SEND
				//接收和回传时候,源地址和目的地址,是相反的
				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数据包
				struct rte_mbuf *txbuf = ng_send(mbuf_pool, (uint8_t *)(udphdr+1), length - sizeof(struct rte_udp_hdr));

				//发送数据报
				rte_eth_tx_burst(gDpdkPortId, 0, &txbuf, 1);
				rte_pktmbuf_free(txbuf);
				
#endif

				rte_pktmbuf_free(mbufs[i]);
			}

			
		}

	}

}

四、dpdk一些基本函数接口

rte_eal_init()

初始化环境抽象层,在DPDK应用程序中使用DPDK库之前,必须首先调用rte_eal_init函数进行初始化。

#include <rte_eal.h>
int rte_eal_init(int argc, char **argv);

argc和argv参数分别表示应用程序的命令行参数数量和参数列表
在初始化完成后,它会返回0以表示初始化成功,或返回负值以表示初始化失败。

rte_pktmbuf_pool_create()

创建和初始化一个内存池,用于管理mempool对象中的mempool元素,同时用于存储和分发网络数据包的缓冲区。

#include <rte_mbuf.h>

struct rte_mempool* rte_pktmbuf_pool_create	(	
const char *name,
unsigned 	n,
unsigned 	cache_size,
uint16_t 	priv_size,
uint16_t 	data_room_size,
int 		socket_id 
);

其中,各参数的含义如下:
1)name: 内存池的名称
2)n: 内存池中最多可以包含的元素数量
3)cache_size: 每个CPU缓存队列的大小,用于提高缓存命中率和性能
4)priv_size: 每个元素的私有数据大小,通常为0或包含一些与网络协议相关的元数据
5)data_room_size: 每个元素中数据缓冲区的大小,用于存储接收到的网络数据包或构建要发送的数据包
6)socket_id: 内存池分配的NUMA节点,-1表示由系统自动选择

rte_socket_id()

获取当前线程或者任务所在的CPU socket的ID

int rte_socket_id(void);

获取当前线程或者任务所在的CPU socket的ID

rte_eth_dev_configure()

配置和初始化指定的以太网设备及其相关参数。

int rte_eth_dev_configure(	
	uint16_t 	port_id,
	uint16_t 	nb_rx_queue,
	uint16_t 	nb_tx_queue,
	const struct rte_eth_conf * 	eth_conf 
);

其中,各参数的含义如下:
1)port_id: 待配置的以太网设备端口号
2)nb_rx_queue: 接收队列数量
3)nb_tx_queue: 发送队列数量
4)eth_conf: 以太网设备配置参数,包括MAC地址、速率、MTU等

rte_eth_rx_queue_setup()

配置和启动指定以太网设备的接收队列.

int rte_eth_rx_queue_setup	(	
uint16_t 			port_id,
uint16_t 			rx_queue_id,
uint16_t 			nb_rx_desc,
unsigned int 		socket_id,
const struct rte_eth_rxconf * 	rx_conf,
struct rte_mempool * mb_pool 
);

其中各参数的含义如下:
1)port_id:待配置的以太网设备端口号。
2)rx_queue_id:待配置的接收队列编号。
3)nb_rx_desc:接收队列中缓冲区描述符的数量,决定了队列的深度和性能。
4)socket_id:所属的NUMA节点,用于内存分配。如果为RTE_ETH_DEV_NO_NUMA_SOCKET,则表示不分配NUMA节点。
5)rx_conf:接收队列相关的配置参数。
6)mb_pool:接收队列使用的内存池,用于存储接收到的数据包。

rte_eth_tx_queue_setup ()

配置和启动指定以太网设备的发送队列

int rte_eth_tx_queue_setup	(	
	uint16_t 		port_id,
	uint16_t 		tx_queue_id,
	uint16_t 		nb_tx_desc,
	unsigned int 	socket_id,
	const struct rte_eth_txconf * 	tx_conf 
);

其中各参数的含义如下:
1)port_id:待配置的以太网设备端口号。
2)tx_queue_id:待配置的发送队列编号。
3)nb_tx_desc:发送队列中缓冲区描述符的数量,决定了队列的深度和性能。
4)socket_id:所属的NUMA节点,用于内存分配。如果为RTE_ETH_DEV_NO_NUMA_SOCKET,则表示不分配NUMA节点。
5)tx_conf:发送队列相关的配置参数。

rte_eth_dev_socket_id()

获取指定以太网设备所使用的 NUMA节点编号

int rte_eth_dev_socket_id( uint16_t 	port_id	)	

rte_eth_dev_start()

启动以太网设备。

int rte_eth_dev_start(uint16_t port_id);

rte_eth_rx_burst()

从指定的以太网设备的接收队列中获取数据包。

static uint16_t rte_eth_rx_burst	(	
	uint16_t 			port_id,
	uint16_t 			queue_id,
	struct rte_mbuf ** 	rx_pkts,
	const uint16_t 		nb_pkts 
);

其中各参数的含义如下:
1)port_id:待读取数据包的以太网设备端口号。
2)queue_id:待读取数据包的接收队列编号。
3)rx_pkts:用于存储读取数据包的缓冲区数组指针。
4)nb_pkts:缓冲区数组中可存储的最大数据包数量。
返回实际检索到的数据包数

rte_eth_tx_burst()

用于发送数据报.

static uint16_t rte_eth_tx_burst(	
	uint16_t 	port_id,
	uint16_t 	queue_id,
	struct rte_mbuf ** 	tx_pkts,
	uint16_t 	nb_pkts 
);

1)port_id: 要发送数据报的端口 ID。
2)queue_id: 要发送数据报的队列 ID。
3)tx_pkts: 一个指向 rte_mbuf 结构体指针数组的指针,数组中包含要发送的数据报。
4)nb_pkts: 要发送的数据报的数量。
返回实际发送的数据包数

rte_pktmbuf_mtod_offset()

将struct rte_mbuf类型的数据包缓冲区对象转换为指向偏移量处的缓冲区数据的指针。

#define rte_pktmbuf_mtod_offset	(m,t,o)	  ((t)(void *)((char *)(m)->buf_addr + (m)->data_off + (o)))

其中各参数的含义如下:
1)m:待转换为指针的rte_mbuf结构体对象。
2)t:指向缓冲区数据的指针类型,即要强制转换的类型。
3)o:偏移量。

rte_pktmbuf_mtod()

#define rte_pktmbuf_mtod(m,t ) rte_pktmbuf_mtod_offset(m, t, 0)

rte_pktmbuf_alloc()

用于分配一个rte_mbuf类型的缓冲区,可以用来存储网络数据包。需要注意的是,rte_mbuf缓冲区分为两部分:一个是元数据,主要用于存储关于包的信息,比如包的长度、IP地址等;另一个是包的实际数据,主要用于存储网络数据包的内容。

static struct rte_mbuf* rte_pktmbuf_alloc(struct rte_mempool *mp);

rte_memcpy()

用于将一个内存区域的数据内容复制到另一个内存区域。该函数与标准库函数 memcpy() 的语义相同,但是对于 DPDK 的应用场景做了一定的优化,可以提高拷贝性能。

void *rte_memcpy(void *dst, const void *src, size_t n);

其中,dst 是目标内存区域的指针,src 是源内存区域的指针,n 是拷贝的字节数。

rte_eth_dev_info_get()

获取指定 Ethernet 设备的设备信息。

void rte_eth_dev_info_get(uint16_t port_id, struct rte_eth_dev_info *dev_info);

该函数需要两个参数:
1)port_id:表示需要获取设备信息的端口 ID。
2)dev_info:一个指向 rte_eth_dev_info 结构体的指针。rte_eth_dev_info 结构体中包含了各种设备信息的字段,例如设备名称、设备类型、最大传输单元(MTU)大小等。

在调用 rte_eth_dev_info_get 函数之前,需要先声明一个 struct rte_eth_dev_info 类型的变量,以便将获取到的设备信息存储在其中。在调用函数时,需要将该变量的指针作为第二个参数传递给函数,以便函数可以将获取到的信息存储在该结构体中。

以太网协议的头部 rte_ether_hdr

#define RTE_ETHER_ADDR_LEN   6

typedef uint16_t rte_be16_t

struct rte_ether_addr{
	uint8_t 	addr_bytes [RTE_ETHER_ADDR_LEN];
};

struct rte_ether_hdr{
struct rte_ether_addr 	dst_addr;
struct rte_ether_addr 	src_addr;
rte_be16_t 				ether_type;
};

1)src_addr:一个长度为 6 字节的数组,表示源 MAC 地址;
2)dst_addr:一个长度为 6 字节的数组,表示目的 MAC 地址;
3)ether_type:一个 16 位无符号整数,表示以太网数据帧的类型,常见的有 IP 数据包、ARP 请求和响应等。

IPv4 协议的头部 rte_ipv4_hdr

/**
 * An IPv4 header.
 */
struct rte_ipv4_hdr {
    uint8_t version_ihl;    /**< version and header length */
    uint8_t type_of_service;/**< type of service */
    uint16_t total_length;  /**< length of packet including header */
    uint16_t packet_id;     /**< unique packet ID */
    uint16_t fragment_offset;/**< fragment offset field */
    uint8_t time_to_live;   /**< time to live */
    uint8_t next_proto_id;  /**< protocol ID */
    uint16_t hdr_checksum;  /**< header checksum */
    uint32_t src_addr;      /**< source address */
    uint32_t dst_addr;      /**< destination address */
} __attribute__((__packed__));

1)version_ihl:一个 8 位无符号整数,表示 IPv4 头部的版本和长度信息;
2)type_of_service:一个 8 位无符号整数,表示服务类型信息;
3)total_length:一个 16 位无符号整数,表示整个 IPv4 数据包的长度;
4)packet_id:一个 16 位无符号整数,表示数据包的唯一标识符;
5)fragment_offset:一个 16 位无符号整数,表示分段信息,常用于分片后的数据包重组;
6)time_to_live:一个 8 位无符号整数,表示数据包的生存时间;
7)next_proto_id:一个 8 位无符号整数,表示下一层协议的 ID,例如 TCP、UDP 等;
8)hdr_checksum:一个 16 位无符号整数,表示 IPv4 头部的校验和;
9)src_addr:一个 32 位无符号整数,表示源 IP 地址;
10)dst_addr:一个 32 位无符号整数,表示目的 IP 地址。

UDP协议的头部 rte_udp_hdr

 /**
 * A UDP header.
 */
struct rte_udp_hdr {
    uint16_t src_port;  /**< UDP source port. */
    uint16_t dst_port;  /**< UDP destination port. */
    uint16_t dgram_len; /**< Length of UDP datagram including header. */
    uint16_t dgram_cksum; /**< UDP datagram checksum (0 if not used). */
} __attribute__((__packed__));

1)src_port:一个 16 位无符号整数,表示 UDP 数据包的源端口号。
2)dst_port:一个 16 位无符号整数,表示 UDP 数据包的目的端口号。
3)dgram_len:一个 16 位无符号整数,表示 UDP 数据报的总长度(包括头部和负载数据)。
4)dgram_cksum:一个 16 位无符号整数,用于存储 UDP 数据包的校验和。如果该字段为 0,则表示数据包没有启用校验和。

小问题

uint8_t *和char *的区别和适用场景

uint8_t *char * 都是 C 语言中常用的指针类型,它们之间的主要区别在于指向的对象类型。uint8_t * 表示一个指向 unsigned char 类型数据的指针,而 char * 则表示一个指向 char 类型数据的指针。两者都占用1个字节(即8位)的内存空间,因此可以将它们视为等价的数据类型。
char * 指针适用于字符串操作、文件读写等普遍场景,而 uint8_t * 指针则适用于二进制数据处理、网络编程、加密解密等专用领域。
在这里插入图片描述

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

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

相关文章

Java连接MySQL对数据实现增删改查

在实现好的窗口实现 添加修改删除查询数据的方法 以如下数据实验 statement自带的函数使用说明execute &#xff08;SQL&#xff09;执行给定的SQL语句返回一个或多个结果结果集 execute方法应该仅在语句能返回多个ResultSet对象、多个更新计数或ResultSet对象与更新计数的组…

Verilog | 基4 booth乘法器

上接乘法器介绍 原理 跟基2的算法一样&#xff0c;假设A和B是乘数和被乘数&#xff0c;且有&#xff1a; A ( a 2 n 1 a 2 n ) a 2 n − 1 a 2 n − 2 … a 1 a 0 ( a − 1 ) B b 2 n − 1 b 2 n − 2 … b 1 b 0 \begin{align}A&(a_{2n1}a_{2n})a_{2n−1}a_{2n−2}……

安卓期末考试知识总结(3)

文章目录 第五章 数据存储文件存储(非重点)内部存储获取或者打开目录操作文件 外部存储区 SharedPreferences存储写入Shared Preferences读取数据 SQLite数据库SQLite数据库的创建操作数据库数据Curosr数据库的事务 第五章 数据存储 简述Android数据存储的方式 Android平台提供…

FreeRTOS:任务通知

目录 一、任务通知简介二、发送任务通知2.1 函数xTaskNotify()2.2函数xTaskNotifyFromISR()2.3函数xTaskNotifyGive()2.4函数vTaskNotifyGiveFromISR()2.5函数xTaskNotifyAndQuery()2.6函数xTaskNotifyAndQueryFromISR() 三、获取任务通知3.1函数ulTaskNotifyTake()3.2函数xTas…

[进阶]Java:打印流、Properties、common-io框架

打印流&#xff1a; 作用&#xff1a;打印流可以实现方便、高效的打印数据到文件中去。打印流一般是&#xff1a;PrintStream&#xff0c;PrintWriter两个类。可以实现打印什么数据就是什么数据&#xff0c;例如打印整数97写出去就是97&#xff0c;打印boolean的true&#xff…

chatgpt赋能python:Python截取某段文字的方法

Python截取某段文字的方法 在处理文本数据时&#xff0c;截取某段文字是常见需求。Python作为一门优秀的脚本语言&#xff0c;提供了多种方法来完成这个任务。本篇文章将介绍Python截取某段文字的几种方便易用的方法。 方法一&#xff1a;使用切片 Python中的切片操作可以方…

JavaScript的一些编程题分享

将字符串abc-def-ghi转换为驼峰格式 这里我们的思路是利用字符串方法和正则表达式 const str abc-def-ghi;const camelCaseStr str.replace(/[-_][^-_]/g, match > match.charAt(1).toUpperCase());console.log(camelCaseStr); // abcDefGhi 这里使用了 replace 方法&a…

haproxy

haproxy haproxy一&#xff1a;常见的Web集群调度器1.软件2.硬件3.LVS &#xff0c;Nginx &#xff0c;Haproxy 的区别&#xff1a; 二&#xff1a;Haproxy应用分析1.HAProxy的主要特性有&#xff1a;2.HAProxy负载均衡策略非常多&#xff0c;常见的有如下8种&#xff1a; 三&a…

MySQL8.0数据库开窗函数

简介 数据库开窗函数是一种在SQL中使用的函数&#xff0c;它可以用来对结果集中的数据进行分组和排序&#xff0c;以便更好地分析和处理数据。开窗函数与聚合函数不同&#xff0c;它不会将多行数据聚合成一行&#xff0c;而是保留每一行数据&#xff0c;并对其进行分组和排序。…

Linux中/dev/random和/dev/urandom的作用

1./dev/random和/dev/urandom介绍 在Linux环境中&#xff0c;我们会用到/dev/random和/dev/urandom&#xff0c;今天为大家讲讲/dev/random和/dev/urandom的作用以及使用场景。 1.1./dev/random介绍 /dev/random是一个特殊的字符设备文件&#xff0c;用于生成“高质量”的随…

Python面向对象编程1-面向过程的简单纸牌游戏程序 项目1.1 定义纸牌的花色和点数

总项目目标&#xff1a;用面向过程思想设计一个简单的纸牌游戏程序&#xff0c;称为"Higher or Lower"&#xff08;高还是低&#xff09;。游戏中&#xff0c;玩家需要猜测接下来的一张牌是比当前牌高还是低。根据猜测的准确性&#xff0c;玩家可以得到或失去相应的积…

Unity UGUI1——基础组件概述

一、UGUI 介绍 ​ UGUI 是 Unity 引擎内自带的 UI 系统&#xff0c;官方称之为&#xff1a;Unity UI ​ 是目前 Unity 商业游戏开发中使用最广泛的 UI 系统开发解决方案 ​ 它是基于 Unity 游戏对象的 UI 系统&#xff0c;只能用来做游戏 UI 功能 ​ 不能用于开发 Unity 编…

【MarkDown】CSDN Markdown之Git图gitGraph详解

Git图 Git图是对不同分支上的Git提交和Git操作&#xff08;命令&#xff09;的图形化表示。 这种类型的图特别适合开发人员和DevOps团队分享他们的Git分支策略。例如&#xff0c;它可以更容易地可视化git流的工作方式。 Mermaid可以呈现Git图,但是只有v10.2.3 才支持。 代码…

kubernets 笔记

kubernets 笔记 kubernets 安装 1. 环境准备 硬件要求 内存&#xff1a;2GBCPU&#xff1a;2 核硬盘&#xff1a;30GB 本次环境说明 操作系统&#xff1a;CentOS 7.9内核版本&#xff1a;3.10.0-1160.76.1.el7.x86_64k8s-m&#xff1a;192.168.222.3k8s-s01&#xff1a;192.…

安装Apache、MySQL、PHP、论坛实操

文章目录 一、安装Apache1、准备阶段2、开始安装3、浏览器访问验证 二、部署MySQL三、部署php四、部署BBS论坛 一、安装Apache 1、准备阶段 &#xff08;1&#xff09;准备源码包 httpd-2.4.29.tar.gz apr-1.6.2.tar.gz apr-util-1.6.0.tar.gz cd /opt tar xf apr-1.6.2.tar…

【Windows】创建Windows远程桌面快捷方式

【Windows】创建Windows远程桌面快捷方式 1、背景2、操作 1、背景 windows系统自带了远程连接工具&#xff0c;可以实现局域网内的远程控制&#xff0c;参考&#xff1a; 【Windows】局域网内远程桌面控制 https://blog.csdn.net/jn10010537/article/details/130926888 但是w…

【Leetcode -404.左子叶之和 -543.二叉树的直径】

Leetcode Leetcode -404.左子叶之和Leetcode -543.二叉树的直径 Leetcode -404.左子叶之和 题目&#xff1a;给定二叉树的根节点 root &#xff0c;返回所有左叶子之和。 示例 1&#xff1a; 输入: root [3, 9, 20, null, null, 15, 7] 输出 : 24 解释 : 在这个二叉树中&…

vue三部曲

vue初入 简介 ​ vue生于2014年 Vue等框架与jQuery的区别 ​ jQuery是基于操作dom的库 ​ Vue框架是以数据驱动和组件化开发为核心 留坑、引包、实例化、插值表达式{{}} ​ vue第一个简单案例练习图。 引包 ​ 使用cnpm install vue2下载vue2&#xff08;version下载指…

ansible的剧本

一、playbooks 概述以及实例操作 1、playbooks 的组成 playbooks 本身由以下各部分组成 &#xff08;1&#xff09;Tasks&#xff1a;任务&#xff0c;即通过 task 调用 ansible 的模板将多个操作组织在一个 playbook 中运行 &#xff08;2&#xff09;Variables&#xff1…

数据挖掘工程师岗位的工作职责

数据挖掘工程师岗位的工作职责1 职责&#xff1a; 1.负责数据分析,数据挖掘相关的算法、应用的设计与开发; 2.负责公司产品各阶段数据的整理、分析、挖掘及提交数据报告&#xff0c;重点对车辆行为数据进行分析和挖掘&#xff0c;利用数据分析结论推动业务产品的优化; 3.对海量…