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

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

一、网络协议栈

如何实现网络协议栈,首先需要拿到网络数据,有以下几种方式

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 * 指针则适用于二进制数据处理、网络编程、加密解密等专用领域。

目录
相关文章
|
2月前
|
存储 网络协议 算法
UDP 协议和 TCP 协议
本文介绍了UDP和TCP协议的基本结构与特性。UDP协议具有简单的报文结构,包括报头和载荷,报头由源端口、目的端口、报文长度和校验和组成。UDP使用CRC校验和来检测传输错误。相比之下,TCP协议提供更可靠的传输服务,其结构复杂,包含序列号、确认序号和标志位等字段。TCP通过确认应答和超时重传来保证数据传输的可靠性,并采用三次握手建立连接,四次挥手断开连接,确保通信的稳定性和完整性。
93 1
UDP 协议和 TCP 协议
|
16天前
|
网络协议 SEO
TCP连接管理与UDP协议IP协议与ethernet协议
TCP、UDP、IP和Ethernet协议是网络通信的基石,各自负责不同的功能和层次。TCP通过三次握手和四次挥手实现可靠的连接管理,适用于需要数据完整性的场景;UDP提供不可靠的传输服务,适用于低延迟要求的实时通信;IP协议负责数据包的寻址和路由,是网络层的重要协议;Ethernet协议定义了局域网的数据帧传输方式,广泛应用于局域网设备之间的通信。理解这些协议的工作原理和应用场景,有助于设计和维护高效可靠的网络系统。
27 4
|
2月前
|
网络协议
UDP 协议
UDP 协议
126 58
|
1月前
|
网络协议 网络性能优化 C#
C# 一分钟浅谈:UDP 与 TCP 协议区别
【10月更文挑战第8天】在网络编程中,传输层协议的选择对应用程序的性能和可靠性至关重要。本文介绍了 TCP 和 UDP 两种常用协议的基础概念、区别及应用场景,并通过 C# 代码示例详细说明了如何处理常见的问题和易错点。TCP 适用于需要可靠传输和顺序保证的场景,而 UDP 适用于对延迟敏感且可以容忍一定数据丢失的实时应用。
38 1
|
1月前
|
网络协议 算法 数据格式
【TCP/IP】UDP协议数据格式和报文格式
【TCP/IP】UDP协议数据格式和报文格式
134 3
|
1月前
|
存储 网络协议 算法
更深层次理解传输层两协议【UDP | TCP】【UDP 缓冲区 | TCP 8种策略 | 三次握手四次挥手】
UDP和TCP各有所长,UDP以其低延迟、轻量级的特点适用于对实时性要求极高的应用,而TCP凭借其强大的错误检测、流量控制和拥塞控制机制,确保了数据的可靠传输,适用于文件传输、网页浏览等场景。理解它们的工作原理,特别是UDP的缓冲区管理和TCP的8种策略,对于优化网络应用的性能、确保数据的高效和可靠传输至关重要。开发者在选择传输层协议时,应根据实际需求权衡利弊,合理利用这两项关键技术。
68 5
|
1月前
|
JavaScript 安全 Java
谈谈UDP、HTTP、SSL、TLS协议在java中的实际应用
下面我将详细介绍UDP、HTTP、SSL、TLS协议及其工作原理,并提供Java代码示例(由于Deno是一个基于Node.js的运行时,Java代码无法直接在Deno中运行,但可以通过理解Java示例来类比Deno中的实现)。
73 1
|
2月前
|
监控 网络协议 网络性能优化
如何办理支持UDP协议的网络
在当今网络环境中,UDP(用户数据报协议)因传输速度快、延迟低而广泛应用于在线游戏、视频流媒体、VoIP等实时服务。本文详细介绍了办理支持UDP协议网络的方法,包括了解UDP应用场景、选择合适的ISP及网络套餐、购买支持UDP的设备并进行优化设置,以及解决常见问题的策略,帮助用户确保网络稳定性和速度满足实际需求。
|
2月前
|
网络协议
UDP协议在网络通信中的独特应用与优势
UDP(用户数据报协议)作为关键的传输层协议,在网络通信中展现出独特优势。本文探讨UDP的无连接性及低开销特性,使其在实时性要求高的场景如视频流、在线游戏中表现优异;其不保证可靠交付的特性赋予应用程序自定义传输策略的灵活性;面向报文的高效处理能力及短小的包头设计进一步提升了数据传输效率。总之,UDP适用于高速、实时性强且对可靠性要求不高的应用场景,为网络通信提供了多样化的选择。
|
2月前
|
网络协议 视频直播 C语言
C语言 网络编程(三)UDP 协议
UDP(用户数据报协议)是一种无需建立连接的通信协议,适用于高效率的数据传输,但不保证数据的可靠性。其特点是无连接、尽力交付且面向报文,具备较高的实时性。UDP广泛应用于视频会议、实时多媒体通信、直播及DNS查询等场景,并被许多即时通讯软件和服务(如MSN/QQ/Skype、流媒体、VoIP等)采用进行实时数据传输。UDP报文由首部和数据部分组成,首部包含源端口、目的端口、长度和校验和字段。相比TCP,UDP具有更高的传输效率和更低的资源消耗。