lwip有3种编成接口,分别为:RAW、NETCONN和SOCKET。
RAW:RAW编程接口不需要操作系统的支持,可以直接裸机使用LWIP,但是RAW的编程接口比较复杂,RAW使用的是回调机制,需要大家了解回调函数。
NETCONN和SOCKET:这两种编程接口需要有操作系统的支持,否则的话没法使用,但是这两种接口使用起来比较简单。
udp控制块
控制块里面描述了一个UDP连接的所有信息,包括源端口号、目的端口号、源IP地址、目的IP地址等。用户的UDP编程,本质上都是对UDP的控制块进行操作。
系统为每一个连接分配一个UDP控制块,并将其组织在一个链表上,当UDP层收到IP层的报文时,会去遍历整个链表,找出与报文部首匹配的控制块,并调用控制块中注册的函数来完成报文的处理。
在udp.h中:
struct udp_pcb {
/** Common members of all PCB types */
IP_PCB;
/* Protocol specific PCB members */
struct udp_pcb *next;
//标识UDP控制块状态信息
u8_t flags;
/** ports are in host byte order */
u16_t local_port, remote_port;
#if LWIP_MULTICAST_TX_OPTIONS
#if LWIP_IPV4
/** outgoing network interface for multicast packets, by IPv4 address (if not 'any') */
ip4_addr_t mcast_ip4;
#endif /* LWIP_IPV4 */
/** outgoing network interface for multicast packets, by interface index (if nonzero) */
u8_t mcast_ifindex;
/** TTL for outgoing multicast packets */
u8_t mcast_ttl;
#endif /* LWIP_MULTICAST_TX_OPTIONS */
#if LWIP_UDPLITE
/** used for UDP_LITE only */
u16_t chksum_len_rx, chksum_len_tx;
#endif /* LWIP_UDPLITE */
/** receive callback function */
//回调函数
//UDP协议接收数据后处理过程
//lwip内核调用
udp_recv_fn recv;
//回调函数的参数
/** user-supplied argument for the recv callback */
void *recv_arg;
};
udp raw编程接口
- 新建一个UDP的PCB控制块
struct udp_pcb *
udp_new(void)
{
struct udp_pcb *pcb;
LWIP_ASSERT_CORE_LOCKED();
pcb = (struct udp_pcb *)memp_malloc(MEMP_UDP_PCB);
/* could allocate UDP PCB? */
if (pcb != NULL) {
/* UDP Lite: by initializing to all zeroes, chksum_len is set to 0
* which means checksum is generated over the whole datagram per default
* (recommended as default by RFC 3828). */
/* initialize PCB to all zeroes */
memset(pcb, 0, sizeof(struct udp_pcb));
pcb->ttl = UDP_TTL;
#if LWIP_MULTICAST_TX_OPTIONS
udp_set_multicast_ttl(pcb, UDP_TTL);
#endif /* LWIP_MULTICAST_TX_OPTIONS */
}
return pcb;
}
如果MEMP_UDP_PCB类型的内存池用完就无法申请,个数可以在lwipotps.h中定义。
- 将一个pcb控制块从链表中删除,并释放这个控制块的内存
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);
/* pcb to be removed is first in list? */
if (udp_pcbs == pcb) {
/* make list start at 2nd pcb */
udp_pcbs = udp_pcbs->next;
/* pcb not 1st in list */
} else {
for (pcb2 = udp_pcbs; pcb2 != NULL; pcb2 = pcb2->next) {
/* find pcb in udp_pcbs list */
if (pcb2->next != NULL && pcb2->next == pcb) {
/* remove pcb from list */
pcb2->next = pcb->next;
break;
}
}
}
memp_free(MEMP_UDP_PCB, pcb);
}
- 为UDP的PCB绑定一个本地ip地址和端口号
实质是设置udp_pcb内部本地ip、port字段并把他添加到udp_pcbs链表上
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;
}
#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;
/* Check for double bind and rebind of the same pcb */
for (ipcb = udp_pcbs; ipcb != NULL; ipcb = ipcb->next) {
/* is this UDP PCB already on active list? */
if (pcb == ipcb) {
rebind = 1;
break;
}
}
- 连接到指定的ip地址主机的指定端口号,其实就是设置pcb控制块的remote_ip和remote_port
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_addr_set_ipaddr(&pcb->remote_ip, ipaddr);
#if LWIP_IPV6 && LWIP_IPV6_SCOPES
/* 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;
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));
/* Insert UDP PCB into the list of active UDP PCBs. */
for (ipcb = udp_pcbs; ipcb != NULL; ipcb = ipcb->next) {
if (pcb == ipcb) {
/* already on the list, just return */
return ERR_OK;
}
}
/* PCB not yet on the list, add PCB now */
pcb->next = udp_pcbs;
udp_pcbs = pcb;
return ERR_OK;
}
- 断开连接
void
udp_disconnect(struct udp_pcb *pcb)
{
LWIP_ASSERT_CORE_LOCKED();
LWIP_ERROR("udp_disconnect: invalid pcb", pcb != NULL, return);
/* reset remote address association */
#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;
/* mark PCB as unconnected */
udp_clear_flags(pcb, UDP_FLAGS_CONNECTED);
}
- 通过一个PCB控制块发送数据
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;
}
/* send to the packet using remote ip and port stored in the pcb */
return udp_sendto(pcb, p, &pcb->remote_ip, pcb->remote_port);
}
- 接收函数
接收处理是ip层收到ip数据报后通过协议字段判断是udp后调用udp_input
udp_input根据报文中源目ip、port寻找匹配的udp控制块,
port匹配原则如下,
1.首先寻找与报文中源目port都匹配的且处于连接状态的控制块,若没有
2.寻找与报文中目地port匹配的非连接状态的控制块
ip匹配原则如下,
1.报文是非广播包,且本地绑定的是任意ip
2.绑定的ip和报文目的ip一致
3.对于广播包,控制块的ip要和广播包在同一网段
如果匹配不到会返回一个端口不可达icmp报文
匹配好后就调用之前注册的回调函数,注意收到的数据包由用户释放,即回调函数内部释放。代码在udp.c,我就不粘贴了。