打印
[牛人杂谈]

LWIP UDP 协议分析

[复制链接]
1259|9
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
zhuotuzi|  楼主 | 2016-11-21 18:14 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

一、udp.c实现的函数

1、void udp_input(struct pbuf *p, struct netif *inp)
说明:处理接收到的udp数据包。
参数:p数据包缓存区;inp网络接口。

2、err_t udp_send(struct udp_pcb *pcb, struct pbuf *p)
说明:发送udp包。这个函数直接调用udp_sendto()函数。
参数:pcb协议控制块;p数据包发送缓存区。
返回:ERR_OK发送成功;ERR_MEM发送溢出;ERR_RTE不能发送到指定ip;其它表示发送失败。

3、err_t udp_sendto(struct udp_pcb *pcb, struct pbuf *p,
                    struct ip_addr *dst_ip, u16_t dst_port)
说明:发送udp包到指定ip地址。
参数:pcb协议控制块;p数据包发送缓存区;dst_ip目的ip地址;dst_port目的端口号。

4、err_t udp_sendto_if(struct udp_pcb *pcb, struct pbuf *p,
                       struct ip_addr *dst_ip, u16_t dst_port, struct netif *netif)
说明:按照指定的网络接口和ip地址发送udp包。
参数:pcb协议控制块;p数据包发送缓存区;dest_ip目的ip地址;dst_port目的端口号,netif网络接口。

5、err_t udp_bind(struct udp_pcb *pcb, struct ip_addr *ipaddr, u16_t port)
说明:在协议控制块中绑定本地ip地址和本地端口号
参数:pcb协议控制块;ipaddr本地ip地址;port本地端口号。
返回:ERR_OK成功;ERR_USE已经被占用。

6、err_t udp_connect(struct udp_pcb *pcb, struct ip_addr *ipaddr, u16_t port)
说明:与远端udp主机建立连接。
参数:pcb所需连接的协议控制块;ipaddr远端ip地址;port远端端口号。

7、void udp_disconnect(struct udp_pcb *pcb)
说明:断开指定连接。
参数:pcb所需断开连接的协议控制块。

8、void udp_recv(struct udp_pcb *pcb,
                 void (* recv)(void *arg, struct udp_pcb *upcb, struct pbuf *p,
                               struct ip_addr *addr, u16_t port),
                 void *rev_arg)
说明:设置接收到数据包时调用的回调函数及其参数。
参数:pcb协议控制块;recv回调函数名(地址);rev_arg回调函数参数。
这个函数直接修改pcb->recv和pcb->recv_arg的值。

9、void udp_remove(struct udp_pcb *pcb)
说明:删除指定udp协议控制块,从协议控制链表中删除并释放内存资源。
参数:pcb所要删除的协议控制块。

10、struct udp_pcb * udp_new(void)
说明:创建udp协议控制块,并不分配资源。
返回:协议控制块指针,指向NULL。

沙发
zhuotuzi|  楼主 | 2016-11-21 18:16 | 只看该作者
- UDP functions

err_t udp_bind(struct udp_pcb *pcb, struct ip_addr *ipaddr, u16_t port)
函数遍历整个UDP PCB链表,以排除在没有设置REUSE_ADDR或者REUSE_PORT标志的情况下绑定到一个以相同port绑定的pcb或者以相同port及ip绑定的pcb。如果需要绑定的port无效,则分配最小可用port。如果该pcb未在原来PCB链表中,则加入链表。具体流程参看流程图。
err_t udp_connect(struct udp_pcb *pcb, struct ip_addr *ipaddr, u16_t port)
连接到远程端口。如果还未分配本地port,则分配一个空闲port。然后将一下两种地址绑定类型进行转换:
a. *.local_port  foreign_ip.foreign_port: 调用ip_router确定本地ip。
b. *.*  *.foreign_port: 转换为 *.local_port  *.foreign_port

err_t udp_sendto(struct udp_pcb *pcb, struct pbuf *p,
struct ip_addr *dst_ip, u16_t dst_port)

该函数借用当前的pcb调用udp_send发送UDP包,完成后,回复原来pcb内容。

err_t udp_send(struct udp_pcb *pcb, struct pbuf *p)
如果pcb未绑定,则调用udp_bind获取一个可用的port绑定之。然后构造UDP包,查找能够到达remote_ip的router接口,如果有必要,将该接口的本地ip作为UDP的src ip。如果UDP需要校验和,则调用inet_chksum_pseudo函数,计算校验和。最后调用ip_output_if将UDP包传送到下层IP层发送。

使用特权

评论回复
板凳
zhuotuzi|  楼主 | 2016-11-21 18:17 | 只看该作者
void udp_input(struct pbuf *p, struct netif *inp)
该函数接受来自ip层的UDP包。将所有PCB都遍历,如果有多个绑定,则给每一个进程复制一份数据报,实际调用pcb->recv()。
其中的数据报的地址绑定匹配优先级和协议上的略有区别:
    Local                            Foreign
local_ip(*).local_port  foreign_ip(*).foreign_port
local_ip(*).local_port  *.*

使用特权

评论回复
地板
643757107| | 2016-11-21 23:29 | 只看该作者
struct udp_pcb {
/* Common members of all PCB types */
  IP_PCB;
/* Protocol specific PCB members */
  struct udp_pcb *next;
  u8_t flags;
  u16_t local_port, remote_port;
  
  u16_t chksum_len;
  
  void (* recv)(void *arg, struct udp_pcb *pcb, struct pbuf *p,
    struct ip_addr *addr, u16_t port);
  void *recv_arg;  
};


ip_input()--->
udp_input()--->
pcb->recv(pcb->recv_arg, pcb, p, &(iphdr->src), src);   //回调

void
udp_recv(struct udp_pcb *pcb,
   void (* recv)(void *arg, struct udp_pcb *upcb, struct pbuf *p,
           struct ip_addr *addr, u16_t port),
   void *recv_arg)
{
  /* remember recv() callback and user data */
  pcb->recv = recv;
  pcb->recv_arg = recv_arg;
}

UDP例子(9000端口----9000端口)

void test_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *addr, u16_t port)
{
   struct pbuf a;
   a = *p;
   pcb = pcb;
   udp_sendto(pcb,&a,addr,port);
   pbuf_free(p);
}

struct udp_pcb *test_udp;

test_udp = udp_new();
udp_bind(test_udp,&ipaddr,9000);
udp_connect(test_udp,IP_ADDR_ANY,9000);
udp_recv(test_udp, test_recv, ðif);

u16_t pbuf_copy_partial(struct pbuf *buf, void *dataptr, u16_t len, u16_t offset)  ---重要函数可用于将UDP接收的数据取出放在自己的定义的数组中
  将pbuf中偏移offset开始的len长度内容拷贝到dataptr指向的存储区中

/* ---------- Pbuf options ---------- */
/* PBUF_POOL_SIZE: the number of buffers in the pbuf pool. */
#define PBUF_POOL_SIZE          30
/* PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. */
#define PBUF_POOL_BUFSIZE       500

例如一个UDP接收默认采用PBUF_POOL固定容量,每个链表长度500字节,链表长度30个






关于LWIP---UDP
lwip是一个轻量级的TCP/IP协议栈(Lightweight TCP/IP Stack)实现,最初是瑞士计算机科学学院Adam Dunkels编写的一个应用于无操作系统嵌入式系统中的TCP/IP协议栈,后来作为一个开源(open source)项目,由一个全球性的团队进行开发和维护。
已实现的部分有:
1. 标准的TCP/IP协议栈实现,包括TCP、UDP、ICMP、IP、ARP、DHCP;
ICMP(Internet control message protocol):网络维护和调试。
UDP(User datagram protocol)
DHCP(Dynamic host configuration protocol)
ARP(Address resolution protocol)

2.非标准Socket接口,lwip提供了一套Socket API,这套API的标准与正常操作系统下的Socket API的形式不是很一致,我们先前已经在这套API上实现了Web Server,已测试在没有Mobile IP环境下工作正常。
下面我们就一个lwip典型的UDP协议工作过程作为对lwip的简单介绍。

UDP发送过程:
1.应用层:绑定UDP套接字
我们必须先创建一个UDP套接字,通过调用udp_new()进行申请,然后调用udp_bind()绑定在UDP端口上,在这个调用过程中,我们必须编写一个用于处理这个UDP套接字接收到的数据报文的函数,并把这个函数作为udp_bind()的参数,以后当套接字接收到数据报文时会自动调用这个函数,我们将在后面介绍这个函数怎么调用的。绑定结束之后,必须调用udp_connect()将数据报文的目的地址绑定在UDP的数据结构中,最后就是调用udp_send()把数据报文发送出去。
udp_bind()的处理流程图

2.传输层的处理
做好应用层的处理之后,数据报文被提交到UDP层,udp_send()函数中首先给数据报文加入UDP头部,然后调用ip_route()选择一个合适的网络接口进行发送,最后调用ip_output()把数据报文传入IP层。
3.IP层的处理
ip_route()函数比较各个网络接口的IP地址是否与目的IP地址在同一子网中,如果有,就把它当成发送的网络接口返回,如果没有就返回一个默认的网络接口。
在ip_output()函数中,先给数据报文加上IP头部,然后比较目的IP地址与网络接口的IP地址是否在同一网段,如果不是,就必须先把数据报文发送到网关,于是使用网关的IP地址作为目的主机,如果目的IP地址与网络接口的IP地址在同一网段,则把目的IP地址作为目的主机。接着调用arp_lookup()在ARP缓存中查找目的主机的MAC地址,找到了调用ethernet_output()把数据报文传入到数据链路层发送,如果找不到,就调用arp_query()发送ARP请求解析目的主机的MAC地址。
4.ARP协议的处理
arp_lookup()实现在本地ARP缓存中查找目的主机的MAC地址,找到了返回该MAC地址,找不到返回NULL。
arp_query()函数中构造一个ARP请求报文,然后调用ethernet_output()把该报文送到数据链路层发送。
5. 数据链路层的处理
数据链路层的处理就是给数据报文添上相对的以太网头部,然后调用lowlever_output()直接把报文传送出去。


UDP接收过程:
接收过程与发送过程刚好相反,数据报文首先调用ethernet_input()函数到达数据链路层,去掉以太网头部之后如果是ARP报文传给调用arp_input()交给ARP协议处理,如果是IP报文就调用ip_input()进入IP层处理,ip_input()函数中比较数据报文的目的IP地址,如果与某个网络接口的IP地址相同,则接收这个报文,依照IP头部的协议字段,调用各自协议的输入处理函数,本例中将调用udp_input(),在udp_input()中提取数据报文的端口号,然后在已登记的套接字中查找与该端口号符合的UDP接收函数,如果没有找到相应的套接字,调用icmp_output()发送一个ICMP不可达报文,如果找到了,就调用该函数(这个函数就是我们在udp_bind()时传入的其中一个参数)。
udp_input处理流程图:



使用特权

评论回复
5
zhuotuzi|  楼主 | 2016-12-4 14:00 | 只看该作者
下载开发包的时候,里面貌似就提供这个第三方的东西了。

使用特权

评论回复
6
huangcunxiake| | 2016-12-4 15:27 | 只看该作者
LWIP用的越来越多了,好好学习下怎么玩。

使用特权

评论回复
7
玛尼玛尼哄| | 2016-12-5 16:18 | 只看该作者
用这些底层的东西可以获取更加灵活的应用体验。

使用特权

评论回复
8
dongnanxibei| | 2016-12-6 18:44 | 只看该作者
最近正在学习这个东西,来看看这个资料。

使用特权

评论回复
9
heisexingqisi| | 2016-12-6 18:46 | 只看该作者
这个协议很适合那些有线网络的通信使用。

使用特权

评论回复
10
zhuotuzi|  楼主 | 2016-12-8 19:10 | 只看该作者
如果你不懂怎么联网的技术,那么将会被这个时代淘汰掉。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

201

主题

3316

帖子

7

粉丝