目录

前言

主要分析源码实现。

源码部分,本章节也只分析协议实现部分和最原始的南北接口。

北向协议栈接口和套接字接口的封装后面有独立章节分析。

即是UDP RAW接口。

友链:

11.1 传输层说明

IP协议只能完成数据报在互联网主机之间的传递,交付。

而传输层主要负责向两个主机中进程之间的通信提供服务。

传输层的几个重要的任务:

  1. 为两个通信的进程提供连接机制。即是传输层需要识别两个正在通信的进程。
  2. 传输层应该提供数据传输服务。在数据发送端,传输层将用户数据进行组装递交给IP层发送出去。在接收端,等待属于同一应用程序的所有数据单元到达,然后解析转交给用户。
  3. 为了提供可靠的传输服务,传输层还可以提供流量控制、数据确认等,保证两个主机的应用程序之间数据的有效性。

11.2 UDP协议简介

UDP 是 User Datagram Protocol 的简称,中文名是用户数据报协议。

11.3 UDP特点

UDP特点:

  1. 无连接、不可靠。
  2. 尽可能提供交付数据服务,出现差错直接丢弃,无反馈。
  3. 面向报文,发送方的 UDP 拿到上层数据直接添加个 UDP 首部,然后进行校验后就递交给IP 层,而接收的一方在接收到 UDP 报文后简单进行校验,然后直接去除数据递交给上层应用。
  4. 支持一对一,一对多,多对一,多对多的交互通信。
  5. 速度快,UDP 没有 TCP 的握手、确认、窗口、重传、拥塞控制等机制,UDP 是一个无状态的传输协议,所以它在传递数据时非常快,即使在网络拥塞的时候 UDP 也不会降低发送的数据。

11.4 UDP端口号

UDP 报文协议根据对应的端口号传递到目标主机的应用线程的。

传输层到应用层的唯一标识是通过端口号决定的,两个线程之间进行通信必须用端口号进行识别。

使用“IP 地址 + 端口号”来区分主机不同的线程。

范围:[0,65535],因为只有2字节:

  1. 端口号小于256的定义为常用端口,服务器一般都是通过常用端口号来识别的。任何TCP/IP实现所提供的服务都用[1,1023]之间的端口号,是由ICANN来管理的;
  2. 端口号从[1024, 49151]是被注册的端口,也成为“用户端口”,被IANA指定为特殊服务使用。
  3. 大多数TCP/IP实现给临时端口号分配[1024, 5000]之间的端口号。
  4. 大于5000的端口号是为其他服务器预留的。
  5. 客户端只需保证该端口号在本机上是唯一的就可以了。客户端端口号因存在时间很短暂又称临时端口号

常见的UDP协议端口号:




说明
0 保留
7 echo 报文回送服务器端口
53 DNS 域名服务器端口
69 TFT 中小型文件传输协议端口
123 NTP 网络时问协议端口
是用来同步网络中各个计算机时问的协议
161 SNMP 简单网络管理协议端口

11.5 UDP报文

UDP 报文也被称为用户数据报。

UDP报文封装:

  • UDP的数据区就是用户程序的数据了。

UDP报文格式:

源端口号:用户发送数据的进程锁绑定的本地端口号。

  • 用户进程调用UDP相关业务时,可以不配置源端口号。若不配置,内部会自动适配一个临时端口号。

目的端口号:远端主机用户进程接收数据绑定的端口号。

总长度:是UDP数据报的总长度:UDP首部+UDP数据区。

  • 这个字段有点冗余,因为在IP报文中就包含了IP首部和IP总长度,这样能计算出UDP数据报的长度。所以,UDP LITE就把这个字段改为需要进行校验和的UDP报文数据长度(从UDP首部算起)。
  • 对于UDP LITE协议,这个字段为0时,表示对整个UDP报文进行校验和计算。参考RFC 3828 chap. 3.1
  • 对于UDP LITE协议,这个字段要么为0,要么不少于UDP报文首部长度(即是校验和至少要涵盖UDP首部)。参考RFC 3828 chap. 3.1

检验和:UDP协议为UDP伪首部+UDP首部+UDP数据区所有数据都加入校验和。UDP LITE协议为“总长度”指定的长度加入校验和,从UDP伪首部算起,再加上伪首部校验和。

  • 填入0时,表示不进行校验和。而在实际计算校验和得到的结果刚好为0时,则向校验和字段填入0FFFF。

    • 填入0XFFFF的可行性证明:如果校验和结果为0,即是其它数据的和为0XFFFF。而对端继续校验和时,就是0XFFFF+0XFFFF,结果还是0XFFFF。

UDP LITE:UDP协议的校验和是UDP首部和UDP数据区,如果数据区很多数据,一个校验失败就丢弃了,代价有点大,所以衍生出UDP LITE。只校验UDP报文前面指定数据长度的数据。一般用于实时适配、实时通话等这些要求通信速度快,可靠性要求不高的业务中。

11.6 UDP伪首部和校验和

UDP校验和的计算包括了三部分:UDP伪首部+UDP首部+UDP数据区。

UDP伪首部包含IP首部一些字段。其目的是让UDP验证数据是否已经正确到达目的地。

UDP伪首部只参与校验,不参与实际发送。

伪首部中UDP总长度和UDP首部的总长度字段一致。

11.7 wireshark报文分析

11.8 UDP数据结构

参考udp.cudp.h文件

11.8.1 UDP首部

UDP首部长度:

#define UDP_HLEN 8

UDP首部数据结构:

struct udp_hdr {
PACK_STRUCT_FIELD(u16_t src); /* 源端口号 */
PACK_STRUCT_FIELD(u16_t dest); /* 目的端口号 */
PACK_STRUCT_FIELD(u16_t len); /* 总长度 */
PACK_STRUCT_FIELD(u16_t chksum); /* 校验和 */
} PACK_STRUCT_STRUCT;

11.9 UDP控制块

UDP控制块是整个UDP协议实现的核心部分。

LWIP使用UDP控制块来描述一个UDP连接的所有相关信息,包括源端口号、目的端口号、源IP、目的IP等等。

LWIP为每个UDP连接都分配一个UDP控制块,并用链表udp_pcbs链起来。

但是LWIP也给UDP控制块数量设限制,MEMP_NUM_UDP_PCB为UDP控制块的内存池数量。该宏缺省为8。

UDP控制块数据结构:

#if LWIP_NETIF_USE_HINTS
#define IP_PCB_NETIFHINT ;struct netif_hint netif_hints
#else /* LWIP_NETIF_USE_HINTS */
#define IP_PCB_NETIFHINT
#endif /* LWIP_NETIF_USE_HINTS */ /* This is the common part of all PCB types. It needs to be at the
beginning of a PCB type definition. It is located here so that
changes to this common part are made in one location instead of
having to change all PCB structs. */
#define IP_PCB \
/* 按网络字节顺序排列的IP地址 */ \
ip_addr_t local_ip; \
ip_addr_t remote_ip; \
/* 绑定的netif的索引 */ \
u8_t netif_idx; \
/* 套接口选项 */ \
u8_t so_options; \
/* 服务类型 */ \
u8_t tos; \
/* TTL */ \
u8_t ttl \
/* 链路层地址解析提示 */ \
IP_PCB_NETIFHINT /** the UDP protocol control block */
struct udp_pcb {
IP_PCB; /* UDP控制块和IP协议相关的字段 */ struct udp_pcb *next; /* UDP控制块链表节点 */ u8_t flags; /* 控制块状态 */
u16_t local_port, remote_port; /* 本地端口号和远端端口号 */ #if LWIP_MULTICAST_TX_OPTIONS /* 支持组播相关 */
#if LWIP_IPV4
/* 组播数据包的出网络接口,通过IPv4地址(如果没有'any') */
ip4_addr_t mcast_ip4;
#endif /* LWIP_IPV4 */
/* 组播数据包的出网络接口,根据接口索引(如果非零) */
u8_t mcast_ifindex;
/* 发送数据时,组播报文的TTL值 */
u8_t mcast_ttl;
#endif /* LWIP_MULTICAST_TX_OPTIONS */ #if LWIP_UDPLITE /* 支持UDP LITE */
u16_t chksum_len_rx, chksum_len_tx; /* 接收、发送数据时需要进行校验的数据长度 */
#endif /* LWIP_UDPLITE */ /* 接收回调函数 */
udp_recv_fn recv;
/* 接收回调函数参数 */
void *recv_arg;
};

11.10 端口号相关

11.10.1 端口号范围

#define UDP_LOCAL_PORT_RANGE_START  0xc000
#define UDP_LOCAL_PORT_RANGE_END 0xffff
#define UDP_ENSURE_LOCAL_PORT_RANGE(port) ((u16_t)(((port) & (u16_t)~UDP_LOCAL_PORT_RANGE_START) + UDP_LOCAL_PORT_RANGE_START))

11.10.2 端口号初始值

UDP的端口号由全局值udp_port累加管理。

其初始值有两次初始:第一次是变量赋值,第二次是调用udp_init()进行随机初始。

变量初始值:

/* last local UDP port */
static u16_t udp_port = UDP_LOCAL_PORT_RANGE_START;

随机初始化:

  • 需要开启LWIP随机宏LWIP_RAND
/**
* Initialize this module.
*/
void
udp_init(void)
{
#ifdef LWIP_RAND
udp_port = UDP_ENSURE_LOCAL_PORT_RANGE(LWIP_RAND());
#endif /* LWIP_RAND */
}

11.10.3 udp_new_port()端口号申请

端口号申请是有udp_port进行累加,溢出就复位到UDP_LOCAL_PORT_RANGE_START

/**
* Allocate a new local UDP port.
*
* @return a new (free) local UDP port number
*/
static u16_t
udp_new_port(void)
{
u16_t n = 0;
struct udp_pcb *pcb; again:
if (udp_port++ == UDP_LOCAL_PORT_RANGE_END) { /* 累加获取 */
udp_port = UDP_LOCAL_PORT_RANGE_START; /* 溢出复位 */
}
/* Check all PCBs. */
for (pcb = udp_pcbs; pcb != NULL; pcb = pcb->next) { /* 检查是否有重复 */
if (pcb->local_port == udp_port) { /* 重复 */
if (++n > (UDP_LOCAL_PORT_RANGE_END - UDP_LOCAL_PORT_RANGE_START)) {
return 0; /* 如果所有端口号都重复了,返回申请失败 */
}
goto again; /* 重新申请 */
}
}
return udp_port; /* 申请成功 */
}

11.11 UDP控制块操作函数

UDP控制块的操作函数相对简单,因为没有流量控制、没有确认机制等等。

11.11.1 udp_new():新建UDP控制块

udp_new()

  • MEMP_UDP_PCB内存池中获取UDP控制块资源。
  • 初始化部分字段。
/**
* @ingroup udp_raw
* Creates a new UDP pcb which can be used for UDP communication. The
* pcb is not active until it has either been bound to a local address
* or connected to a remote address.
* @see MEMP_NUM_UDP_PCB
*
* @return The UDP PCB which was created. NULL if the PCB data structure
* could not be allocated.
*
* @see udp_remove()
*/
struct udp_pcb *
udp_new(void)
{
struct udp_pcb *pcb; LWIP_ASSERT_CORE_LOCKED(); /* 内核锁确认 */ pcb = (struct udp_pcb *)memp_malloc(MEMP_UDP_PCB); /* 申请UDP控制块资源 */
if (pcb != NULL) {
memset(pcb, 0, sizeof(struct udp_pcb));
pcb->ttl = UDP_TTL; /* UDP数据出口默认的TTL值 */
#if LWIP_MULTICAST_TX_OPTIONS /* 多播TX相关 */
udp_set_multicast_ttl(pcb, UDP_TTL);
#endif /* LWIP_MULTICAST_TX_OPTIONS */
}
return pcb;
}

11.11.2 udp_remove():删除UDP控制块

udp_remove()

  • struct udp_pcb *pcb:需要删除的UDP控制块。
/**
* @ingroup udp_raw
* Removes and deallocates the pcb.
*
* @param pcb UDP PCB to be removed. The PCB is removed from the list of
* UDP PCB's and the data structure is freed from memory.
*
* @see udp_new()
*/
void
udp_remove(struct udp_pcb *pcb)
{
struct udp_pcb *pcb2; LWIP_ASSERT_CORE_LOCKED(); /* 内核所内 */ LWIP_ERROR("udp_remove: invalid pcb", pcb != NULL, return); mib2_udp_unbind(pcb);
/* 先从udp_pcbs链表中移除 */
if (udp_pcbs == pcb) {
/* 如果当前UDP控制块是udp_pcbs的链表头,则直接更新链表头即可移除 */
udp_pcbs = udp_pcbs->next;
} else { /* 需要遍历udp_pcbs,把当前UDP控制块移除 */
for (pcb2 = udp_pcbs; pcb2 != NULL; pcb2 = pcb2->next) {
if (pcb2->next != NULL && pcb2->next == pcb) {
pcb2->next = pcb->next;
break;
}
}
}
/* 释放内存资源 */
memp_free(MEMP_UDP_PCB, pcb);
}

11.11.3 udp_bind():绑定控制块

当UDP服务于应用程序时,数据流需要底层和应用层进行对接,就需要把UDP控制块绑定到本地IP和端口号。

绑定控制块时需要注意的是:

  1. 检查是否有PCB已经绑定了当前IP和端口号。
  2. 当前PCB有没有已经插入了udp_pcbs链表。

小笔记:在没有设置SOF_REUSEADDR选项功能时,需要确保一个UDP报文最多只能到达一个应用程序。即是一个网络接口中的一个端口号。需要注意的是任意IP。

udp_bind()

  • struct udp_pcb *pcb:需要绑定本地IP和端口号的UDP控制块。

  • ip_addr_t *ipaddr:UDP控制块需要绑定的本地IP地址。

    • 如果为NULL,则绑定本地IP为全0的IP。即表示本地任意IP都可。
    • 如果不为空,则绑定指定的本地IP。
  • u16_t port:UDP控制块需要绑定的本地端口号。

    • 如果为0,则绑定由内部调用udp_new_port()随机生成端口号。
    • 如果不为0,则绑定指定的端口号。
  • 先检查下当前UDP控制块有没有插入了udp_pcbs链表,因为绑定成功后,需要插入该链表。已经插入了,就不需要重复操作。

  • 检查绑定的IP地址。传入为空,则赋值为全0的IP地址。

  • 检查绑定的端口号。

    • 如果为0,则调用udp_new_port()生成一个并绑定。

    • 如果不为0,则遍历udp_pcbs链表,判断是否有其它UDP控制块重复使用这个端口号。确保一个UDP报文最多只有一个应用程序去向。相同条件:端口号相同且IP报文能到达这个服务。IP报文能否到达这个服务,可以通过以下判断(其一即符合要求):重复端口号的UDP控制块绑定的IP对比当前UDP控制块需要绑定的IP。

      1. 两个IP一致。
      2. 任一IP为全0。(出现万能IP)
      3. 如果开启了SO_REUSE且两个UDP控制块都配置了SO_REUSEADDR功能,则不用对比了,直接支持复用。
    • 这里需要注意是否开启SO_REUSEADDR选项,即是立即启用端口号的功能。由宏SO_REUSE决定有没有这个功能,用户在代码中设置SO_REUSEADDR是否开启该功能。

  • 把需要绑定的IP和端口号填入UDP控制块。绑定成功。

  • 确保当前UDP控制块插入了udp_pcbs链表。

/**
* @ingroup udp_raw
* Bind an UDP PCB.
*
* @param pcb UDP PCB to be bound with a local address ipaddr and port.
* @param ipaddr local IP address to bind with. Use IP_ANY_TYPE to
* bind to all local interfaces.
* @param port local UDP port to bind with. Use 0 to automatically bind
* to a random port between UDP_LOCAL_PORT_RANGE_START and
* UDP_LOCAL_PORT_RANGE_END.
*
* ipaddr & port are expected to be in the same byte order as in the pcb.
*
* @return lwIP error code.
* - ERR_OK. Successful. No error occurred.
* - ERR_USE. The specified ipaddr and port are already bound to by
* another UDP PCB.
*
* @see udp_disconnect()
*/
err_t
udp_bind(struct udp_pcb *pcb, const ip_addr_t *ipaddr, u16_t port)
{
struct udp_pcb *ipcb;
u8_t rebind;
#if LWIP_IPV6 && LWIP_IPV6_SCOPES
ip_addr_t zoned_ipaddr;
#endif /* LWIP_IPV6 && LWIP_IPV6_SCOPES */ LWIP_ASSERT_CORE_LOCKED(); #if LWIP_IPV4
/* Don't propagate NULL pointer (IPv4 ANY) to subsequent functions */
if (ipaddr == NULL) {
ipaddr = IP4_ADDR_ANY; /* 如果传入绑定本地IP为NULL,则绑定为全0,表示任意本地IP */
}
#else /* LWIP_IPV4 */
LWIP_ERROR("udp_bind: invalid ipaddr", ipaddr != NULL, return ERR_ARG);
#endif /* LWIP_IPV4 */ LWIP_ERROR("udp_bind: invalid pcb", pcb != NULL, return ERR_ARG); LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, ("udp_bind(ipaddr = "));
ip_addr_debug_print(UDP_DEBUG | LWIP_DBG_TRACE, ipaddr);
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, (", port = %"U16_F")\n", port)); rebind = 0;
/* 检查下当前UDP控制块是否已经插入到udp_pcbs链表中,如果插入了,后面绑定成功后就不需要重新插入了 */
for (ipcb = udp_pcbs; ipcb != NULL; ipcb = ipcb->next) {
if (pcb == ipcb) {
rebind = 1;
break;
}
} #if LWIP_IPV6 && LWIP_IPV6_SCOPES /* IPV6暂时跳过 */
/* If the given IP address should have a zone but doesn't, assign one now.
* This is legacy support: scope-aware callers should always provide properly
* zoned source addresses. Do the zone selection before the address-in-use
* check below; as such we have to make a temporary copy of the address. */
if (IP_IS_V6(ipaddr) && ip6_addr_lacks_zone(ip_2_ip6(ipaddr), IP6_UNKNOWN)) {
ip_addr_copy(zoned_ipaddr, *ipaddr);
ip6_addr_select_zone(ip_2_ip6(&zoned_ipaddr), ip_2_ip6(&zoned_ipaddr));
ipaddr = &zoned_ipaddr;
}
#endif /* LWIP_IPV6 && LWIP_IPV6_SCOPES */ /* 确定下本地端口号 */
if (port == 0) {
port = udp_new_port(); /* 如果没有指定端口号,则由内部生成一个临时端口号 */
if (port == 0) { /* 端口号资源不足,申请失败 */
LWIP_DEBUGF(UDP_DEBUG, ("udp_bind: out of free UDP ports\n"));
return ERR_USE;
}
} else { /* 端口号申请成功 */
/* 检查下有没有其它UDP控制块绑定了相同端口号且IP报文能到达这个服务,这样的话可能会导致一个UDP包有多个应用程序去向。 */
for (ipcb = udp_pcbs; ipcb != NULL; ipcb = ipcb->next) {
if (pcb != ipcb) { /* 需要跳过当前UDP控制块 */
#if SO_REUSE /* 支持`SO_REUSEADDR`选项:立即启用端口号 */
if (!ip_get_option(pcb, SOF_REUSEADDR) ||
!ip_get_option(ipcb, SOF_REUSEADDR)) /* 两个其中一个没有设置SO_REUSEADDR选项功能,则不能重复使用能到达该IP下的相同端口号 */
#endif /* SO_REUSE */
{
if ((ipcb->local_port == port) && /* 端口号相同 */
(((IP_GET_TYPE(&ipcb->local_ip) == IP_GET_TYPE(ipaddr)) &&
(ip_addr_eq(&ipcb->local_ip, ipaddr) ||
ip_addr_isany(ipaddr) ||
ip_addr_isany(&ipcb->local_ip))) ||
(IP_GET_TYPE(&ipcb->local_ip) == IPADDR_TYPE_ANY) ||
(IP_GET_TYPE(ipaddr) == IPADDR_TYPE_ANY))) {
/* 端口号相同且(IP一致或有任意IP),则这个UDP报文到达应用程序就没有唯一性。错误 */
LWIP_DEBUGF(UDP_DEBUG,
("udp_bind: local port %"U16_F" already bound by another pcb\n", port));
return ERR_USE;
}
}
}
}
} /* 到此,相关数据检查完毕,符合要求 */ ip_addr_set_ipaddr(&pcb->local_ip, ipaddr); /* 绑定本地IP */ pcb->local_port = port; /* 绑定本地端口号 */
mib2_udp_bind(pcb);
/* UDP控制块还没有激活就需要激活:插入udp_pcbs链表 */
if (rebind == 0) {
pcb->next = udp_pcbs;
udp_pcbs = pcb;
}
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("udp_bind: bound to "));
ip_addr_debug_print_val(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, pcb->local_ip);
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, (", port %"U16_F")\n", pcb->local_port));
return ERR_OK; /* 绑定成功 */
}

11.11.4 udp_bind_netif():绑定网卡

udp控制块也可以绑定指定网卡。

udp_bind_netif()

  • udp_pcb *pcb:需要绑定网卡的UDP控制块。

  • struct netif *netif:UDP控制块需要绑定的网卡。

    • 为NULL时,表示解绑。
  • 获取网卡索引,绑定到UDP控制块。

/**
* @ingroup udp_raw
* Bind an UDP PCB to a specific netif.
* After calling this function, all packets received via this PCB
* are guaranteed to have come in via the specified netif, and all
* outgoing packets will go out via the specified netif.
*
* @param pcb UDP PCB to be bound.
* @param netif netif to bind udp pcb to. Can be NULL.
*
* @see udp_disconnect()
*/
void
udp_bind_netif(struct udp_pcb *pcb, const struct netif *netif)
{
LWIP_ASSERT_CORE_LOCKED(); if (netif != NULL) {
pcb->netif_idx = netif_get_index(netif); /*获取网卡索引绑定到UDP控制块 */
} else {
pcb->netif_idx = NETIF_NO_INDEX; /* 取消绑定 */
}
}

11.11.5 udp_connect():连接控制

(本地行为)

UDP协议是没有连接状态的,但是为什么UDP可以调用udp_connect()这个函数?有什么用?

  • 调用这个是为了这个UDP控制块本地长期绑定一个远端IP和端口号,减少后面重复绑定和解绑的步骤。

先了解下UDP sendto() 函数传输数据过程 :

  1. 第 1 阶段:向 UDP 控制块注册目标 IP 和端口号。
  2. 第 2 阶段:传输数据。
  3. 第 3 阶段:删除 UDP 控制块中注册的目标地址信息。

如果需要频繁发送,那第一阶段和第三阶段是重复多余的,所以可以使用 已连接(connect)UDP 控制块。

所以udp_connect()这个函数的目的是把UDP控制块注册长期目标IP和端口号,这样中途调用发送函数时,不需要重新注册和注销。

可以使用udp_disconnect()进行注销。

udp_connect()

  • struct udp_pcb *pcb:需要连接的UDP控制块。
  • ip_addr_t *ipaddr:远端IP地址。
  • u16_t port:远端端口号。
  • 先检查有没有绑定了本地应用程序:即是UDP控制块有没有绑定了本地IP(包括任意IP)和本地端口号。还没有绑定,则调用udp_bind()进行绑定。
  • 注册远端IP和远端端口号。
  • 标记当前UDP控制块状态为已连接状态。
  • 确保当前UDP控制块已激活:即是是否插入了udp_pcbs链表。还没插入就需要插入处理。
/**
* @ingroup udp_raw
* Sets the remote end of the pcb. This function does not generate any
* network traffic, but only sets the remote address of the pcb.
*
* @param pcb UDP PCB to be connected with remote address ipaddr and port.
* @param ipaddr remote IP address to connect with.
* @param port remote UDP port to connect with.
*
* @return lwIP error code
*
* ipaddr & port are expected to be in the same byte order as in the pcb.
*
* The udp pcb is bound to a random local port if not already bound.
*
* @see udp_disconnect()
*/
err_t
udp_connect(struct udp_pcb *pcb, const ip_addr_t *ipaddr, u16_t port)
{
struct udp_pcb *ipcb; LWIP_ASSERT_CORE_LOCKED(); /* 内核所内 */ LWIP_ERROR("udp_connect: invalid pcb", pcb != NULL, return ERR_ARG);
LWIP_ERROR("udp_connect: invalid ipaddr", ipaddr != NULL, return ERR_ARG); /* 确保已经绑定了本地端口号 */
if (pcb->local_port == 0) { /* 本地端口号还没绑定,则需要先绑定 */
err_t err = udp_bind(pcb, &pcb->local_ip, pcb->local_port);
if (err != ERR_OK) {
return err;
}
} /* 注册远端IP */
ip_addr_set_ipaddr(&pcb->remote_ip, ipaddr);
#if LWIP_IPV6 && LWIP_IPV6_SCOPES /* [lzm][test][可暂时跳过] */
/* If the given IP address should have a zone but doesn't, assign one now,
* using the bound address to make a more informed decision when possible. */
if (IP_IS_V6(&pcb->remote_ip) &&
ip6_addr_lacks_zone(ip_2_ip6(&pcb->remote_ip), IP6_UNKNOWN)) {
ip6_addr_select_zone(ip_2_ip6(&pcb->remote_ip), ip_2_ip6(&pcb->local_ip));
}
#endif /* LWIP_IPV6 && LWIP_IPV6_SCOPES */ /* 注册远端端口号 */
pcb->remote_port = port;
/* UDP控制块状态标记上已连接(本地已连接) */
pcb->flags |= UDP_FLAGS_CONNECTED; LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("udp_connect: connected to "));
ip_addr_debug_print_val(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
pcb->remote_ip);
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, (", port %"U16_F")\n", pcb->remote_port)); /* 检查下是否插入了udp_pcbs链表,如果没有插入,则需要插入处理 */
for (ipcb = udp_pcbs; ipcb != NULL; ipcb = ipcb->next) {
if (pcb == ipcb) {
return ERR_OK;
}
}
pcb->next = udp_pcbs;
udp_pcbs = pcb;
return ERR_OK;
}

11.11.6 udp_disconnect():断开连接

(本地行为)

就是本地注销远端IP和远端端口号的绑定。

udp_disconnect()

  • 重置UDP控制块远端IP和远端端口号字段。
  • 解绑本地网卡。
  • 标记UDP控制块为未连接。
/**
* @ingroup udp_raw
* Remove the remote end of the pcb. This function does not generate
* any network traffic, but only removes the remote address of the pcb.
*
* @param pcb the udp pcb to disconnect.
*/
void
udp_disconnect(struct udp_pcb *pcb)
{
LWIP_ASSERT_CORE_LOCKED(); LWIP_ERROR("udp_disconnect: invalid pcb", pcb != NULL, return); /* 重置远端IP */
#if LWIP_IPV4 && LWIP_IPV6
if (IP_IS_ANY_TYPE_VAL(pcb->local_ip)) {
ip_addr_copy(pcb->remote_ip, *IP_ANY_TYPE);
} else {
#endif
ip_addr_set_any(IP_IS_V6_VAL(pcb->remote_ip), &pcb->remote_ip);
#if LWIP_IPV4 && LWIP_IPV6
}
#endif
/* 重置远端端口号 */
pcb->remote_port = 0;
/* 解绑本地网卡 */
pcb->netif_idx = NETIF_NO_INDEX;
/* 标记PCB为未连接 */
udp_clear_flags(pcb, UDP_FLAGS_CONNECTED);
}

11.11.7 udp_recv():控制块注册接收函数

udp_recv()只是用于UDP控制块注册接收函数。

/**
* @ingroup udp_raw
* Set a receive callback for a UDP PCB.
* This callback will be called when receiving a datagram for the pcb.
*
* @param pcb the pcb for which to set the recv callback
* @param recv function pointer of the callback function
* @param recv_arg additional argument to pass to the callback function
*/
void
udp_recv(struct udp_pcb *pcb, udp_recv_fn recv, void *recv_arg)
{
LWIP_ASSERT_CORE_LOCKED(); LWIP_ERROR("udp_recv: invalid pcb", pcb != NULL, return); /* remember recv() callback and user data */
pcb->recv = recv;
pcb->recv_arg = recv_arg;
}

11.11.8 udp_netif_ip_addr_changed():更新UDP控制块本地IP

当底层网卡在IP层的IP有所更新时,需要把UDP控制块中本地IP绑定就的IP也更新。

即是当IP地址改变时,将从netif.c调用此函数检查并更新。

udp_netif_ip_addr_changed()

  • ip_addr_t *old_addr:旧IP。
  • ip_addr_t *new_addr:新IP。
  • 检索LWIP中UDP控制块链表udp_pcbs,把绑定就的IP更新到新的IP去。
/** This function is called from netif.c when address is changed
*
* @param old_addr IP address of the netif before change
* @param new_addr IP address of the netif after change
*/
void udp_netif_ip_addr_changed(const ip_addr_t *old_addr, const ip_addr_t *new_addr)
{
struct udp_pcb *upcb; if (!ip_addr_isany(old_addr) && !ip_addr_isany(new_addr)) { /* 新旧IP不一致才有意义 */
for (upcb = udp_pcbs; upcb != NULL; upcb = upcb->next) { /* 检索所有已激活的UDP控制块 */
if (ip_addr_eq(&upcb->local_ip, old_addr)) { /* 找到绑定需要更新IP的UDP控制块 */
ip_addr_copy(upcb->local_ip, *new_addr); /* 更新 */
}
}
}
}

11.12 UDP发送数据

注意校验和相关宏:

  • LWIP_CHECKSUM_ON_COPY:在支持使用数据区已经计算好的UDP数据区校验和。
  • CHECKSUM_GEN_UDP:在软件中生成出UDP数据包的校验和。

在分析前先说明需要分析的几个函数的关系:

  • udp_send():UDP RAW的接口,需要的参数只需要UDP和用户数据即可。

  • udp_sendto():UDP RAW的接口,对比上面函数,可以指定远端IP和与远端端口号。

  • udp_sendto_if():UDP RAW的接口,对比上面udp_sendto()函数,该函数还能指定网卡。

  • udp_sendto_if_src():UDP RAW的接口,也是UDP发送数据的基函数,是实现组装UDP包,和转交到IP层的接口函数。上面的函数都是必须经过该函数实现的。

    • 主要分析该函数。其它函数看看就好了。

11.12.1 udp_sendto_if_src():UDP发送数据基函数

先分析UDP发送数据基函数,即是组装UDP报文的函数。

然后再分析其它封装这个基函数的相关API。

udp_sendto_if_src()

  • struct udp_pcb *pcb:负责本次数据交互的UDP控制块。
  • struct pbuf *p:需要发送的数据的pbuf。
  • ip_addr_t *dst_ip:远端IP。
  • u16_t dst_port:远端端口号。
  • struct netif *netif:本地网卡,即是发送本次UDP报文的网络接口。
  • ip_addr_t *src_ip:源IP地址。
  • 检查传入的参数是否异常。
  • 检查当前UDP控制块有没有绑定了本地IP(包括任意IP)和本地端口号。还没绑定就需要调用udp_bind()进行绑定。
  • 预测检查UDP报文长度:UDP数据区追加UDP首部后是否溢出,溢出丢弃。
  • 检查pbuf长度能否扩充到链路层报文:不能就申请多一个pbuf q,包含么UDP首部+IP首部+链路层首部,然后拼接到当前pbuf,让其拥有足够空间。
  • 填充UDP报文首部的几个字段。
  • 其中,UDP首部的长度字段和校验和需要按协议类型和相关宏处理。
  • 最后把UDP报文转交给IP层:调用ip_output_if_src()转发出去。
/** @ingroup udp_raw
* Same as @ref udp_sendto_if, but with source address */
err_t
udp_sendto_if_src(struct udp_pcb *pcb, struct pbuf *p,
const ip_addr_t *dst_ip, u16_t dst_port, struct netif *netif, const ip_addr_t *src_ip)
{
#if LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP /* 需要计算校验和 */
return udp_sendto_if_src_chksum(pcb, p, dst_ip, dst_port, netif, 0, 0, src_ip); /* 校验和字段先填入0,后面会计算 */
} /** Same as udp_sendto_if_src(), but with checksum */
err_t
udp_sendto_if_src_chksum(struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *dst_ip,
u16_t dst_port, struct netif *netif, u8_t have_chksum,
u16_t chksum, const ip_addr_t *src_ip)
{
#endif /* LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP */
struct udp_hdr *udphdr; /* UDP首部 */
err_t err;
struct pbuf *q; /* 组装好的UDP报文,用于推到发送缓冲区 */
u8_t ip_proto; /* IP协议 */
u8_t ttl; /* TTL */ LWIP_ASSERT_CORE_LOCKED(); /* 内核锁内 */ /* 校验必要参数 */
LWIP_ERROR("udp_sendto_if_src: invalid pcb", pcb != NULL, return ERR_ARG);
LWIP_ERROR("udp_sendto_if_src: invalid pbuf", p != NULL, return ERR_ARG);
LWIP_ERROR("udp_sendto_if_src: invalid dst_ip", dst_ip != NULL, return ERR_ARG);
LWIP_ERROR("udp_sendto_if_src: invalid src_ip", src_ip != NULL, return ERR_ARG);
LWIP_ERROR("udp_sendto_if_src: invalid netif", netif != NULL, return ERR_ARG); /* UDP控制块绑定的本地IP类型和需要组装的IP报文中的源IP地址和目的IP地址类型一致 */
if (!IP_ADDR_PCB_VERSION_MATCH(pcb, src_ip) ||
!IP_ADDR_PCB_VERSION_MATCH(pcb, dst_ip)) {
return ERR_VAL;
} #if LWIP_IPV4 && IP_SOF_BROADCAST /* 支持发送广播数据 */
/* broadcast filter? */
if (!ip_get_option(pcb, SOF_BROADCAST) &&
#if LWIP_IPV6
IP_IS_V4(dst_ip) &&
#endif /* LWIP_IPV6 */
ip_addr_isbroadcast(dst_ip, netif)) {
/* 如果本次UDP报文的目的IP是广播地址,但是用户又没有设置SOF_BROADCAST选项,则不能广播 */
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
("udp_sendto_if: SOF_BROADCAST not enabled on pcb %p\n", (void *)pcb));
return ERR_VAL;
}
#endif /* LWIP_IPV4 && IP_SOF_BROADCAST */ /* 如果这个UDP控制块还没有绑定到本地端口,则将其绑定 */
if (pcb->local_port == 0) {
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, ("udp_send: not yet bound to a port, binding now\n"));
err = udp_bind(pcb, &pcb->local_ip, pcb->local_port); /* 绑定本地IP(包括任意IP)和本地端口号 */
if (err != ERR_OK) { /* 绑定失败就不能发送 */
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("udp_send: forced port bind failed\n"));
return err;
}
} if ((u16_t)(p->tot_len + UDP_HLEN) < p->tot_len) {
return ERR_MEM; /* UDP报文溢出 */
}
/* pbuf扩展UDP首部 */
if (pbuf_add_header(p, UDP_HLEN)) {
/* 原pbuf长度不足,需要申请相关首部空间:UDP首部+IP报文 */
q = pbuf_alloc(PBUF_IP, UDP_HLEN, PBUF_RAM);
if (q == NULL) {
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("udp_send: could not allocate header\n"));
return ERR_MEM; /* 申请UDP报文空间失败 */
}
if (p->tot_len != 0) {
/* 把p拼接到q去 */
pbuf_chain(q, p);
}
LWIP_DEBUGF(UDP_DEBUG,
("udp_send: added header pbuf %p before given pbuf %p\n", (void *)q, (void *)p));
} else { /* p这个pbuf可以扩展UDP首部空间 */
q = p;
LWIP_DEBUGF(UDP_DEBUG, ("udp_send: added header in given pbuf %p\n", (void *)p));
}
LWIP_ASSERT("check that first pbuf can hold struct udp_hdr",
(q->len >= sizeof(struct udp_hdr))); /* 至此,q就是需要发送的UDP报文pbuf */ /* 组装UDP报文 */
udphdr = (struct udp_hdr *)q->payload;
udphdr->src = lwip_htons(pcb->local_port); /* UDP报文源端口 */
udphdr->dest = lwip_htons(dst_port); /* UDP报文目的端口 */
/* UDP报文校验和字段,先初始化为0,表示不计算校验和 */
udphdr->chksum = 0x0000; #if LWIP_MULTICAST_TX_OPTIONS /* 组播TX */
if (((pcb->flags & UDP_FLAGS_MULTICAST_LOOP) != 0) && ip_addr_ismulticast(dst_ip)) {
/* 如果当前这路UDP支持组播环回,且目的IP是个组播IP,则标记当前pbuf为要环回的UDP组播 */
q->flags |= PBUF_FLAG_MCASTLOOP;
}
#endif /* LWIP_MULTICAST_TX_OPTIONS */ LWIP_DEBUGF(UDP_DEBUG, ("udp_send: sending datagram of length %"U16_F"\n", q->tot_len)); #if LWIP_UDPLITE /* 支持UDP LITE协议 */
if (pcb->flags & UDP_FLAGS_UDPLITE) { /* 当前这路UDP为UDP LITE协议 */
u16_t chklen, chklen_hdr;
LWIP_DEBUGF(UDP_DEBUG, ("udp_send: UDP LITE packet length %"U16_F"\n", q->tot_len));
/* 设置UDP首部总长度字段和伪首部UDP总长度字段为需要进行校验和的长度 */
chklen_hdr = chklen = pcb->chksum_len_tx;
if ((chklen < sizeof(struct udp_hdr)) || (chklen > q->tot_len)) { /* 需要进行校验和的数据量不能超出现有数据量 */
if (chklen != 0) {
LWIP_DEBUGF(UDP_DEBUG, ("udp_send: UDP LITE pcb->chksum_len is illegal: %"U16_F"\n", chklen));
}
/* 对于UDP LITE协议,校验和长度字段为0时,表示对整个UDP报文进行校验和计算(校验和字段除外)。
(See RFC 3828 chap. 3.1) */
chklen_hdr = 0; /* UDP LITE校验和长度字段 */
chklen = q->tot_len; /* UDP LITE需要进行校验和的UDP报文数据长度 */
}
udphdr->len = lwip_htons(chklen_hdr); /* UDP报文校验和长度字段值 */ #if CHECKSUM_GEN_UDP /* UDP支持校验和 */
IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_UDP) { /* 网卡需要检查UDP校验和 */
#if LWIP_CHECKSUM_ON_COPY /* 拷贝时需要计算校验和 */
if (have_chksum) {
chklen = UDP_HLEN; /* UDP数据已经有校验和,则还需要再计算UDP首部的校验和 */
}
#endif /* LWIP_CHECKSUM_ON_COPY */
udphdr->chksum = ip_chksum_pseudo_partial(q, IP_PROTO_UDPLITE,
q->tot_len, chklen, src_ip, dst_ip); /* 计算校验和(含伪首部) */
#if LWIP_CHECKSUM_ON_COPY
if (have_chksum) {
/* 如果是已经有校验和,则把UDP首部(函伪首部)的校验和和已有的UDP数据校验和进行校验和即可 */
u32_t acc;
acc = udphdr->chksum + (u16_t)~(chksum);
udphdr->chksum = FOLD_U32T(acc);
}
#endif /* LWIP_CHECKSUM_ON_COPY */ /* 校验和刚好为0时必须变成0xffff,因为在UDP协议中,校验和字段为0表示“没有校验和” */
if (udphdr->chksum == 0x0000) {
udphdr->chksum = 0xffff;
}
}
#endif /* CHECKSUM_GEN_UDP */ /* IP协议字段标记为UDPLITE协议 */
ip_proto = IP_PROTO_UDPLITE;
} else
#endif /* LWIP_UDPLITE */
{ /* UDP 协议 */
LWIP_DEBUGF(UDP_DEBUG, ("udp_send: UDP packet length %"U16_F"\n", q->tot_len));
udphdr->len = lwip_htons(q->tot_len); #if CHECKSUM_GEN_UDP /* UDP支持校验和计算 */
IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_UDP) { /* 网卡需要检查UDP校验和 */
/* 校验和在IPv6中是必须的 */
if (IP_IS_V6(dst_ip) || (pcb->flags & UDP_FLAGS_NOCHKSUM) == 0) {
u16_t udpchksum;
#if LWIP_CHECKSUM_ON_COPY
if (have_chksum) {
/* UDP数据已生成校验和,则只需要继续计算UDP首部的校验和即可 */
u32_t acc;
udpchksum = ip_chksum_pseudo_partial(q, IP_PROTO_UDP,
q->tot_len, UDP_HLEN, src_ip, dst_ip);
acc = udpchksum + (u16_t)~(chksum);
udpchksum = FOLD_U32T(acc);
} else
#endif /* LWIP_CHECKSUM_ON_COPY */
{ /* 不使用UDP数据区计算好的校验和,咱们UDP协议自己独立生成 */
udpchksum = ip_chksum_pseudo(q, IP_PROTO_UDP, q->tot_len,
src_ip, dst_ip); /* 计算UDP报文校验和 */
} /* 校验和刚好为0时必须变成0xffff,因为在UDP协议中,校验和字段为0表示“没有校验和” */
if (udpchksum == 0x0000) {
udpchksum = 0xffff;
}
/* 设置UDP报文校验和 */
udphdr->chksum = udpchksum;
}
}
#endif /* CHECKSUM_GEN_UDP */
/* IP协议字段标记为UDP协议 */
ip_proto = IP_PROTO_UDP;
} /* TTL相关 */
#if LWIP_MULTICAST_TX_OPTIONS /* 多播TX的TTL */
ttl = (ip_addr_ismulticast(dst_ip) ? udp_get_multicast_ttl(pcb) : pcb->ttl);
#else /* LWIP_MULTICAST_TX_OPTIONS */
ttl = pcb->ttl; /* UDP控制块中的默认TTL */
#endif /* LWIP_MULTICAST_TX_OPTIONS */ LWIP_DEBUGF(UDP_DEBUG, ("udp_send: UDP checksum 0x%04"X16_F"\n", udphdr->chksum));
LWIP_DEBUGF(UDP_DEBUG, ("udp_send: ip_output_if (,,,,0x%02"X16_F",)\n", (u16_t)ip_proto));
/* 把UDP报文涉及网卡用户数据配置到这个网卡中 */
NETIF_SET_HINTS(netif, &(pcb->netif_hints));
/* UDP报文转交给IP层 */
err = ip_output_if_src(q, src_ip, dst_ip, ttl, pcb->tos, ip_proto, netif);
NETIF_RESET_HINTS(netif); /* @todo: must this be increased even if error occurred? */
MIB2_STATS_INC(mib2.udpoutdatagrams); if (q != p) {
/* 释放在这里申请的pbuf */
pbuf_free(q);
q = NULL;
/* p is still referenced by the caller, and will live on */
} UDP_STATS_INC(udp.xmit);
return err;
}

11.12.2 udp_send():UDP发送数据函数

从用户的角度看,用户前期配置好UDP控制块后,后面发送数据只需要提供两个参数:UDP控制块和需要发送的数据即可。

所以就有了udp_send()函数,该函数的实现是层层封装UDP发送数据的基函数udp_sendto_if_src()实现的:udp_send() --> udp_sendto() --> udp_sendto_if() --> udp_sendto_if_src()

udp_send():没有指定远端IP和端口号则使用这个UDP PCB中的。

  • udp_pcb *pcb:负责本次发送的UDP控制块。
  • struct pbuf *p:需要发送的UDP数据。
/**
* @ingroup udp_raw
* Sends the pbuf p using UDP. The pbuf is not deallocated.
*
* @param pcb UDP PCB used to send the data.
* @param p chain of pbuf's to be sent.
*
* The datagram will be sent to the current remote_ip & remote_port
* stored in pcb. If the pcb is not bound to a port, it will
* automatically be bound to a random port.
*
* @return lwIP error code.
* - ERR_OK. Successful. No error occurred.
* - ERR_MEM. Out of memory.
* - ERR_RTE. Could not find route to destination address.
* - ERR_VAL. No PCB or PCB is dual-stack
* - More errors could be returned by lower protocol layers.
*
* @see udp_disconnect() udp_sendto()
*/
err_t
udp_send(struct udp_pcb *pcb, struct pbuf *p)
{
LWIP_ERROR("udp_send: invalid pcb", pcb != NULL, return ERR_ARG);
LWIP_ERROR("udp_send: invalid pbuf", p != NULL, return ERR_ARG); if (IP_IS_ANY_TYPE_VAL(pcb->remote_ip)) {
return ERR_VAL;
} /* 使用这路UDP控制块中配置的远端IP和远端端口号 */
return udp_sendto(pcb, p, &pcb->remote_ip, pcb->remote_port);
}

udp_sendto():指定远端IP和远端端口号的UDP发送。

  • struct udp_pcb *pcb:负责本次发送的UDP控制块。

  • struct pbuf *p:需要发送的数据的pbuf。

  • ip_addr_t *dst_ip:远端IP地址。

  • u16_t dst_port:远端端口号地址。

  • 传入参数校验。

  • 还需要指定本地网卡:

    • 如果UDP控制块已经绑定了本地网卡,则直接调用该网卡即可。
    • 否则,需要根据远端IP地址,用ip4_route_src()去路由匹配。这个匹配逻辑可以参考前面IP章节。
  • 然后调用udp_sendto_if()发送出去。

/**
* @ingroup udp_raw
* Send data to a specified address using UDP.
*
* @param pcb UDP PCB used to send the data.
* @param p chain of pbuf's to be sent.
* @param dst_ip Destination IP address.
* @param dst_port Destination UDP port.
*
* dst_ip & dst_port are expected to be in the same byte order as in the pcb.
*
* If the PCB already has a remote address association, it will
* be restored after the data is sent.
*
* @return lwIP error code (@see udp_send for possible error codes)
*
* @see udp_disconnect() udp_send()
*/
err_t
udp_sendto(struct udp_pcb *pcb, struct pbuf *p,
const ip_addr_t *dst_ip, u16_t dst_port)
{
struct netif *netif;
/* 参数校验 */
LWIP_ERROR("udp_sendto: invalid pcb", pcb != NULL, return ERR_ARG);
LWIP_ERROR("udp_sendto: invalid pbuf", p != NULL, return ERR_ARG);
LWIP_ERROR("udp_sendto: invalid dst_ip", dst_ip != NULL, return ERR_ARG); /* UDP控制块本地IP类型和目标IP类型要一致 */
if (!IP_ADDR_PCB_VERSION_MATCH(pcb, dst_ip)) {
return ERR_VAL;
} LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, ("udp_send\n")); if (pcb->netif_idx != NETIF_NO_INDEX) {
/* 如果已经绑定了网卡,则直接使用该网卡发送UDP报文 */
netif = netif_get_by_index(pcb->netif_idx);
} else { /* 没有绑定网卡就需要匹配 */
#if LWIP_MULTICAST_TX_OPTIONS /* 多播TX功能 */
netif = NULL;
if (ip_addr_ismulticast(dst_ip)) {
/* 如果UDP报文的目的IP地址是多播地址,则使用多播网卡来发送 */
if (pcb->mcast_ifindex != NETIF_NO_INDEX) {
netif = netif_get_by_index(pcb->mcast_ifindex);
}
#if LWIP_IPV4
else
#if LWIP_IPV6
if (IP_IS_V4(dst_ip))
#endif /* LWIP_IPV6 */
{
/* 如果当前UDP指定的多播地址不是任意也不是广播,就需要通过路由去匹配 */
if (!ip4_addr_isany_val(pcb->mcast_ip4) &&
!ip4_addr_eq(&pcb->mcast_ip4, IP4_ADDR_BROADCAST)) {
/* 通过UDP本地IP和多播IP去匹配本地网卡 */
netif = ip4_route_src(ip_2_ip4(&pcb->local_ip), &pcb->mcast_ip4);
}
}
#endif /* LWIP_IPV4 */
} if (netif == NULL) /* 还没有指定网卡 */
#endif /* LWIP_MULTICAST_TX_OPTIONS */
{
/* 通过UDP本地IP和目的IP去匹配网卡 */
netif = ip_route(&pcb->local_ip, dst_ip);
}
} if (netif == NULL) {
/* 找不到适合发送的当前UDP报文的网卡,则丢弃 */
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("udp_send: No route to "));
ip_addr_debug_print(UDP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, dst_ip);
LWIP_DEBUGF(UDP_DEBUG, ("\n"));
UDP_STATS_INC(udp.rterr);
return ERR_RTE;
}
/* 通过以下API实现发送UDP报文 */
#if LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP
return udp_sendto_if_chksum(pcb, p, dst_ip, dst_port, netif, have_chksum, chksum);
#else /* LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP */
return udp_sendto_if(pcb, p, dst_ip, dst_port, netif);
#endif /* LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP */
}

udp_sendto_if():确定UDP本地IP地址,然后调用udp_sendto_if_src()UDP发送数据基函数进行组包。

  • struct udp_pcb *pcb:负责本次发送的UDP控制块。

  • struct pbuf *p:需要发送的数据的pbuf。

  • ip_addr_t *dst_ip:远端IP地址。

  • u16_t dst_port:远端端口号地址。

  • struct netif *netif:指定发送UDP报文的网卡。

  • 参数校验。

  • 确定本地IP:

    • 如果UDP控制块没有指定本地IP,则获取指定的网卡的IP作为UDP本地IP。
    • 如果UDP控制块指定了本地IP,则这个IP必须和指定网卡的IP一致,否则不发送。
/**
* @ingroup udp_raw
* Send data to a specified address using UDP.
* The netif used for sending can be specified.
*
* This function exists mainly for DHCP, to be able to send UDP packets
* on a netif that is still down.
*
* @param pcb UDP PCB used to send the data.
* @param p chain of pbuf's to be sent.
* @param dst_ip Destination IP address.
* @param dst_port Destination UDP port.
* @param netif the netif used for sending.
*
* dst_ip & dst_port are expected to be in the same byte order as in the pcb.
*
* @return lwIP error code (@see udp_send for possible error codes)
*
* @see udp_disconnect() udp_send()
*/
err_t
udp_sendto_if(struct udp_pcb *pcb, struct pbuf *p,
const ip_addr_t *dst_ip, u16_t dst_port, struct netif *netif)
{
const ip_addr_t *src_ip;
/* 参数校验 */
LWIP_ERROR("udp_sendto_if: invalid pcb", pcb != NULL, return ERR_ARG);
LWIP_ERROR("udp_sendto_if: invalid pbuf", p != NULL, return ERR_ARG);
LWIP_ERROR("udp_sendto_if: invalid dst_ip", dst_ip != NULL, return ERR_ARG);
LWIP_ERROR("udp_sendto_if: invalid netif", netif != NULL, return ERR_ARG); if (!IP_ADDR_PCB_VERSION_MATCH(pcb, dst_ip)) {
return ERR_VAL;
} /* PCB本地地址是IP_ANY_ADDR还是多播? */
#if LWIP_IPV6
if (IP_IS_V6(dst_ip)) {
if (ip6_addr_isany(ip_2_ip6(&pcb->local_ip)) ||
ip6_addr_ismulticast(ip_2_ip6(&pcb->local_ip))) {
src_ip = ip6_select_source_address(netif, ip_2_ip6(dst_ip));
if (src_ip == NULL) {
/* 没有找到合适的源地址 */
return ERR_RTE;
}
} else {
/* 使用UDP PCB本地IPv6地址作为源地址,如果仍然有效 */
if (netif_get_ip6_addr_match(netif, ip_2_ip6(&pcb->local_ip)) < 0) {
/* 地址无效 */
return ERR_RTE;
}
src_ip = &pcb->local_ip;
}
}
#endif /* LWIP_IPV6 */
#if LWIP_IPV4 && LWIP_IPV6
else
#endif /* LWIP_IPV4 && LWIP_IPV6 */
#if LWIP_IPV4
if (ip4_addr_isany(ip_2_ip4(&pcb->local_ip)) ||
ip4_addr_ismulticast(ip_2_ip4(&pcb->local_ip))) {
/* 如果UDP控制块本地IP没有指定,或者指定的是多播地址,则使用指定的网卡的IP作为UDP本地IP即可 */
src_ip = netif_ip_addr4(netif);
} else { /* */
/* 检查UDP PCB本地IP地址是否正确,如果netif->ip_addr已更改,这可能是旧地址 */
if (!ip4_addr_cmp(ip_2_ip4(&(pcb->local_ip)), netif_ip4_addr(netif))) {
/* UDP本地IP和指定网卡的IP不匹配,不发送。 */
return ERR_RTE;
}
/* 确认使用的源IP */
src_ip = &pcb->local_ip;
}
#endif /* LWIP_IPV4 */
#if LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP
return udp_sendto_if_src_chksum(pcb, p, dst_ip, dst_port, netif, have_chksum, chksum, src_ip);
#else /* LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP */
return udp_sendto_if_src(pcb, p, dst_ip, dst_port, netif, src_ip);
#endif /* LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP */
}

11.13 UDP接收数据

UDP接收处理数据,南向是通过udp_input()API给IP层收到UDP数据报后上交到UDP协议处理。

udp_input()

  • struct pbuf *p:收到UDP报文的pbuf。

  • struct netif *inp:收到该UDP报文的网卡。

  • 参数校验。

  • 报文校验。

  • 匹配UDP PCB:通过IP和端口号确保该UDP报文得到某个应用程序。遍历UDP PCB udp_pcbs

    • UDP PCB 本地端口、IP和UDP报文目的端口和IP匹配:端口一致且IP匹配:

      • 当前UDP PCB没有指定本地IP,或UDP报文的目的IP就是指向当前UDP PCB的IP。本地可以匹配成功。
      • 如果UDP报文对应的目的IP是一个广播地址,且当前UDP设置了SOF_BROADCAST选项。这个IP是全广播地址或者和当前UDP PCB IP处于同一个子网。本地可以匹配成功。
      • 如果UDP PCB 本地端口、IP和UDP报文目的端口和IP匹配成功后,但是该UDP PCB还没有处于连接状态,则可以记录到uncon_pcb变量中,有更适合且未连接的UDP PCB适配本次UDP报文的,更新到uncon_pcb中。
    • UDP PCB 远端端口、IP和UDP报文源端口和IP匹配:端口一致且IP匹配:

      • UDP PCB远端IP随意或者就是当前UDP报文的源IP。远端匹配成功。
      • 如果UDP PCB 远端端口、IP和UDP报文源端口和IP匹配失败,则可以使用UDP PCB 本地端口、IP和UDP报文目的端口和IP匹配但是未连接的UDP PCBuncon_pcb
    • 上述都匹配成功后,UDP PCB即可匹配成功,当前 UDP 报文是给我们的。

  • 校验和校验:

    • UDP协议:校验和字段为0,不用校验。校验和字段不为0,则全部校验。
    • UDP LITE协议:UDP报文的总长度字段值即为需要进行校验和计算的数据长度。注意:长度字段为0表示整个报文校验。(参考RFC 3828章3.1)
  • pbuf偏移头部,指向UDP数据区。即是用户数据。

  • 如果开启了SOF_REUSEADDR选项:则把当前UDP PCB包复制转发到所有能匹配成功的UDP PCB。

    • 如果没有开启该选项,当前UDP报文就只递交给第一个匹配成功的DUP PCB了。
  • 把数据回调到上层应用:pcb->recv()

/**
* Process an incoming UDP datagram.
*
* Given an incoming UDP datagram (as a chain of pbufs) this function
* finds a corresponding UDP PCB and hands over the pbuf to the pcbs
* recv function. If no pcb is found or the datagram is incorrect, the
* pbuf is freed.
*
* @param p pbuf to be demultiplexed to a UDP PCB (p->payload pointing to the UDP header)
* @param inp network interface on which the datagram was received.
*
*/
void
udp_input(struct pbuf *p, struct netif *inp)
{
struct udp_hdr *udphdr;
struct udp_pcb *pcb, *prev;
struct udp_pcb *uncon_pcb;
u16_t src, dest;
u8_t broadcast;
u8_t for_us = 0; LWIP_UNUSED_ARG(inp); LWIP_ASSERT_CORE_LOCKED(); /* 确保在内核锁内 */
/* 参数校验 */
LWIP_ASSERT("udp_input: invalid pbuf", p != NULL);
LWIP_ASSERT("udp_input: invalid netif", inp != NULL); PERF_START; UDP_STATS_INC(udp.recv); /* 检查最小长度(UDP首部) */
if (p->len < UDP_HLEN) {
/* drop short packets */
LWIP_DEBUGF(UDP_DEBUG,
("udp_input: short UDP datagram (%"U16_F" bytes) discarded\n", p->tot_len));
UDP_STATS_INC(udp.lenerr);
UDP_STATS_INC(udp.drop);
MIB2_STATS_INC(mib2.udpinerrors);
pbuf_free(p);
goto end;
}
/* 提取UDP首部 */
udphdr = (struct udp_hdr *)p->payload; /* 检查是否是广播包 */
broadcast = ip_addr_isbroadcast(ip_current_dest_addr(), ip_current_netif()); LWIP_DEBUGF(UDP_DEBUG, ("udp_input: received datagram of length %"U16_F"\n", p->tot_len)); src = lwip_ntohs(udphdr->src); /* UDP报文的源端口号 */
dest = lwip_ntohs(udphdr->dest); /* UDP报文的目的端口号 */ udp_debug_print(udphdr); /* 打印相关信息 */
LWIP_DEBUGF(UDP_DEBUG, ("udp ("));
ip_addr_debug_print_val(UDP_DEBUG, *ip_current_dest_addr());
LWIP_DEBUGF(UDP_DEBUG, (", %"U16_F") <-- (", lwip_ntohs(udphdr->dest)));
ip_addr_debug_print_val(UDP_DEBUG, *ip_current_src_addr());
LWIP_DEBUGF(UDP_DEBUG, (", %"U16_F")\n", lwip_ntohs(udphdr->src))); pcb = NULL;
prev = NULL;
uncon_pcb = NULL;
/* 遍历UDP PCB列表以找到匹配的PCB。
匹配pcb:连接到远程端口和ip地址优先。
如果没有找到完全匹配的,那么与本地端口和ip地址匹配的第一个未连接的pcb将获得数据报 */
for (pcb = udp_pcbs; pcb != NULL; pcb = pcb->next) {
/* 每次遍历都打印PCB本地和远端IP地址和端口号 */
LWIP_DEBUGF(UDP_DEBUG, ("pcb ("));
ip_addr_debug_print_val(UDP_DEBUG, pcb->local_ip);
LWIP_DEBUGF(UDP_DEBUG, (", %"U16_F") <-- (", pcb->local_port));
ip_addr_debug_print_val(UDP_DEBUG, pcb->remote_ip);
LWIP_DEBUGF(UDP_DEBUG, (", %"U16_F")\n", pcb->remote_port)); /* 匹配UDP PCB 本地端口、IP和UDP报文目的端口和IP */
if ((pcb->local_port == dest) &&
(udp_input_local_match(pcb, inp, broadcast) != 0)) {
if ((pcb->flags & UDP_FLAGS_CONNECTED) == 0) {
if (uncon_pcb == NULL) {
/* 第一个未连接的匹配PCB */
uncon_pcb = pcb;
#if LWIP_IPV4
} else if (broadcast && ip4_current_dest_addr()->addr == IPADDR_BROADCAST) {
/* 全局广播地址(仅对IPv4有效;之前检查过匹配) */
if (!IP_IS_V4_VAL(uncon_pcb->local_ip) || !ip4_addr_cmp(ip_2_ip4(&uncon_pcb->local_ip), netif_ip4_addr(inp))) {
/* uncon_pcb 与收到数据netif不匹配,则需要重新检查此PCB */
if (IP_IS_V4_VAL(pcb->local_ip) && ip4_addr_cmp(ip_2_ip4(&pcb->local_ip), netif_ip4_addr(inp))) {
/* 更新uncon_pcb */
uncon_pcb = pcb;
}
}
#endif /* LWIP_IPV4 */
}
/* 支持SOF_REUSEADDR选项功能。
因为如果没有开启这个功能,那前面两个if的匹配逻辑就能找到唯一一个符合要求的uncon_pcb。
如果支持SOF_REUSEADDR功能,UDP PCB中就可能存在多个匹配成功未连接的PCB,这样选第一个即可(靠近链表尾,即是老的) */
#if SO_REUSE
else if (!ip_addr_isany(&pcb->local_ip)) {
/* 更加倾向于有指定本地IP未连接的PCB */
uncon_pcb = pcb;
}
#endif /* SO_REUSE */
} /* 匹配UDP PCB 远端端口、IP和UDP报文源端口和IP */
if ((pcb->remote_port == src) &&
(ip_addr_isany_val(pcb->remote_ip) ||
ip_addr_cmp(&pcb->remote_ip, ip_current_src_addr()))) {
/* 第一个完全匹配的PCB */
if (prev != NULL) {
/* 将PCB移动到udp_pcbs的前面,以便下次更快地找到它 */
prev->next = pcb->next;
pcb->next = udp_pcbs;
udp_pcbs = pcb;
} else {
UDP_STATS_INC(udp.cachehit);
}
break;
}
} prev = pcb; /* 遍历下一个UDP PCB */
}
/* 没有找到完全匹配的PCB,就使用未连接的匹配PCB */
if (pcb == NULL) {
pcb = uncon_pcb;
} /* 最终检查当前UDP报文是不是给我们的 */
if (pcb != NULL) {
for_us = 1; /* UDP PCB匹配成功,是给我们的 */
} else { /* UDP PCB匹配不成功 */
#if LWIP_IPV6
if (ip_current_is_v6()) {
/* 检查下当前UDP报文的目的IP是不是给我们的 */
for_us = netif_get_ip6_addr_match(inp, ip6_current_dest_addr()) >= 0;
}
#endif /* LWIP_IPV6 */
#if LWIP_IPV4
if (!ip_current_is_v6()) {
/* 检查下当前UDP报文的目的IP是不是给我们的 */
for_us = ip4_addr_cmp(netif_ip4_addr(inp), ip4_current_dest_addr());
}
#endif /* LWIP_IPV4 */
} if (for_us) { /* 当前UDP包是给我们的 */
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, ("udp_input: calculating checksum\n"));
/* 校验和校验 */
#if CHECKSUM_CHECK_UDP
IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_CHECK_UDP) {
#if LWIP_UDPLITE
if (ip_current_header_proto() == IP_PROTO_UDPLITE) { /* UDP LITE协议:总长度字段就是需要进行校验和的数据长度 */
u16_t chklen = lwip_ntohs(udphdr->len);
if (chklen < sizeof(struct udp_hdr)) {
if (chklen == 0) {
/* 对于UDP-Lite,校验和长度为0表示对整个报文的校验和(参考RFC 3828章3.1) */
chklen = p->tot_len;
} else {
/* 至少UDP-Lite头必须被校验和覆盖!(再次参考RFC 3828第3.1章) */
goto chkerr;
}
}
/* 加上伪首部,一起进行校验和 */
if (ip_chksum_pseudo_partial(p, IP_PROTO_UDPLITE,
p->tot_len, chklen,
ip_current_src_addr(), ip_current_dest_addr()) != 0) {
goto chkerr;
}
} else
#endif /* LWIP_UDPLITE */
{ /* UDP协议 */
if (udphdr->chksum != 0) { /* 校验和字段不为0,则说明要进行校验和计算 */
/* 加上伪首部,一起进行校验和 */
if (ip_chksum_pseudo(p, IP_PROTO_UDP, p->tot_len,
ip_current_src_addr(),
ip_current_dest_addr()) != 0) {
goto chkerr;
}
}
}
}
#endif /* CHECKSUM_CHECK_UDP */
if (pbuf_remove_header(p, UDP_HLEN)) { /* pbuf指向UDP数据区,即是用户数据 */
/* Can we cope with this failing? Just assert for now */
LWIP_ASSERT("pbuf_remove_header failed\n", 0);
UDP_STATS_INC(udp.drop);
MIB2_STATS_INC(mib2.udpinerrors);
pbuf_free(p);
goto end;
} if (pcb != NULL) { /* 如果已成功匹配PCB */
MIB2_STATS_INC(mib2.udpindatagrams);
#if SO_REUSE && SO_REUSE_RXTOALL /* SOF_REUSEADDR选项功能 */
/* 如果设置了SOF_REUSEADDR选项功能,则说明可能存在多个匹配成功的PBC,都需要把当前UDP报文拷贝传递过去 */
if (ip_get_option(pcb, SOF_REUSEADDR) &&
(broadcast || ip_addr_ismulticast(ip_current_dest_addr()))) {
/* 如果SOF_REUSEADDR在第一次匹配时设置,则将广播或组播数据包传递给所有组播pcb */
struct udp_pcb *mpcb;
for (mpcb = udp_pcbs; mpcb != NULL; mpcb = mpcb->next) {
if (mpcb != pcb) { /* 跳过前面匹配成功的PCB(后面会处理) */
/* 比较PCB本地IP地址+端口号 和 UDP目的IP地址+端口号 */
if ((mpcb->local_port == dest) &&
(udp_input_local_match(mpcb, inp, broadcast) != 0)) {
/* 将一个包的副本传递给所有本地匹配 */
if (mpcb->recv != NULL) {
struct pbuf *q;
/* 拷贝UDP报文 */
q = pbuf_clone(PBUF_RAW, PBUF_POOL, p);
if (q != NULL) {
/* 回调到用户层 */
mpcb->recv(mpcb->recv_arg, mpcb, q, ip_current_src_addr(), src);
}
}
}
}
}
}
#endif /* SO_REUSE && SO_REUSE_RXTOALL */ if (pcb->recv != NULL) {
/* 把数据回调到对应UDP PCB的应用程序 */
pcb->recv(pcb->recv_arg, pcb, p, ip_current_src_addr(), src);
} else {
/* 没有recv功能注册,那就得释放pbuf! */
pbuf_free(p);
goto end;
}
} else {
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, ("udp_input: not for us.\n")); #if LWIP_ICMP || LWIP_ICMP6
/* 没有找到匹配项,发送ICMP目的端口不可达,除非目的地址是广播/组播 */
if (!broadcast && !ip_addr_ismulticast(ip_current_dest_addr())) {
/* 将pbuf数据区指针移回IP头 */
pbuf_header_force(p, (s16_t)(ip_current_header_tot_len() + UDP_HLEN));
icmp_port_unreach(ip_current_is_v6(), p);
}
#endif /* LWIP_ICMP || LWIP_ICMP6 */
UDP_STATS_INC(udp.proterr);
UDP_STATS_INC(udp.drop);
MIB2_STATS_INC(mib2.udpnoports);
pbuf_free(p);
}
} else { /* 当前UDP报文不是给我们的,丢弃 */
pbuf_free(p);
}
end:
PERF_STOP("udp_input");
return;
#if CHECKSUM_CHECK_UDP
chkerr:
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
("udp_input: UDP (or UDP Lite) datagram discarded due to failing checksum\n"));
UDP_STATS_INC(udp.chkerr);
UDP_STATS_INC(udp.drop);
MIB2_STATS_INC(mib2.udpinerrors);
pbuf_free(p);
PERF_STOP("udp_input");
#endif /* CHECKSUM_CHECK_UDP */
}

11.14 UDP RAW接口编程

UDP层初始化接口:

void             udp_init       (void);

南向供IP层使用:

void             udp_input      (struct pbuf *p, struct netif *inp);

UDP RAW相关接口分析:北向,供用户使用

struct udp_pcb * udp_new        (void);
struct udp_pcb * udp_new_ip_type(u8_t type);
void udp_remove (struct udp_pcb *pcb);
err_t udp_bind (struct udp_pcb *pcb, const ip_addr_t *ipaddr,
u16_t port);
void udp_bind_netif (struct udp_pcb *pcb, const struct netif* netif);
err_t udp_connect (struct udp_pcb *pcb, const ip_addr_t *ipaddr,
u16_t port);
void udp_disconnect (struct udp_pcb *pcb);
void udp_recv (struct udp_pcb *pcb, udp_recv_fn recv,
void *recv_arg);
err_t udp_sendto_if (struct udp_pcb *pcb, struct pbuf *p,
const ip_addr_t *dst_ip, u16_t dst_port,
struct netif *netif);
err_t udp_sendto_if_src(struct udp_pcb *pcb, struct pbuf *p,
const ip_addr_t *dst_ip, u16_t dst_port,
struct netif *netif, const ip_addr_t *src_ip);
err_t udp_sendto (struct udp_pcb *pcb, struct pbuf *p,
const ip_addr_t *dst_ip, u16_t dst_port);
err_t udp_send (struct udp_pcb *pcb, struct pbuf *p);

11.15 个人博客

【lwip】11-UDP协议&源码分析的更多相关文章

  1. 【lwip】10-ICMP协议&源码分析

    目录 前言 10.1 ICMP简介 10.2 ICMP报文 10.2.1 ICMP报文格式 10.2.2 ICMP报文类型 10.2.3 ICMP报文固定首部字段意义 10.3 ICMP差错报告报文 ...

  2. 透视RPC协议:SOFA-BOLT协议源码分析

    前提 最近在看Netty相关的资料,刚好SOFA-BOLT是一个比较成熟的Netty自定义协议栈实现,于是决定研读SOFA-BOLT的源码,详细分析其协议的组成,简单分析其客户端和服务端的源码实现. ...

  3. learning armbian steps(11) ----- armbian 源码分析(六)

    接下来我们来分析一下uboot的编写过程: 从 lib/compilation.sh  89开始阅读: compile_uboot() { # not optimal, but extra clean ...

  4. python基础-11 socket,IO多路复用,select伪造多线程,select读写分离。socketserver源码分析

    Socket socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求. sock ...

  5. 【lwip】07-链路层收发以太网数据帧源码分析

    目录 前言 7.1 链路层概述 7.2 MAC地址的基本概念 7.3 以太网帧结构 7.4 以太网帧结构 7.5 以太网帧报文数据结构 7.6 发送以太网数据帧 7.7 接收以太网数据帧 7.8 虚拟 ...

  6. dubbo源码分析4-基于netty的dubbo协议的server

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

  7. Nginx学习笔记(四) 源码分析&socket/UDP/shmem

    源码分析 在茫茫的源码中,看到了几个好像挺熟悉的名字(socket/UDP/shmem).那就来看看这个文件吧!从简单的开始~~~ src/os/unix/Ngx_socket.h&Ngx_s ...

  8. Solr4.8.0源码分析(11)之Lucene的索引文件(4)

    Solr4.8.0源码分析(11)之Lucene的索引文件(4) 1. .dvd和.dvm文件 .dvm是存放了DocValue域的元数据,比如DocValue偏移量. .dvd则存放了DocValu ...

  9. 比特币源码分析--C++11和boost库的应用

    比特币源码分析--C++11和boost库的应用     我们先停下探索比特币源码的步伐,来分析一下C++11和boost库在比特币源码中的应用.比特币是一个纯C++编写的项目,用到了C++11和bo ...

随机推荐

  1. Java中字节流的总结及代码练习

    Java中的字节流 在描述字节流时,先知道什么是流 流可以分为:输入流和输出流 输入流和输出流 示意图: 字节流读取内容:二进制,音频,视频 优缺点:可以保证视频音频无损,效率低,没有缓冲区 字节流可 ...

  2. SpringMVC 03: 请求和响应的乱码解决 + SpringMVC响应Ajax请求

    请求或响应的中文乱码问题 tomcat9解决了get请求和响应的中文乱码问题,但是没有解决post请求或响应的中文乱码问题 tomcat10解决了get和post请求以及响应的中文乱码问题 考虑到实际 ...

  3. docker可视化

    可视化第一种方式 Portainer(不是最佳选择但先用这个) docker run -d -p 8088:9000 \ #docker run 启动:通过内网9000端口,外网8088端口:rest ...

  4. KingbaseES insert all/first 功能介绍

    KingbaseES 内置了对于insert all / first 语法的支持. 一.数据准备 create table t1(product_id number, product_name var ...

  5. IIS 实现http重定向https(亲测有效:解决URL重写模块配置https重定向不生效的问题)

    前言 以前部署网站的时候,都是通过代码来实现http重定向https,最近在部署个人网站的时候,突发奇想可不可通过IIS来实现无代码的重定向呢? 在一番操作猛如虎的搜索引擎操作后,发现只有google ...

  6. 【微服务】Nacos初体验

    SpringCloud - Nacos初体验 生命不息,写作不止 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 一个有梦有戏的人 @怒放吧德德 分享学习心得,欢迎指正,大家一起学习成长 ...

  7. 微服务系列之授权认证(二) identity server 4

    1.简介 IdentityServer4 是为ASP.NET Core系列量身打造的一款基于 OpenID Connect 和 OAuth 2.0 认证授权框架. 官方文档:https://ident ...

  8. Stream流式计算

    Stream流式计算 集合/数据库用来进行数据的存储 而计算则交给流 /** * 现有5个用户,用一行代码 ,一分钟按以下条件筛选出指定用户 *1.ID必须是偶数 *2.年龄必须大于22 *3.用户名 ...

  9. 为MinIO Server设置Nginx代理

    官方文档地址:http://docs.minio.org.cn/docs/master/setup-nginx-proxy-with-minio nginx参考网址:https://www.nginx ...

  10. k8s上安装安装 Ingress Controller &卸载

    在 master 节点上执行 nginx-ingress.yaml文件内容 # 如果打算用于生产环境,请参考 https://github.com/nginxinc/kubernetes-ingres ...