UDP是一个简单的面向数据报的传输层协议,我们先站在UDP客户端的角度来看看如何发送一个UDP数据报,以及协议栈为发送一个UDP数据报做了哪些事情。
UDP数据报可以在未连接的socket上发送(使用sendto系统调用,指定目的地址),也可以在已连接的socket上发送(使用send系统调用,不用指定目的地址),下面我们分两种情况讨论。
下面是一个在未连接的socket上发送UDP数据的用户态程序示例(注:该程序的格式和风格相当不好,只是为临时测试使用。),该程序目前还只管发送,不处理接收,关于接收,我们后面再作分析:
#include
#include
#include
#include "my_inet.h"
#include
#include
#include
#include
int main()
{
int i;
struct sockaddr_in dest;
dest.sin_family = MY_PF_INET;
dest.sin_port = htons(16000);
dest.sin_addr.s_addr = 0x013010AC; //目的地址是172.16.48.1(网络字节序)
//创建UDP数据报服务的socket。
int fd = socket( MY_PF_INET, SOCK_DGRAM, MY_IPPROTO_UDP );
if( fd < 0 ){
perror("socket: ");
return -1;
}
int bwrite = sendto( fd, "abcdefg", 7, 0, (struct sockaddr *)&dest, sizeof(dest) );
if( bwrite == -1 ){
perror("send: ");
close(fd);
return -1;
}
printf("sendto: %d/n", bwrite);
close( fd );
return 0;
}
创建socket的操作跟RAW协议的差不多,只有极少区别,内核中表示套接字的结构上的操作集,协议名略有不同而已。我们重点看sendto操作所引发
的内核代码执行。sendto所到达的my_inet模块的第一站是myinet_sendmsg,一般来讲,该函数只要调用udp协议自己的
udp_sendmsg即可,但在之前,它还有一样事情要完成,就是为这个socket执行绑定,这个绑定可能跟服务器端的bind系统调用有些区别。试
想,如果我们用这个udp socket发送出去了一个数据报,但没有记录下这个udp
socket,那等对端的回应数据报来的时候,我们就不知道哪个socket要接收这个数据报了。绑定就是记录这个udp socket。
myudp_hash是一个具有128项的哈希数组,每一项都是一个udp socket的链表,每个udp socket以自己的源地址端口号为哈希主键插入这个数组。源地址端口可以是用户自己指定的,也可以是由内核自动分配的。
内核自动分配的源端口号有一个范围,这个范围段似乎是由系统的内存大小决定的(具体有待进一步分析),如果内存大(似乎是有高端内存可用),范围段是
32768-61000,否则就是1024-4999。udp_port_rover是一个全局变量,初始值为范围段的下限,每次新分配端口,记录下新分
配的端口号,下一次再分配时,在前一次的基础上加1,然后查询对应的myudp_hash中的项,如果该项的链表不为空,则找下一项,直至遍历整个数组,
如果为空,则分配成功。所以,当连续分配128个端口后(数组中的128项中,链表全不为空),这个查询必然失败,最后遍历数组完成时,得到的端口号必然
是前一次分配的端口号加127,然后,端口号每次加128,再查询对应的数组项,看该端口号有没有被使用掉。
这个描述可能有点模糊,简单总结一下就是:每次分配一个端口号,先在前一次分配值的基础上以1为步进值递增,如果对应的哈希数组中的链表为空,则肯定没有被使用过,直接使用。如果遍历完整个哈希表都没有空的链表,则要查询链表中的每一项,以得到未使用的端口。
用户自己指定一个端口,则我们到对应的哈希数组中的链表查询,如果已被使用,并且不能重用,则分配端口号失败。对用户自己指定的端口,没有范围段的限制。这个一般用于服务端,而自动分配端口用于客户端。
绑定完成后,myinet_sendmsg会调用myudp_sendmsg,它与myraw_sendmsg所执行的操作相差并不多。先查询输出路由,
然后添加协议首部,最后发送数据包。与raw相比,udp要在IP首部前添加一个UDP首部。以下是UDP首部的定义:
struct udphdr {
__u16 source; //发送端端口号。
__u16 dest; //目的端端口号。
__u16 len; //UDP长度。
__u16 check; //UDP检验和。
};
UDP是一个传输层协议,与下层的网络层协议相比,它不仅需要知道数据传输的两端的主机,还需要知道是主机上的哪个进程在进行数据传输,端口号其实就是用
于标识发送进程和接收进程的。UDP长度是UDP头加上UDP数据的长度(不包括IP首部)。UDP检验和覆盖UDP首部和UDP数据。
由于UDP数据报在未连接的socket上进行发送,所以每次进入myraw_sendmsg,都要进行输出路由的查询,以确定源地址和目的地址。但我们
知道,路由是有缓存的,所以,并没有太多的额外开销。认为在未连接的socket上发送UDP数据报开销要大的观点并不完全正确。
阅读(1530) | 评论(0) | 转发(1) |