前面讲发送回送请求时介绍了原始协议控制块raw_pcb,udp的实现和它原理一样,内核会为udp维护udp控制块链表通过遍历来寻找匹配的控制块,然后执行控制块内回调函数。udp的实现在udp.c/h
中
数据结构
在udp.h
中
udp控制块
在udp.h
中,这里可以和raw_pcb对比下,后面的操作都可以和原始控制块操作对比下
udp raw编程接口
新建udp_pcb
如果MEMP_UDP_PCB
类型的内存池用完就无法申请,个数可以在lwipotps.h
中定义
绑定本地ip、port
实质是设置udp_pcb内部本地ip、port字段并把他添加到udp_pcbs链表上
/**
* 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_ADDR_ANY 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 occured.
* - 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, ip_addr_t *ipaddr, u16_t port)
{
struct udp_pcb *ipcb;
u8_t rebind;//是否已经插入局部变量
rebind = 0;//遍历udp_pcbs,判断是否已经插入并判断是否重复绑定
/* 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) {
/* pcb may occur at most once in active list */
LWIP_ASSERT("rebind == 0", rebind == 0);
/* pcb already in list, just rebind */
rebind = 1;
}
/* By default, we don't allow to bind to a port that any other udp
PCB is alread bound to, unless *all* PCBs with that port have tha
REUSEADDR flag set. */
#if SO_REUSE
else if (!ip_get_option(pcb, SOF_REUSEADDR) &&
!ip_get_option(ipcb, SOF_REUSEADDR)) {
#else /* SO_REUSE */
/* port matches that of PCB in list and REUSEADDR not set -> reject */
else {
#endif /* SO_REUSE */
if ((ipcb->local_port == port) &&
/* IP address matches, or one is IP_ADDR_ANY? */
(ip_addr_isany(&(ipcb->local_ip)) ||
ip_addr_isany(ipaddr) ||
ip_addr_cmp(&(ipcb->local_ip), ipaddr))) {
/* other PCB already binds to this local IP and port */
LWIP_DEBUGF(UDP_DEBUG,
("udp_bind: local port %"U16_F" already bound by another pcb\n", port));
return ERR_USE;//重复绑定则返回错误
}
}
}
ip_addr_set(&pcb->local_ip, ipaddr);
/* no port specified? */
if (port == 0) {
port = udp_new_port();//没有填端口则自动选择一个有效端口
if (port == 0) {
/* no more ports available in local range */
LWIP_DEBUGF(UDP_DEBUG, ("udp_bind: out of free UDP ports\n"));
return ERR_USE;
}
}
pcb->local_port = port;
/* pcb not active yet? */
if (rebind == 0) {//没有插入则插入
/* place the PCB on the active list if not already there */
pcb->next = udp_pcbs;
udp_pcbs = pcb;
}
return ERR_OK;
}
连接控制块
本质是设置udp_pcb中远程ip、port字段并设置已连接标志
/**
* Connect an UDP PCB.
*
* This will associate the UDP PCB with the remote address.
*
* @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, ip_addr_t *ipaddr, u16_t port)
{
struct udp_pcb *ipcb;
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(&pcb->remote_ip, ipaddr);
pcb->remote_port = port;
pcb->flags |= UDP_FLAGS_CONNECTED;
/* 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;
}
断开控制块
本质是清除udp_pcb中远程ip、port字段、已连接标志
注册数据接收函数
发送函数
udp_send
和udp_sendto
都可以,实质是调用udp_sendto_if
/**
* Send data using UDP.
*
* @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 occured.
* - ERR_MEM. Out of memory.
* - ERR_RTE. Could not find route to destination address.
* - 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)
{
/* send to the packet using remote ip and port stored in the pcb */
return udp_sendto(pcb, p, &pcb->remote_ip, pcb->remote_port);
}
/**
* 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,
ip_addr_t *dst_ip, u16_t dst_port)
{
struct netif *netif;
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, ("udp_send\n"));
//调用ip层函数,寻找发送报文的接口,即源ip
netif = ip_route(dst_ip);
/* no outgoing network interface could be found? */
if (netif == NULL) {
return ERR_RTE;
}
return udp_sendto_if(pcb, p, dst_ip, dst_port, netif);
}
/**
* 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,
ip_addr_t *dst_ip, u16_t dst_port, struct netif *netif)
{
struct udp_hdr *udphdr;
ip_addr_t *src_ip;
err_t err;
struct pbuf *q; /* q will be sent down the stack */
/* if the PCB is not yet bound to a port, bind it here */
if (pcb->local_port == 0) {//未绑定则绑定
err = udp_bind(pcb, &pcb->local_ip, pcb->local_port);
if (err != ERR_OK) {
return err;
}
}
//构造udp首部
/* not enough space to add an UDP header to first pbuf in given p chain? */
if (pbuf_header(p, UDP_HLEN)) {//这里判断用户数据pbuf前面是否能放的下udp首部
/* allocate header in a separate new pbuf */
q = pbuf_alloc(PBUF_IP, UDP_HLEN, PBUF_RAM);//放不下则要申请个装udp首部的pbuf
/* new header pbuf could not be allocated? */
if (q == NULL) {
return ERR_MEM;
}
if (p->tot_len != 0) {
/* chain header q in front of given pbuf p (only if p contains data) */
pbuf_chain(q, p);//把2个pbuf连连来,头指针为q
}
/* first pbuf q points to header pbuf */
} else { //装的下则直接q指向p
/* adding space for header within p succeeded */
/* first pbuf q equals given pbuf */
q = p;
}
/* q now represents the packet to be sent */
udphdr = (struct udp_hdr *)q->payload;//指向udp首部
udphdr->src = htons(pcb->local_port);//填本地port
udphdr->dest = htons(dst_port);//填远程port
/* in UDP, 0 checksum means 'no checksum' */
udphdr->chksum = 0x0000;
/* PCB local address is IP_ANY_ADDR? */
if (ip_addr_isany(&pcb->local_ip)) {//如果本地ip是0则使用netif中ip
/* use outgoing network interface IP address as source address */
src_ip = &(netif->ip_addr);
} else { //如果本地ip和netif中ip不一至则不发送直接返回
/* check if UDP PCB local IP address is correct
* this could be an old address if netif->ip_addr has changed */
if (!ip_addr_cmp(&(pcb->local_ip), &(netif->ip_addr))) {
/* local_ip doesn't match, drop the packet */
if (q != p) {
/* free the header pbuf */
pbuf_free(q);
q = NULL;
/* p is still referenced by the caller, and will live on */
}
return ERR_VAL;
}
/* use UDP PCB local IP address as source address */
src_ip = &(pcb->local_ip); //伪首部源ip
}
udphdr->len = htons(q->tot_len); //首部总长度
/* calculate checksum */
if ((pcb->flags & UDP_FLAGS_NOCHKSUM) == 0) {
u16_t udpchksum;
//计算udp校验和
udpchksum = inet_chksum_pseudo(q, src_ip, dst_ip, IP_PROTO_UDP, q->tot_len);
/* chksum zero must become 0xffff, as zero means 'no checksum' */
if (udpchksum == 0x0000) {
udpchksum = 0xffff;
}
udphdr->chksum = udpchksum;
}
/* output to IP *///调用ip层函数发送
err = ip_output_if(q, src_ip, dst_ip, pcb->ttl, pcb->tos, IP_PROTO_UDP, netif);
/* did we chain a separate header pbuf earlier? */
if (q != p) {
/* free the header pbuf */
pbuf_free(q);
q = NULL;
/* p is still referenced by the caller, and will live on */
}
return err;
}
接收函数
接收处理是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
,我就不粘贴了。