当前位置: 首页 > 图文教程 > 服务器 > Linux服务器 > Apache源代码全景分析:网络地址处理
Linux服务器 中的 Apache源代码全景分析:网络地址处理
出处:互联网 整理: 软晨网(RuanChen.com) 发布: 2009-10-18 浏览: 276 ::
收藏到网摘:
n/a
套接字地址在了解APR中对IP地址的封装之前,我们首先看一下通常情况下对IP地址的使用情况。下面的代码掩饰了简单的服务器端套接字的地址初始化过程:structsockaddr_inserver_addr;/*本机地址信息*/server_addr.sin_family=AF_INET;server_addr.sin_port=htons(SERVPORT);server_addr.sin_addr.s_addr=INADDR_ANY;bzero(&(server_addr.sin_zero),8);……bind(sockfd,(structsockaddr*)&my_addr,sizeof(structsockaddr));accept(sockfd,(structsockaddr*)&remote_addr,&sin_size);SocketAPI中提供了三种类型的地址:sockaddr,sockaddr_in和sockaddr_un。sockaddr是通用的套接字结构,sockaddr_in为Internet协议族的地址描述结构,sockaddr_un则是Unix协议组的地址描述结构。sockaddr_in结构中的sa_family决定是sockaddr_in还是sockaddr_un。如果直接使用SocketAPI提供的地址结构,则至少存在下面的几个问题:1、在网络应用程序中,对于internet地址,如上面的程序代码所示,通常总是使用sockaddr_in描述,而在一些SocketAPI函数中则使用sockaddr作为套接字地址,因此在使用的时候必须将sockaddr_in强制转换为sockaddr类型,这是一个麻烦而且容易出错的地方。2、sockaddr_in也不是一个特别容易理解的数据结构。通常情况下,sin_family和sin_port相对容易记忆,而套接字地址sin_addr.s_addr则未必。套接字的这种结构对一般人而言无疑是一种噩梦。3、另外一个问题则是Ipv6的地址问题。目前,Apache已经开始同时支持Ipv4和Ipv6两种类型的地址,如果用户需要支持Ipv6,则还必须使用Ipv6对应的地址数据结构。对于一个良好的类库,不管是Ipv4还是Ipv6协议,都必须提供同样的接口,这种接口必须简单易懂,同时必须尽可能的隐藏内部的细节,比如对于sin_addr.s_addr无非暴露给用户。基于上面的分析,APR中只使用一种数据结构apr_sockaddr_t来描述IP地址,该结构定义在文件apr_network_io.h中:structapr_sockaddr_t{apr_pool_t*pool;/*第一部分*/char*hostname;char*servname;/*第二部分*/apr_port_tport;apr_int32_tfamily;union{structsockaddr_insin;#ifAPR_HAVE_IPV6structsockaddr_in6sin6;#endif#ifAPR_HAVE_SA_STORAGEstructsockaddr_storagesas;#endif}sa;/*第三部分*/apr_socklen_tsalen;intipaddr_len;intaddr_str_len;void*ipaddr_ptr;apr_sockaddr_t*next;}; 该结构描述了socket地址的三部分的信息内容:第一部分:Hostname是该地址对应的主机名称,而servname则是对应端口的服务名称,比如80对应的名称为”www”,21端口对应的servname则是”FTP”。如果某个端口比如9889并没有对应某个众所皆知的服务,那么servname则直接是端口的字符串描述。第二部分:该部分则对应的是sockaddr结构中的内容,port是端口,family则是地址协议族类型,包括AF_INET,AF_UNIX等。sa则为联合类型,用以描述对应的套接字地址,或者是Ipv4类型,或者是Ipv6类型,两者只能居其一。第三部分:这部分主要是一些与套接字地址相关的附加信息。Salen是当前套接字地址的长度,通常情况下它的值为sizeof(structsockaddr_in),对于IPV6,则是sizeof(structsockaddr_in6);ipiaddr_len则是对应得IP地址结构的长度,对于Ipv4总是sizeof(structin_addr),而对于Ipv6,则是sizeof(structin6_addr);addr_str_len则是IP地址缓冲的长度,对于Ipv4,该值为14,而对于IPV6,则是46。这三个地址的含义完全不同。Ipaddr_ptr指针指向sockaddr结构中的IP地址结构,通常情况下,它的初始化使用下面的代码:apr_socketaddr_taddr;addr->ipaddr_ptr=&(addr->sa.sin.sin_addr);对于一些服务器而言,可能会使用多个IP地址。这些IP地址之间通过next指针形成单链表结构。从next可以看出各个socket地址之间可以形成链表。子网掩码结构与此同时,APR中也定义了数据结构apr_ipsubnet_t来描述IP地址掩码,当然由于IP地址分为Ipv4和Ipv6,因此掩码描述也可以分为两种,apr_ipsubnet_t结构定义在文件apr_sockaddr.c中,属于内部数据结构,具体如下:structapr_ipsubnet_t{intfamily;#ifAPR_HAVE_IPV6apr_uint32_tsub[4];/*bigenoughforIPv4andIPv6addresses*/apr_uint32_tmask[4];#elseapr_uint32_tsub[1];apr_uint32_tmask[1];#endif};family是当前掩码所属于的地址族,APR_INET表示Ipv4,而APR_INET6则表示Ipv6。对于Ipv4而言,该结构演变为如下:structapr_ipsubnet_t{intfamily;apr_uint32_tsub[1];apr_uint32_tmask[1];};而对于Ipv6,则该结构可以演变为如下:structapr_ipsubnet_t{intfamily;apr_uint32_tsub[4];/*bigenoughforIPv4andIPv6addresses*/apr_uint32_tmask[4];}; Socket地址处理接口为了处理Socket地址,APR中提供了四个操作接口,这些接口定义在apr_network_io.h中,而实现则sockaddr.c中。这四个接口分别是:地址获取由于APR中仅仅使用apr_sockaddr_t结构描述套接字地址,因此其余的各类描述信息最终都要转换为该结构,APR中提供apr_sockaddr_info_get函数实现该功能:APR_DECLARE(apr_status_t)apr_sockaddr_info_get(apr_sockaddr_t**sa,constchar*hostname,apr_int32_tfamily,apr_port_tport,apr_int32_tflags,apr_pool_t*p);该函数允许从主机名hostname,地址协议族family和端口port创建新的apr_sockaddr_t地址,并由sa返回。hostname参数允许是实际的主机名称,或者也可以是字符串类型的IP地址,比如”127.0.0.1”,甚至可以是NULL,此时默认的地址是”0.0.0.0”。family的值可以是AF_INET,AF_UNIX等系统定义类型,也可以是APR_UNSPEC类型,此时,地址协议族由系统决定。flags参数用以指定Ipv4和Ipv6处理的优先级,它的取值包括两种:APR_IPV4_ADDR_OK和APR_IPV6_ADDR_OK。这两个标志并不是在所有的情况下都有效,这可以从函数的实现中看出它的用法:{apr_int32_tmasked;*sa=NULL;if((masked=flags&(APR_IPV4_ADDR_OK|APR_IPV6_ADDR_OK))){if(!hostname||family!=APR_UNSPEC||masked==(APR_IPV4_ADDR_OK|APR_IPV6_ADDR_OK)){returnAPR_EINVAL;u}#if!APR_HAVE_IPV6if(flags&APR_IPV6_ADDR_OK){returnAPR_ENOTIMPL;}#endif}#if!APR_HAVE_IPV6if(family==APR_UNSPEC){family=APR_INET;v}#endifreturnfind_addresses(sa,hostname,family,port,flags,p);w}从实现代码可以看出,函数的内部实际的地址转换过程是由函数find_address完成的。不过在调用find_address之前,函数进行了相关检查和预处理,这些检查和预处理包括:1、APR_IPV4_ADDR_OK标记只有在hostname为NULL,同时family为APR_UNSPEC的时候才会有效,而APR_IPV6_ADDR_OK和APR_IPV4_ADDR_OK是相互排斥的,一旦定义了APR_IPV4_ADDR_OK,就不能使用APR_IPV6_ADDR_OK,反之亦然。只有在hostname为NULL,同时family为APR_UNSPEC并且没有定义APR_IPV4_ADDR_OK的时候APR_IPV6_ADDR_OK才会有效。2、如果操作系统平台并不支持IPV6,同时并没有限定获取的地址族,那么此时将默认为IPV6。如果指定必须获取IPV6的地址信息,但系统并不提供支持,此时返回APR_EINVAL。一般情况下,在IPV4中从主机名到网络地址的解析可以通过gethostbyname()函数完成,不过该API不允许调用者指定所需地址类型的任何信息,这意味着它仅返回包含IPV4地址的信息,对于目前新的IPV6则无能为力。一些平台中为了支持IPV6地址的解析,提供了新的地址解析函数getaddrinfo()以及新的地址描述结构structaddrinfo。APR中通过宏HAVE_GETADDRINFO判断是否支持IPV6地址的解析。目前Window2000/XP以上的操作系统都能支持新特性。为此APR中根据系统平台的特性采取不同的方法完成地址解析。 首先我们来看支持IPV6地址解析平台下的实现代码,find_address函数的实现如下:staticapr_status_tfind_addresses(apr_sockaddr_t**sa,constchar*hostname,apr_int32_tfamily,apr_port_tport,apr_int32_tflags,apr_pool_t*p){if(flags&APR_IPV4_ADDR_OK){apr_status_terror=call_resolver(sa,hostname,AF_INET,port,flags,p);#ifAPR_HAVE_IPV6if(error){family=AF_INET6;/*tryagain*/u}else#endifreturnerror;}#ifAPR_HAVE_IPV6elseif(flags&APR_IPV6_ADDR_OK){apr_status_terror=call_resolver(sa,hostname,AF_INET6,port,flags,p);if(error){vfamily=AF_INET;/*tryagain*/}else{returnAPR_SUCCESS;}}#endifreturncall_resolver(sa,hostname,family,port,flags,p);w}从上面的代码可以清晰的看到APR_IPV4_ADDR_OK和APR_IPV6_ADDR_OK的含义:对于前者,函数内部首先查询对应主机的IPV4地址,只有在IPV4查询失败的时候才会继续查询IPV6地址;而后者则与之相反,对于给定的主机名称,首先查询IPV6地址,只有在查询失败的时候才会查询IPV4。因此APR_IPV4_ADDR_OK和APR_IPV6_ADDR_OK决定了查询的优先性,任何时候一旦查询成功都不会继续查询另外协议地址,即使被查询主机具有该协议地址。 查询的核心代码封装在内部函数call_resolve中,该函数的参数和apr_sockaddr_info_get函数的参数完全相同且对应,call_resolve中的宏处理比较的多,因此我们将分开描述:staticapr_status_tcall_resolver(apr_sockaddr_t**sa,constchar*hostname,apr_int32_tfamily,apr_port_tport,apr_int32_tflags,apr_pool_t*p){structaddrinfohints,*ai,*ai_list;apr_sockaddr_t*prev_sa;interror;char*servname=NULL;memset(&hints,0,sizeof(hints));hints.ai_family=family;hints.ai_socktype=SOCK_STREAM;#ifdefHAVE_GAI_ADDRCONFIGif(family==APR_UNSPEC){hints.ai_flags=AI_ADDRCONFIG;}#endif在了解上面的代码之前我们首先简要的了解一些getaddrinfo函数的用法,该函数定义如下:intgetaddrinfo(constchar*hostname,constchar*service,conststructaddinfo*hints,structaddrinfo**result);hostname是需要进行地址解析的主机名称或者是二进制的地址串(IPV4的点分十进制或者Ipv6的十六进制数串),service则是一个服务名或者是一个十进制的端口号数串。其中hints是addfinfo结构,该结构定义如下:structaddrinfo{intai_flags;/*AI_PASSIVE,AI_CANONNAME,AI_NUMERICHOST*/intai_family;/*PF_xxx*/intai_socktype;/*SOCK_xxx*/intai_protocol;/*0orIPPROTO_xxxforIPv4andIPv6*/size_tai_addrlen;/*lengthofai_addr*/char*ai_canonname;/*canonicalnamefornodename*/structsockaddr*ai_addr;/*binaryaddress*/structaddrinfo*ai_next;/*nextstructureinlinkedlist*/};hints参数可以是一个空置针,也可以是一个指向某个addrinfo结构的指针,调用者在该结构中填入关于期望返回的信息类型的暗示,这些暗示将控制内部的转换细节。比如,如果指定的服务器既支持TCP,也支持UDP,那么调用者可以把hints结构中的ai_socktype成员设置为SOCK_DGRAM,使得返回的仅仅是适用于数据报套接口的信息。 hints结构中调用者可以设置的成员包括ai_flags,ai_family,ai_socktype和ai_protocol。其中,ai_flags成员可用的标志值及含义如下:标志名称标志含义AI_PASSIVE套接口将用于被动打开AI_CANONNAME告知getaddrinfo函数返回主机的规范名称AI_NUMERICHOST防止任何类型的名字到地址的映射;hostname必须是一个地址串AI_NUMERICSERV防止任何类型的名字到服务的映射,service参数必须是一个十进制端口号数串AI_V4MAPPED如果同时指定ai_family成员的值为AF_INET6和AF_INET,那么如果没有可用的AAAA记录就返回与A记录对应得Ipv4映射的IPV6地址AI_ALL如果同时指定AI_V4MAPPED标志,那么除了返回与AAAA对应得IPV6地址之外,还会返回与A记录对应的IPV4映射的Ipv6地址。AI_ADDRCONFIG按照所在主机的配置选择返回的地址类型,也就是只查找与所在主机回馈接口以外的网络接口配置的IP地址版本一直的地址。只有当本地系统中配置仅仅配置了IPV4地址才会将主机名称转换位IPV4地址;同样只有当本地系统中仅配置了IPV6地址的时候才会返回IPV6地址。Loopback地址并不在这种限制之中。ai_family参数指定调用者期待返回的套接口地址结构的类型。它的值包括三种:AF_INET,AF_INET6和AF_UNSPEC。如果指定AF_INET,那么函数九不能返回任何IPV6相关的地址信息;如果仅指定了AF_INET6,则就不能返回任何IPV4地址信息。AF_UNSPEC则意味着函数返回的是适用于指定主机名和服务名且适合任何协议族的地址。如果某个主机既有AAAA记录(IPV6)地址,同时又有A记录(IPV4)地址,那么AAAA记录将作为sockaddr_in6结构返回,而A记录则作为sockaddr_in结构返回。if(hostname==NULL){#ifdefAI_PASSIVEhints.ai_flags|=AI_PASSIVE;#endif#ifdefOSF1hostname=family==AF_INET6?"::":"0.0.0.0";servname=NULL;#ifdefAI_NUMERICHOSThints.ai_flags|=AI_NUMERICHOST;#endif#else#ifdef_AIXif(!port){servname="1";}else#endif/*_AIX*/servname=apr_itoa(p,port);#endif/*OSF1*/}#ifdefHAVE_GAI_ADDRCONFIGif(error==EAI_BADFLAGS&&family==APR_UNSPEC){hints.ai_flags=0;error=getaddrinfo(hostname,servname,&hints,&ai_list);}#endifif(error){#ifndefWIN32if(error==EAI_SYSTEM){returnerrno;}else#endif{#ifdefined(NEGATIVE_EAI)error=-error;#endifreturnerror+APR_OS_START_EAIERR;}} 主机名获取apr_sockaddr_info_get函数用以完成从主机名到网络地址的转换,而APR中提供的apr_getnameinfo则可以实现从网络地址到主机名的转换,该函数定义如下:APR_DECLARE(apr_status_t)apr_getnameinfo(char**hostname,apr_sockaddr_t*sa,apr_int32_tflags);参数sa指定需要转换的网络地址,转换后的主机名由hostname返回。fags是标志位,用以控制内部的转换过程。{#ifdefined(HAVE_GETNAMEINFO)