【LwIP源码学习6】UDP部分源码分析

前言

本文对lwip协议栈里UDP部分的源码进行分析。将源码中最关键部分提取出来,梳理UDP部分的设计框架。

应用层例子

首先举一个应用层使用UDP的简单例子,如下:

//第一步:申请套接字
sock = socket(AF_INET, SOCK_DGRAM, 0);
//第二步:绑定套接字与网卡信息
bind(sock, (struct sockaddr *)&udp_addr, sizeof(struct sockaddr)
while(1)
{
/*第三步:接收数据*/
	 recv_data_len=recvfrom(sock,recv_data,
                             RECV_DATA,0,
                             (struct sockaddr*)&seraddr,
                             &addrlen);


/*第四步:发送数据*/
      sendto(sock,recv_data,
             recv_data_len,0,
             (struct sockaddr*)&seraddr,
             addrlen);
}

而这些函数的原型如下:
sockets.h文件

#define socket(domain,type,protocol)              lwip_socket(domain,type,protocol)
#define bind(s,name,namelen)                      lwip_bind(s,name,namelen)
#define recvfrom(s,mem,len,flags,from,fromlen)    lwip_recvfrom(s,mem,len,flags,from,fromlen)
#define sendto(s,dataptr,size,flags,to,tolen)     lwip_sendto(s,dataptr,size,flags,to,tolen)

申请套接字

申请套接字使用lwip_socket函数,代码如下,首先调用netconn_new_with_proto_and_callback函数申请一个netconn结构体,然后调用alloc_socket函数在全局变量sockets[NUM_SOCKETS]中找出一个合适的socket分配给用户,可以看到最多可以申请4个套接字。
socket.c文件

#define NUM_SOCKETS 4

static struct lwip_sock sockets[NUM_SOCKETS];

unsigned int lwip_socket(int domain, int type, int protocol)
{
    int i;
    struct netconn *conn;

    conn = netconn_new_with_proto_and_callback(1, 0, NULL);
    i = alloc_socket(conn, 0);
    conn->socket = i;

    return conn->socket
}

static int
alloc_socket(struct netconn *newconn, int accepted)
{
    int i;
    for (i = 0; i < NUM_SOCKETS; i++)
    {
        if (!sockets[i].conn)
        {
            sockets[i].conn = newconn;
            return i;
        }
    }
    
    return -1;
}

以下是netconn_new_with_proto_and_callback函数,首先调用netconn_alloc创建一个netconn,然后调用pcb_new做进一步处理,这里的api_msg用于协调用户线程与内核线程的通信。
api_lib.c文件

struct netconn * 
netconn_new_with_proto_and_callback(enum netconn_type t, char proto, netconn_callback callback)
{
    struct netconn *conn;
    struct api_msg msg;
    
    conn = netconn_alloc(t, callback);

    msg.conn = conn;

    pcb_new(&msg);

    return conn;
}

上面调用的两个函数都在api_msg.c文件里,如下,
netconn_alloc函数用于申请内存,然后创建一个消息队列赋值给conn->recvmbox,然后赋值一下回调函数。
pcb_new函数首先调用udp_new函数创建一个udp_pcb,然后调用udp_recv函数将recv_udp函数与新创建的udp_pcb进行绑定,让recv_udp函数作为这个udp_pcb接收到网口数据时的回调函数。
recv_udp函数里将接收到的数据放入conn->recvmbox指向的消息队列中。所以lwip里UDP协议接收数据的过程是:内核接收到数据后放入消息队列里,应用层从消息队列里取出数据做进一步处理

api_msg.c文件

struct netconn *
netconn_alloc(enum netconn_type t, netconn_callback callback)
{
    conn = (struct netconn *)memp_malloc(MEMP_NETCONN);
    sys_mbox_new(&conn->recvmbox, size);
    conn->callback     = callback;
    
    return conn;
}

static void
pcb_new(struct api_msg *msg)
{
   msg->conn->pcb.udp = udp_new();

   udp_recv(msg->conn->pcb.udp, recv_udp, msg->conn);

}

static void
recv_udp(void *arg, struct udp_pcb *pcb, struct pbuf *p,
         const int *addr, char port)
{
    sys_mbox_trypost(&conn->recvmbox, buf);
}

上面使用到的两个函数在udp.c文件里,如下。
udp_new函数主要就是为新的udp_pcb申请内存。
udp_recv主要设置接收数据的回调函数,也就是api_msg.c文件里的recv_udp函数

udp.c文件

struct udp_pcb *
udp_new(void)
{
  struct udp_pcb *pcb;


  pcb = (struct udp_pcb *)malloc(sizeof(udp_pcb));

  return pcb;
}

void
udp_recv(struct udp_pcb *pcb, udp_recv_fn recv, void *recv_arg)
{
    pcb->recv = recv;
    pcb->recv_arg = recv_arg;  
}

绑定套接字与网卡信息

绑定套接字用到lwip_bind函数,如下,主要调用netconn_bind函数来绑定本地IP地址和端口号。

socket.c文件

int
lwip_bind(int s, const struct sockaddr *name, socklen_t namelen)
{
    struct lwip_sock *sock;
    sock = get_socket(s);

    err = netconn_bind(sock->conn, &local_addr, local_port);
    return 0;
}

lwip_bind函数调用的netconn_bind函数如下,
api_lib.c文件

err_t
netconn_bind(struct netconn *conn, const ip_addr_t *addr, u16_t port)
{
    err = netconn_apimsg(lwip_netconn_do_bind, &API_MSG_VAR_REF(msg));
    return err;
}

void
lwip_netconn_do_bind(void *m)
{
    struct api_msg *msg = (struct api_msg *)m;
    
    switch (msg->conn->type)) {
        case NETCONN_UDP:
            err = udp_bind(msg->conn->pcb.udp, API_EXPR_REF(msg->msg.bc.ipaddr), msg->msg.bc.port);
            break;
        default:
            err = ERR_VAL;
            break;
    }
}

其中的

netconn_apimsg(lwip_netconn_do_bind, &API_MSG_VAR_REF(msg));

就相当于

lwip_netconn_do_bind(msg);

只不过添加了调用前申请锁,调用后释放锁,这么做是为了防止应用层线程调用这个函数修改协议栈信息时,跟协议栈线程访问冲突。
lwip_netconn_do_bind函数调用的udp_bind函数如下,主要就是绑定IP地址、端口号,将新的udp_pcb放到udp_pcbs链表中。
udp.c文件

err_t
udp_bind(struct udp_pcb *pcb, const ip_addr_t *ipaddr, u16_t port)
{
    ip_addr_set_ipaddr(&pcb->local_ip, ipaddr);
    pcb->local_port = port;
	//将新的pcb添加到udp_pcbs链表中
	pcb->next = udp_pcbs;
    udp_pcbs = pcb;
    
    return 0;
}

接收数据

接收数据使用lwip_recvfrom函数,其中判断到套接字协议类型为UDP时,会调用lwip_recvfrom_udp_raw做进一步处理。
lwip_recvfrom_udp_raw函数首先调用netconn_recv_data函数将接收到的UDP数据取出,然后调用pbuf_copy_partial函数将取出的数据放在msg->msg_iov[0].iov_base指向的内存,这块内存是应用层传入的。
socket.c文件

size_t lwip_recvfrom(int s, void *mem, size_t len, int flags,
              struct sockaddr *from, int *fromlen)
{
    struct lwip_sock *sock;
    sock = get_socket(s);

    if (sock->conn->type == NETCONN_TCP)
    {

    }
    else
    {
        err = lwip_recvfrom_udp_raw(sock, flags, &msg, &datagram_len, s);
    }
}

static err_t
lwip_recvfrom_udp_raw(struct lwip_sock *sock, int flags, struct msghdr *msg, u16_t *datagram_len, int dbg_s)
{
    struct netbuf *buf;
    netconn_recv_data(conn, &buf, apiflags);

    //将buf里的数据拷贝到msg->msg_iov[0].iov_base,这里iov_base就是应用层传入的存放数据的位置
    pbuf_copy_partial(buf->p, (u8_t *)msg->msg_iov[0].iov_base, copylen, copied);
    
}

lwip_recvfrom_udp_raw函数里取数据用到的netconn_recv_data函数如下,其中主要是从conn->recvmbox指向的消息队列中取出UDP数据。
api_lib.c文件

static err_t
netconn_recv_data(struct netconn *conn, void **new_buf, u8_t apiflags)
{
    sys_arch_mbox_fetch(&conn->recvmbox, &buf, 0);

    API_EVENT(conn, NETCONN_EVT_RCVMINUS, len); //这里再调用conn里的回调函数

    return ERR_OK;
}

发送数据

发送数据主要用到lwip_sendto函数,如下。
socket.c文件

ssize_t
lwip_sendto(int s, const void *data, size_t size, int flags,
            const struct sockaddr *to, socklen_t tolen)
{
    err = netconn_send(sock->conn, &buf);

    return (err == ERR_OK ? short_size : -1);
}

lwip_sendto函数里调用的netconn_send函数如下,与之前相同,其中的netconn_apimsg(lwip_netconn_do_send, &API_MSG_VAR_REF(msg))相当于
lwip_netconn_do_send(msg),这么封装是为了避免应用层线程与lwip内核线程访问冲突。lwip_netconn_do_send函数主要调用了udp_sendto函数。
api_lib.c文件

err_t
netconn_send(struct netconn *conn, struct netbuf *buf)
{
    err = netconn_apimsg(lwip_netconn_do_send, &API_MSG_VAR_REF(msg));

    return err;
}

void
lwip_netconn_do_send(void *m)
{
    err = udp_sendto(msg->conn->pcb.udp, msg->msg.b->p, &msg->msg.b->addr, msg->msg.b->port);
}

lwip_netconn_do_send函数调用的udp_sendto函数如下,其中一层一层的调用,最后调用的是ip4_output_if_src函数,将数据从UDP层传给IP层。
udp.c文件

err_t
udp_sendto(struct udp_pcb *pcb, struct pbuf *p,
           const ip_addr_t *dst_ip, u16_t dst_port)
{
    return udp_sendto_if(pcb, p, dst_ip, dst_port, netif); 
)

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)
{
    return udp_sendto_if_src(pcb, p, dst_ip, dst_port, netif, src_ip);
}

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 = ip4_output_if_src(q, src_ip, dst_ip, ttl, pcb->tos, ip_proto, netif);
    return err;
}

lwip内核接收UDP数据处理过程

lwip里有一个tcpip_thread线程,如下,他会一直检测tcpip_mbox消息队列中是否有消息,如果有,就说明网卡接收到了数据,则调用tcpip_thread_handle_msg函数做进一步处理,tcpip_thread_handle_msg函数会对消息做一个分类,UDP消息类型是TCPIP_MSG_INPKT,所以会执行msg->msg.inp.input_fn(msg->msg.inp.p, msg->msg.inp.netif),而msg->msg.inp.input_fn函数在创建TCPIP_MSG_INPKT类型消息时被赋值为ethernet_input函数。
tcpip.c文件

static void
tcpip_thread(void *arg)
{
    while (1)
    {
        TCPIP_MBOX_FETCH(&tcpip_mbox, (void **)&msg);
        if (msg == NULL) {
            continue;
        }

        tcpip_thread_handle_msg(msg);
    }
}

static void
tcpip_thread_handle_msg(struct tcpip_msg *msg)
{
    switch (msg->type) {
        case TCPIP_MSG_INPKT:
            msg->msg.inp.input_fn(msg->msg.inp.p, msg->msg.inp.netif);
            break;
        default:
            break;
    }
}

以下就是msg->msg.inp.input_fn函数的原型ethernet_input函数,这里会判断接收到数据的类型,UDP协议数据就会调用ip4_input函数做进一步处理。
ethernet.c文件

err_t
ethernet_input(struct pbuf *p, struct netif *netif)
{
  struct eth_hdr *ethhdr;
  u16_t type;
......
  //获取网卡接受数据的以太网首部
  ethhdr = (struct eth_hdr *)p->payload;
......
  //在以太网首部中获取本包数据的类型
  type = ethhdr->type;
......
  switch (type) {
    ......
    case PP_HTONS(ETHTYPE_IP):  //如果本包数据是IP数据报
    ......    
      ip4_input(p, netif);
    ...
      break;
    case PP_HTONS(ETHTYPE_ARP): //如果本包数据是ARP数据报
    ......
      etharp_input(p, netif);
    ...
      break;
    ......
    default:
    ...
    }

  return ERR_OK;
}

以下是ethernet_input函数处理UDP数据时调用的ip4_input函数,其中会调用udp_input函数做进一步处理。
ip4.c文件

err_t
ip4_input(struct pbuf *p, struct netif *inp)
{
    switch (IPH_PROTO(iphdr)) {
        case IP_PROTO_UDP:
            udp_input(p, inp);
            break;
        default:
            break;
    }
}

以下是ip4_input函数里调用的udp_input函数,其中调用了pcb->recv函数,从之前的分析可知,pcb->recv函数就是api_msg.c文件里的recv_udp函数,也就是将lwip内核接收到的UDP消息放到conn->recvmbox指向的消息队列中。
udp.c文件

void
udp_input(struct pbuf *p, struct netif *inp)
{
    struct udp_pcb *pcb
    for (pcb = udp_pcbs; pcb != NULL; pcb = pcb->next) {
        //在udp_pcbs链表里找出pcb
    }

    pcb->recv(pcb->recv_arg, pcb, p, ip_current_src_addr(), src);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值