IP协议提供了在各个主机之间传送数据报的功能,但是数据的最终目的地是主机上的特定应用程序。传输层协议就承担了这样的责任,典型的传输层协议有UDP和TCP两种。许多著名的上层应用都是基于UDP实现的,比如DNS、DHCP、IGMP、SNMP等。
UDP协议称为用户数据包协议,是一种无连接、不可靠的传输协议。UDP协议只是简单地完成应用程序到应用程序的交互,并不提供流量控制机制、复杂的差错控制方法、确认机制。虽然UDP可靠性非常差,但是UDP适用于那些轻微数据差错不敏感的应用,比如视频传输、网络电话等。
每台主机都包含称为协议端口的抽象目的点,端口号范围为0~65535,进程绑定到端口号上。这样数据包只要递交到相应的端口即可。
报文格式
伪首部:真实传输的UDP数据包中没有该字段,只在计算校验和时虚拟出该字段。
struct udp_hdr {
PACK_STRUCT_FIELD(u16_t src); //源端口号
PACK_STRUCT_FIELD(u16_t dest); //目的端口号
PACK_STRUCT_FIELD(u16_t len); //总长度(UDP首部+UDP数据)
PACK_STRUCT_FIELD(u16_t chksum); //校验和(UDP伪首部+UDP首部+UDP数据)(0:不计算校验和 0xFFFF:检验和为0)
} PACK_STRUCT_STRUCT;
先看一下UDP控制块
/* 所有控制块共有部分 */
#define IP_PCB \
struct ip_addr local_ip; \ //本机IP
struct ip_addr remote_ip; \ //远端IP
u16_t so_options; \ //套接字选项
u8_t tos; \ //服务类型
u8_t ttl \ //生存时间
IP_PCB_ADDRHINT
/* 不进行校验 */
#define UDP_FLAGS_NOCHKSUM 0x01U
/* UDP-LITE协议 */
#define UDP_FLAGS_UDPLITE 0x02U
/* 已绑定远程端口 */
#define UDP_FLAGS_CONNECTED 0x04U
/* UDP控制块 */
struct udp_pcb {
IP_PCB; //IP控制块相关字段
struct udp_pcb *next; //用于将UDP控制块连接成链表
u8_t flags; //控制块状态
u16_t local_port, remote_port; //本地端口、远程端口
/* 接收回调函数 */
void (* recv)(void *arg, struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *addr, u16_t port);
/* 用户提供的接收回调函数参数 */
void *recv_arg;
};
/* UDP控制块链表 */
extern struct udp_pcb *udp_pcbs;
UDP控制块最终被组织成一张表
下面分析UDP创建和删除的一些API
/* 创建一个UDP控制块 */
struct udp_pcb *udp_new(void)
{
struct udp_pcb *pcb;
/* 为UDP控制块申请内存空间 */
pcb = memp_malloc(MEMP_UDP_PCB);
if (pcb != NULL) {
/* 清空UDP控制块 */
memset(pcb, 0, sizeof(struct udp_pcb));
/* 设置UDP控制块的TTL值 */
pcb->ttl = UDP_TTL;
}
return pcb;
}
/* 绑定本地端口,0表示自动分配端口号 */
err_t udp_bind(struct udp_pcb *pcb, struct ip_addr *ipaddr, u16_t port)
{
struct udp_pcb *ipcb;
u8_t rebind;
rebind = 0;
/* 遍历UDP控制块链表 */
for (ipcb = udp_pcbs; ipcb != NULL; ipcb = ipcb->next) {
/* 该控制块已经绑定本地端口 */
if (pcb == ipcb) {
rebind = 1;
}
}
/* 设置UDP控制块的本地IP地址 */
ip_addr_set(&pcb->local_ip, ipaddr);
/* 自动分配端口号 */
if (port == 0) {
#define UDP_LOCAL_PORT_RANGE_START 4096
#define UDP_LOCAL_PORT_RANGE_END 0x7fff
/* 遍历整个UDP控制块链表,查找最小的未使用端口号 */
port = UDP_LOCAL_PORT_RANGE_START;
ipcb = udp_pcbs;
while ((ipcb != NULL) && (port != UDP_LOCAL_PORT_RANGE_END)) {
if (ipcb->local_port == port) {
port++;
ipcb = udp_pcbs;
} else
ipcb = ipcb->next;
}
/* 没有可用端口号 */
if (ipcb != NULL) {
return ERR_USE;
}
}
/* 设置本地端口号 */
pcb->local_port = port;
/* 将UDP控制块插入链表 */
if (rebind == 0) {
pcb->next = udp_pcbs;
udp_pcbs = pcb;
}
return ERR_OK;
}
/* 绑定远程端口 */
err_t udp_connect(struct udp_pcb *pcb, struct ip_addr *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;
/* 将UDP控制块状态设置为已绑定远程端口 */
pcb->flags |= UDP_FLAGS_CONNECTED;
/* UDP控制块如果没有插入链表,则将其插入链表 */
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;
}
/* 取消绑定远程端口 */
void udp_disconnect(struct udp_pcb *pcb)
{
/* 重置UDP控制块远程IP和端口号 */
ip_addr_set(&pcb->remote_ip, IP_ADDR_ANY);
pcb->remote_port = 0;
/* 将UDP控制块状态设置为未绑定远程端口 */
pcb->flags &= ~UDP_FLAGS_CONNECTED;
}
/* 设置UDP控制块回调函数,和回调函数用户参数 */
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)
{
pcb->recv = recv;
pcb->recv_arg = recv_arg;
}
/* 删除UDP控制块 */
void udp_remove(struct udp_pcb *pcb)
{
struct udp_pcb *pcb2;
/* 将UDP控制块从链表中移除 */
if (udp_pcbs == pcb) {
udp_pcbs = udp_pcbs->next;
} else
for (pcb2 = udp_pcbs; pcb2 != NULL; pcb2 = pcb2->next) {
if (pcb2->next != NULL && pcb2->next == pcb) {
pcb2->next = pcb->next;
}
}
/* 释放UDP控制块内存空间 */
memp_free(MEMP_UDP_PCB, pcb);
}
接下来看一下UDP发送API
/* 发送UDP数据包(已经绑定远程主机端口) */
err_t udp_send(struct udp_pcb *pcb, struct pbuf *p)
{
return udp_sendto(pcb, p, &pcb->remote_ip, pcb->remote_port);
}
/* 指定远程端口,发送UDP数据包 */
err_t udp_sendto(struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *dst_ip, u16_t dst_port)
{
struct netif *netif;
/* 根据IP地址选择一个合适(和目的主机处于同一子网)的网络接口 */
netif = ip_route(dst_ip);
if (netif == NULL) {
return ERR_RTE;
}
/* 指定网络接口发送UDP数据包 */
return udp_sendto_if(pcb, p, dst_ip, dst_port, netif);
}
/* 指定网络接口和远程端口,发送UDP数据包 */
err_t udp_sendto_if(struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *dst_ip, u16_t dst_port, struct netif *netif)
{
struct udp_hdr *udphdr;
struct ip_addr *src_ip;
err_t err;
struct pbuf *q;
/* 没有绑定本地端口,先绑定本地端口 */
if (pcb->local_port == 0) {
err = udp_bind(pcb, &pcb->local_ip, pcb->local_port);
if (err != ERR_OK) {
return err;
}
}
/* 向前调整出UDP头部空间,调整失败 */
if (pbuf_header(p, UDP_HLEN)) {
/* 申请UDP头部空间 */
q = pbuf_alloc(PBUF_IP, UDP_HLEN, PBUF_RAM);
if (q == NULL) {
return ERR_MEM;
}
/* 将两个pbuf串起来 */
pbuf_chain(q, p);
}
/* 调整成功 */
else {
/* 直接获取pbuf指针 */
q = p;
}
/* UDP头部指针 */
udphdr = q->payload;
/* 设置UDP头部源端口号 */
udphdr->src = htons(pcb->local_port);
/* 设置UDP头部目的端口号 */
udphdr->dest = htons(dst_port);
/* 0表示不校验 */
udphdr->chksum = 0x0000;
/* UDP本地IP为0,表示自动选择合适的网络接口发送 */
if (ip_addr_isany(&pcb->local_ip)) {
/* 设置源IP地址 */
src_ip = &(netif->ip_addr);
}
else {
/* 本地IP地址和合适的网络接口IP地址不相同 */
if (!ip_addr_cmp(&(pcb->local_ip), &(netif->ip_addr))) {
/* 释放数据包,返回错误 */
if (q != p) {
pbuf_free(q);
q = NULL;
}
return ERR_VAL;
}
/* 本地IP地址和合适的网络接口IP地址相同 */
/* 设置源IP地址 */
src_ip = &(pcb->local_ip);
}
{
/* 设置UDP头部中的总长度字段 */
udphdr->len = htons(q->tot_len);
/* 必须进行和校验 */
if ((pcb->flags & UDP_FLAGS_NOCHKSUM) == 0) {
/* 计算并设置UDP和校验 */
udphdr->chksum = inet_chksum_pseudo(q, src_ip, dst_ip, IP_PROTO_UDP, q->tot_len);
if (udphdr->chksum == 0x0000) udphdr->chksum = 0xffff;
}
/* 将UDP数据包通过IP输出函数发送出去 */
err = ip_output_if(q, src_ip, dst_ip, pcb->ttl, pcb->tos, IP_PROTO_UDP, netif);
}
/* 释放数据包空间 */
if (q != p) {
pbuf_free(q);
q = NULL;
}
return err;
}
最后看一下接口函数
/* UDP数据包接收处理函数 */
void udp_input(struct pbuf *p, struct netif *inp)
{
struct udp_hdr *udphdr;
struct udp_pcb *pcb, *prev;
struct udp_pcb *uncon_pcb;
struct ip_hdr *iphdr;
u16_t src, dest;
u8_t local_match;
u8_t broadcast;
/* IP头部 */
iphdr = p->payload;
/* 检验UDP长度是否合理,pbuf指针向后调整剥离IP头部 */
if (p->tot_len < (IPH_HL(iphdr) * 4 + UDP_HLEN) || pbuf_header(p, -(s16_t)(IPH_HL(iphdr) * 4))) {
pbuf_free(p);
goto end;
}
/* UDP头部指针 */
udphdr = (struct udp_hdr *)p->payload;
/* 判断IP地址是不是广播地址 */
broadcast = ip_addr_isbroadcast(&(iphdr->dest), inp);
/* 取出源IP和目的IP */
src = ntohs(udphdr->src);
dest = ntohs(udphdr->dest);
{
prev = NULL;
local_match = 0;
uncon_pcb = NULL;
/* 遍历UDP控制块链表 */
for (pcb = udp_pcbs; pcb != NULL; pcb = pcb->next) {
local_match = 0;
/* 判断该UDP数据包是不是发给该UDP控制块(广播、IP和端口号相同) */
if ((pcb->local_port == dest) && ((!broadcast && ip_addr_isany(&pcb->local_ip)) || ip_addr_cmp(&(pcb->local_ip), &(iphdr->dest)) || (broadcast))) {
/* 匹配成功 */
local_match = 1;
/* 记录第一个匹配到的没有绑定远程端口的控制块 */
if ((uncon_pcb == NULL) && ((pcb->flags & UDP_FLAGS_CONNECTED) == 0)) {
uncon_pcb = pcb;
}
}
/* 判断该数据包是不是绑定的远程端口相同 */
if ((local_match != 0) && (pcb->remote_port == src) && (ip_addr_isany(&pcb->remote_ip) || ip_addr_cmp(&(pcb->remote_ip), &(iphdr->src)))) {
/* 将控制块移到链表首部,增加效率 */
if (prev != NULL) {
prev->next = pcb->next;
pcb->next = udp_pcbs;
udp_pcbs = pcb;
}
break;
}
prev = pcb;
}
/* 没有找到完全匹配的,就用没有绑定远程端口的控制块 */
if (pcb == NULL) {
pcb = uncon_pcb;
}
}
/* 匹配到合适的控制块 */
if (pcb != NULL || ip_addr_cmp(&inp->ip_addr, &iphdr->dest)) {
{
/* 需要检查校验和 */
if (udphdr->chksum != 0) {
/* 进行检验和检查(UDP伪首部+UDP首部+UDP数据) */
if (inet_chksum_pseudo(p, (struct ip_addr *)&(iphdr->src), (struct ip_addr *)&(iphdr->dest), IP_PROTO_UDP, p->tot_len) != 0) {
pbuf_free(p);
goto end;
}
}
}
/* 向后调整剥离UDP首部 */
if(pbuf_header(p, -UDP_HLEN)) {
pbuf_free(p);
goto end;
}
/* 已经匹配到UDP控制块 */
if (pcb != NULL) {
/* 调用UDP接收回调函数 */
if (pcb->recv != NULL) {
pcb->recv(pcb->recv_arg, pcb, p, &iphdr->src, src);
}
else {
pbuf_free(p);
goto end;
}
}
/* 没有匹配到UDP控制块 */
else {
/* 该UDP数据包不是广播也是不组播 */
if (!broadcast && !ip_addr_ismulticast(&iphdr->dest)) {
/* 发送ICMP目的不可达报文(端口不可达) */
pbuf_header(p, (IPH_HL(iphdr) * 4) + UDP_HLEN);
icmp_dest_unreach(p, ICMP_DUR_PORT);
}
pbuf_free(p);
}
} else {
pbuf_free(p);
}
end:
return;
}
UDP客户端步骤
1.udp_new,创建UDP控制块
2.udp_bind,绑定本地端口(该步骤可省略,省略时自动分配本地端口号)
3.udp_recv,注册接收回调函数
4.udp_sendto,指定远程端口,发送数据包
或
1.udp_new,创建UDP控制块
2.udp_bind,绑定本地端口(该步骤可省略,省略时自动分配本地端口号)
3.udp_connect,绑定远程端口
4.udp_recv,注册接收回调函数
5.udp_send,发送数据包
UDP服务器步骤
1.udp_new,创建UDP控制块
2.udp_bind,绑定本地端口
3.udp_recv,注册接收回调函数
4.udp_sendto,指定远程端口,发送数据包