一、传输层简介
前面介绍了通过IP地址实现网际寻址,通过MAC地址实现链路内寻址,两个地址一起使用可实现两台主机间的寻址与通信。但我们在一台电脑上常用的网络服务不止一个,怎么区分某主机上究竟是哪个进程在请求服务或提供服务呢?
1.1 端口号
TCP/IP协议栈为了区分一台计算机上运行的多个程序,引入了端口号的概念,由于端口号是用来识别同一台计算机中进行通信的不同应用程序,它也被称为程序地址。
再回想下TCP/IP协议栈的分层模型,数据链路层有MAC地址用来识别同一链路中不同的计算机,网络层有IP地址用来识别网络中互连的主机或路由器,这里引入的程序地址(即端口号Port)是在传输层用来识别本机中正在进行通信的应用程序,并准确的进行数据传输。例如提供www服务的HTTP程序端口号为80,提供文件传输服务的FTP程序端口号为21等,根据MAC地址+IP地址+HTTP端口号进行通信的图示如下:
由于网卡上MAC地址与IP地址是绑定关系,因此实际上两个应用进行通信时,只需要确定通信双方的IP地址与端口号就行了,但由于传输层协议有TCP/UDP两种,要想唯一识别一个通信还需要确定协议号。因此,TCP/IP或UDP/IP通信中通常采用5个信息来识别一个通信,它们是“源IP地址”、“目标IP地址”、“协议号”、“源端口号”、“目标端口号”,只要其中某一项不同就会被认为是不同的通信。
在实际通信中,要事先确定端口号,确定端口号的方法可分为两种:
- 标准既定端口号:也称为静态端口号或熟知端口号,指每个应用程序都有特定的端口号,每个端口号都有其对应的使用目的。例如HTTP、TELNET、FTP等广为使用的应用协议中所使用的端口号就是固定的,这类知名端口号一般由0到1023的数字分配而成(1024到49151的数字也被正式注册为端口号了,这些端口号可以用于任何通信用途),如下表列举了部分常用的TCP/UDP端口号对应的应用协议;
- 时序分配法:也称为动态分配法,动态分配的端口号可称为短暂端口号,此时服务端有必要确定监听端口号,但接受服务的客户端没必要确定端口号,而全权交给操作系统分配一个端口号。操作系统可以为每个应用程序分配互不冲突的端口号,这样操作系统就可以动态的管理端口号了。根据这种动态分配端口号的机制,即使是同一个客户端程序发起的多个TCP连接,识别这些通信连接的5部分数字也不会全部相同。动态分配的端口号的取值范围在49152到65535之间。
从上表可以看出,不同的通信协议可以使用相同的端口号,例如TCP与UDP使用同一个端口号,但使用目的各不相同。数据到达IP层后,会检查IP首部中的协议号,再传给相应的协议,即便是同一个端口号,由于传输协议是各自独立进行处理的,因此相互之间不会受到影响。
1.2 传输层的作用
前篇提到,IP首部中有一个协议字段用来标识网络层的上一层所采用的是哪一种传输层协议,根据这个字段的协议号就可以识别IP传输的数据部分究竟是哪一种传输层协议。
在TCP/IP协议栈中能够实现传输层功能的、具有代表性的协议是TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)。两者的区别主要如下:
- TCP协议:面向连接的、可靠的流(不间断的数据结构)协议。TCP为提供可靠性传输,实行“顺序控制”或“重发控制”机制,此外还具备“流量控制”、“拥塞控制”、提高网络利用率等众多功能;
- UDP协议:不具有可靠性的数据报协议,细微的处理会交给上层的应用去完成。UDP协议虽然可以确保发送消息的大小,却不能保证消息一定会到达,因此应用有时会根据自己的需要进行重发处理。
虽然TCP是可靠性的传输协议,能为应用提供可靠的传输服务,但并不一定就优于UDP协议。可靠传输的保障是以牺牲传输效率为代价的,UDP虽然不提供可靠的传输服务,但可实现较高的传输效率,主要用于那些对高速传输和实时性有较高要求的通信或广播通信中。比如使用TCP进行实时通话,可能会因数据包丢失重传而影响流畅交流,采用UDP则不会进行重传处理,即使数据包丢失也只会影响一小部分通话。此外,在多播与广播通信中也常使用UDP协议而非TCP协议,比如RIP(Routing Information Protocol)与DHCP(Dynamic Host Configuration Protocol)等基于广播的协议都依赖于UDP协议。因此,TCP与UDP协议应该根据应用的目的按需使用。
二、UDP协议原理与实现
UDP协议不提供复杂的控制机制,利用IP提供面向无连接的通信服务,并且它是将应用程序发来的数据在收到的那一刻立即按照原样发送到网络上的一种机制。即使是出现网络拥堵的情况下,UDP也无法进行流量控制等避免网络拥塞的行为,传输途中即使出现丢包UDP也不负责重发。甚至当出现包的到达顺序乱掉时也没有纠正的功能。如果需要这些细节控制,那么不得不交由采用UDP的应用程序去处理。
由于UDP面向无连接,它可以随时发送数据,再加上UDP本身的处理既简单又高效,因此常用于以下几个方面:
- 包总量较少的通信(DNS、SNMP等)
- 视频、音频等多媒体通信(即时通信)
- 限定于LAN等特定网络中的应用通信
- 广播、多播通信(RIP、DHCP等)
2.1 UDP报文格式
用户进程使用UDP来传送数据时,UDP协议会在数据前加上首部组成UDP报文,并交给IP协议来发送,而IP层将报文封装在IP数据报中并交给底层发送,在底层IP数据报会被封装在物理数据帧中。因此,一份用户数据在被发送时,经历了三次封装过程,如下图所示:
在接收端,物理网络先接收到数据帧,然后逐层将数据递交给上层协议,每一层都在向上层递交前去除掉一个首部。在UDP层,它将从IP层得到UDP报文,UDP协议会根据该报文首部中的目的端口字段将报文递交给用户进程,绑定到这个目的端口的进程将得到报文中的数据。
UDP报文称为用户数据报,同前面讲的其他协议相同,用户数据报的结构也可以分为两部分:UDP首部和UDP数据区,报文结构如下图所示:
UDP首部比较简单,它由四个16位字段组成,分别指出了该用户数据报从哪个端口来、要到哪个端口去、包含首部与数据区的总长度和校验和。源端口号与目的端口号都是16位的,这样端口号的取值范围在0到65535之间,源端口号是主机上发送该用户数据报的进程所绑定的本地端口号,而目的端口号是该报文要送达的目的主机上应用进程所绑定的端口号。在用户数据报的发起端通常会将目的端口号填写为服务器上某个熟知的端口(标准既定端口号),对源端口号字段的填写是可选的,如果客户端期望服务器为自己返回数据,则必须填写源端口号字段,服务器会在收到的报文中提取到这个源端口号,并在返回数据时使用到。客户端进程也可以不填写源端口号字段,此时该字段置0,但若选用,源端口号字段往往是一个随机分配的短暂端口号(时序分配法)。
UDP首部中的校验和字段是可选的,如果不使用校验和可以直接将该字段填入0,在某些高可靠性的局域网中使用UDP时减少校验和的计算可以增加UDP的处理速度。如果使用校验和,则校验和的计算超出了UDP报文本身,为了计算校验和,UDP引入了伪首部的概念,伪首部的组成结构如下图所示:
这里的伪首部完全是虚拟的,并不会和用户数据报一起被发送出去,只是在校验和的计算过程中被用到而已。因此,UDP校验和的计算覆盖了三部分:UDP伪首部、UDP首部和UDP数据区,算法同前面IP首部校验和相同,即16位的二进制反码求和。伪首部主要来自于运载UDP报文的IP数据报首部,将源IP地址和目的IP地址加入到校验和的计算中可以验证用户数据报是否已经到达正确的终点。前面介绍过,确定一个唯一的连接需要5个字段信息,所以伪首部中还应包含协议字段用于说明这个报文是属于UDP而不是TCP的。伪首部中最后一个总长度字段同UDP首部中的总长度字段值相同。
在UDP协议基础上发展出了一个名为UDP-Lite的协议,该协议在IP数据报中的标识为136,它同UDP用户数据报很相似,但可以采用更为灵活的校验方式。传统UDP协议对整个报文进行完成的校验,若某一位发生变化,需要丢掉整个报文的代价比较大。在UDP-Lite协议中,一个报文到底需不需要使用校验和,或者校验和的覆盖范围都是用户可控的,同UDP采用相似的数据报组织结构,但它用总长度字段指出校验和的覆盖长度,这使得它更适应于网络传输差错率小,对轻微差错不敏感的应用中,比如实时视频播放、实时通话等。
2.2 UDP数据报描述
UDP数据报首部的结构比较简单,在LwIP中用于描述UDP首部的数据结构如下:
// rt-thread\components\net\lwip-1.4.1\src\include\lwip\udp.h
#define UDP_HLEN 8
/* Fields are (of course) in network byte order. */
PACK_STRUCT_BEGIN
struct udp_hdr {
PACK_STRUCT_FIELD(u16_t src);
PACK_STRUCT_FIELD(u16_t dest); /* src/dest UDP ports */
PACK_STRUCT_FIELD(u16_t len);
PACK_STRUCT_FIELD(u16_t chksum);
} PACK_STRUCT_STRUCT;
PACK_STRUCT_END
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
这个结构很简洁,除了使用结构体封装宏定义每个字段外,还应该注意四个字段中保存的值都应该与网络字节序保持一致,即将某主机上的数据填写到这些字段时,要经过小端到大端的变换。
传输层的一个重要作用就是对通信双方的管理,与任务控制块管理任务运行状态、事件控制块管理任务间通信类似,传输层也引入了控制块用来管理通信双方的连接,实际上之前IP层也引入了一个raw_pcb原始协议控制块用来管理通信双方在IP层的连接。
在UDP协议中UDP控制块是整个UDP协议实现中最为核心的东西,LwIP使用UDP控制块来描述一个UDP连接的所有信息,包括源端口号、目的端口号、源IP地址、目的IP地址、协议类型等。用户使用UDP进行编程以及内核对UDP报文的处理,本质上都是对UDP控制块的操作,理解到这个本质很关键。在LwIP中用于描述UDP控制块的数据结构如下:
// rt-thread\components\net\lwip-1.4.1\src\include\lwip\udp.h
struct udp_pcb {
/* Common members of all PCB types */
IP_PCB;
/* Protocol specific PCB members */
struct udp_pcb *next;
u8_t flags;
/** ports are in host byte order */
u16_t local_port, remote_port;
#if LWIP_IGMP
/** outgoing network interface for multicast packets */
ip_addr_t multicast_ip;
#endif /* LWIP_IGMP */
#if LWIP_UDPLITE
/** used for UDP_LITE only */
u16_t chksum_len_rx, chksum_len_tx;
#endif /* LWIP_UDPLITE */
/** receive callback function */
udp_recv_fn recv;
/** user-supplied argument for the recv callback */
void *recv_arg;
};
/* This is the common part of all PCB types. It needs to be at the
beginning of a PCB type definition. It is located here so that
changes to this common part are made in one location instead of
having to change all PCB structs. */
#define IP_PCB \
/* ip addresses in network byte order */ \
ip_addr_t local_ip; \
ip_addr_t remote_ip; \
/* Socket options */ \
u8_t so_options; \
/* Type Of Service */ \
u8_t tos; \
/* Time To Live */ \
u8_t ttl \
/* link layer address resolution hint */ \
IP_PCB_ADDRHINT
struct ip_pcb {
/* Common members of all PCB types */
IP_PCB;
};
#define UDP_FLAGS_NOCHKSUM 0x01U
#define UDP_FLAGS_UDPLITE 0x02U
#define UDP_FLAGS_CONNECTED 0x04U
#define UDP_FLAGS_MULTICAST_LOOP 0x08U
/** Function prototype for udp pcb receive callback functions
* addr and port are in same byte order as in the pcb
* The callback is responsible for freeing the pbuf
* if it's not used any more.
* ATTENTION: Be aware that 'addr' points into the pbuf 'p' so freeing this pbuf
* makes 'addr' invalid, too.
* @param arg user supplied argument (udp_pcb.recv_arg)
* @param pcb the udp_pcb which received data
* @param p the packet buffer that was received
* @param addr the remote IP address from which the packet was received
* @param port the remote port from which the packet was received
*/
typedef void (*udp_recv_fn)(void *arg, struct udp_pcb *pcb, struct pbuf *p,
ip_addr_t *addr, u16_t port);
/* udp_pcbs export for exernal reference (e.g. SNMP agent) */
extern struct udp_pcb *udp_pcbs;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
系统为每一个连接分配一个UDP控制块,并把它们组织在一个全局链表udp_pcbs上,当UDP层收到IP层递交的报文时,会去遍历这个链表,找出与报文首部信息匹配的控制块,并调用控制块中注册的函数最终完成报文的处理。UDP控制块链表的组织形式如下图所示:
宏IP_PCB也可以称为IP控制块,在前面介绍raw_pcb时已经介绍过;本地端口号与远程端口号都是16位使用主机字节序;回调函数指针的定义也在上面给出了,回调函数及回调参数是用户程序与协议栈内核进行通信的纽带;flags控制块状态字段由四个宏定义标识该UDP控制块的四种状态,分别为是否进行校验和计算、是否使用UDP-Lite、是否处于连接状态、是否转发组播包等。
2.3 UDP数据报的操作
UDP报文的发送依靠与接收依靠IP层提供的服务,用户程序可以按照如下过程使用UDP收发数据:
UDP报文的发送过程:首先应该为数据开辟一个pbuf,将用户数据填写到pbuf的数据区域,最好pbuf数据区前面已经为UDP、IP和以太网首部预留了足够的空间;然后用户程序将该数据pbuf作为参数,调用UDP提供的数据发送函数udp_send或udp_sendto。当UDP层发送数据pbuf时,它会在该pbuf中填入UDP首部区域形成一个完整的UDP报文,最后调用IP层的函数来发送报文。上面udp_sendto调用ip_route是为了获得本地IP地址,便于计算UDP报文首部的校验和字段(需要伪首部参与计算,伪首部中需要本地IP字段)。UDP报文发送的实现代码如下:
// rt-thread\components\net\lwip-1.4.1\src\core\udp.c
/**
* 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;
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;
}
}
/* not enough space to add an UDP header to first pbuf in given p chain? */
if (pbuf_header(p, UDP_HLEN)) {
/* allocate header in a separate new pbuf */
q = pbuf_alloc(PBUF_IP, UDP_HLEN, PBUF_RAM);
/* 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);
}
} else {
/* 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;
udphdr->src = htons(pcb->local_port);
udphdr->dest = htons(dst_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)) {
/* use outgoing network interface IP address as source address */
src_ip = &(netif->ip_addr);
} else {
/* 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);
}
{ /* UDP */
udphdr->len = htons(q->tot_len);
/* calculate checksum */
if ((pcb->flags & UDP_FLAGS_NOCHKSUM) == 0) {
u16_t udpchksum;
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;
}
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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
本质上,udp_send通过调用函数udp_sendto来实现其功能,udp_sendto通过调用函数udp_sendto_if来完成对报文的组装和发送,在调用udp_sendto_if之前,udp_sendto需要跟IP层进行交互操作,即调用IP层的函数ip_route为报文寻找一个网络接口结构,这个接口结构中记录了整个系统中的有效IP地址。这里的重点是函数udp_sendto_if,它的主要功能是完成UDP报文的组装,同时调用IP层的发送函数ip_output_if发送报文。
在IP层,当收到一个包含UDP报文的数据报时,函数udp_input会被调用,以处理报文。该函数的处理过程相对繁琐,首先是进行一些报文合法性的检验,然后根据报文中的端口信息查找匹配的UDP控制块,并把报文递交给控制块中注册的用户自定义函数处理。UDP接收报文的实现代码如下:
// rt-thread\components\net\lwip-1.4.1\src\core\udp.c
/**
* Process an incoming UDP datagram.
*
* Given an incoming UDP datagram (as a chain of pbufs) this function
* finds a corresponding UDP PCB and hands over the pbuf to the pcbs
* recv function. If no pcb is found or the datagram is incorrect, the
* pbuf is freed.
* @param p pbuf to be demultiplexed to a UDP PCB.
* @param inp network interface on which the datagram was received.
*/
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;
iphdr = (struct ip_hdr *)p->payload;
/* Check minimum length (IP header + UDP header)
* and move payload pointer to UDP header */
if (p->tot_len < (IPH_HL(iphdr) * 4 + UDP_HLEN) || pbuf_header(p, -(s16_t)(IPH_HL(iphdr) * 4))) {
/* drop short packets */
pbuf_free(p);
goto end;
}
udphdr = (struct udp_hdr *)p->payload;
/* is broadcast packet ? */
broadcast = ip_addr_isbroadcast(¤t_iphdr_dest, inp);
/* convert src and dest ports to host byte order */
src = ntohs(udphdr->src);
dest = ntohs(udphdr->dest);
{
prev = NULL;
local_match = 0;
uncon_pcb = NULL;
/* Iterate through the UDP pcb list for a matching pcb.
* 'Perfect match' pcbs (connected to the remote port & ip address) are
* preferred. If no perfect match is found, the first unconnected pcb that
* matches the local port and ip address gets the datagram. */
for (pcb = udp_pcbs; pcb != NULL; pcb = pcb->next) {
local_match = 0;
/* compare PCB local addr+port to UDP destination addr+port */
if (pcb->local_port == dest) {
if (
(!broadcast && ip_addr_isany(&pcb->local_ip)) ||
ip_addr_cmp(&(pcb->local_ip), ¤t_iphdr_dest) ||
(broadcast && (ip_addr_isany(&pcb->local_ip) ||
ip_addr_netcmp(&pcb->local_ip, ip_current_dest_addr(), &inp->netmask)))) {
local_match = 1;
if ((uncon_pcb == NULL) &&
((pcb->flags & UDP_FLAGS_CONNECTED) == 0)) {
/* the first unconnected matching PCB */
uncon_pcb = pcb;
}
}
}
/* compare PCB remote addr+port to UDP source addr+port */
if ((local_match != 0) &&
(pcb->remote_port == src) &&
(ip_addr_isany(&pcb->remote_ip) ||
ip_addr_cmp(&(pcb->remote_ip), ¤t_iphdr_src))) {
/* the first fully matching PCB */
if (prev != NULL) {
/* move the pcb to the front of udp_pcbs so that is
found faster next time */
prev->next = pcb->next;
pcb->next = udp_pcbs;
udp_pcbs = pcb;
}
break;
}
prev = pcb;
}
/* no fully matching pcb found? then look for an unconnected pcb */
if (pcb == NULL) {
pcb = uncon_pcb;
}
}
/* Check checksum if this is a match or if it was directed at us. */
if (pcb != NULL || ip_addr_cmp(&inp->ip_addr, ¤t_iphdr_dest)) {
if (udphdr->chksum != 0) {
if (inet_chksum_pseudo(p, ip_current_src_addr(), ip_current_dest_addr(),
IP_PROTO_UDP, p->tot_len) != 0) {
pbuf_free(p);
goto end;
}
}
if(pbuf_header(p, -UDP_HLEN)) {
/* Can we cope with this failing? Just assert for now */
pbuf_free(p);
goto end;
}
if (pcb != NULL) {
/* callback */
if (pcb->recv != NULL) {
/* now the recv function is responsible for freeing p */
pcb->recv(pcb->recv_arg, pcb, p, ip_current_src_addr(), src);
} else {
/* no recv function registered? then we have to free the pbuf! */
pbuf_free(p);
goto end;
}
} else {
/* No match was found, send ICMP destination port unreachable unless
destination address was broadcast/multicast. */
if (!broadcast &&
!ip_addr_ismulticast(¤t_iphdr_dest)) {
/* move payload pointer back to ip header */
pbuf_header(p, (IPH_HL(iphdr) * 4) + UDP_HLEN);
LWIP_ASSERT("p->payload == iphdr", (p->payload == iphdr));
icmp_dest_unreach(p, ICMP_DUR_PORT);
}
pbuf_free(p);
}
} else {
pbuf_free(p);
}
end:
PERF_STOP("udp_input");
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
这个函数最大的难点在于根据报文中的源端口与目的端口寻找匹配的UDP控制块。链表上所有的控制块可以分为两大类,即已经绑定了远端IP地址和端口号的已连接控制块,以及未绑定远端信息的未连接控制块。与报文中目的端口号和源端口号都匹配且处于连接状态的控制块将被视作最佳匹配的控制块,若没有这样的控制块,则与目的端口号相匹配的未连接控制块将被视作查找结构。关于目的IP地址与本地IP地址的匹配,有三种情况可视为匹配:一是非广播包,且控制块绑定到本地任意IP地址上(IP为0);二是控制块绑定的IP地址与IP包中的目的IP地址完全相同;三是对于广播包,控制块绑定的IP地址与目的IP地址处于同一个子网内。
使用UDP传输、处理数据的关键在于用户自定义的数据处理函数,当UDP匹配到某个控制块时,将回调用户注册的处理函数,用户程序应该负责报文pbuf的删除工作。如果找不到匹配的端口号,而该数据报确实是发送给本地的,则一个端口不可达报文会被返回给源主机。
评论记录:
回复评论: