对于非EAI_SYSTEM错误码,APR并不能直接返回。正如第一章所说,APR中对于apr_status_t返回码有自己的布局和规则,因此这些错误码必须转换至APR返回码。EAI_XXXX错误码的起始偏移是APR_OS_START_EAIERR,因此返回值实际上是rc+APR_OS_START_EAIERR。不过在一些平台上比如glibc,为了防止和h_errno的值冲突,系统将使用EAI_XXXX的负值, 这正是上面的代码w的原因。
上面的代码有一个假设前提,就是系统中必须提供getnameinfo()函数。但是由于getnameinfo()是比较新的一个函数,并不是每个操作系统平台都支持该函数。目前大部分Ipv4平台上不过都提供了gethostbyaddr()函数,通过该函数也能完成从主机地址到主机名称的转换,不过该函数仅仅支持Ipv4协议,不支持Ipv6协议。具体的代码如下所示:
#else
#if APR_HAS_THREADS && !defined(GETHOSTBYADDR_IS_THREAD_SAFE) &&
defined(HAVE_GETHOSTBYADDR_R) && !defined(BEOS)
#ifdef GETHOSTBYNAME_R_HOSTENT_DATA
struct hostent_data hd;
#else
char tmp[GETHOSTBYNAME_BUFLEN];
#endif
int hosterror;
struct hostent hs, *hptr;
#if defined(GETHOSTBYNAME_R_HOSTENT_DATA)
/* AIX, HP/UX, D/UX et alia */
gethostbyaddr_r((char *)&sockaddr->sa.sin.sin_addr, u
sizeof(struct in_addr), AF_INET, &hs, &hd);
hptr = &hs;
#else
#if defined(GETHOSTBYNAME_R_GLIBC2)
/* Linux glibc2+ */
gethostbyaddr_r((char *)&sockaddr->sa.sin.sin_addr, v
sizeof(struct in_addr), AF_INET,
&hs, tmp, GETHOSTBYNAME_BUFLEN - 1, &hptr, &hosterror);
#else
/* Solaris, Irix et alia */
hptr = gethostbyaddr_r((char *)&sockaddr->sa.sin.sin_addr, w
sizeof(struct in_addr), AF_INET,
&hs, tmp, GETHOSTBYNAME_BUFLEN, &hosterror);
#endif /* !defined(GETHOSTBYNAME_R_GLIBC2) */
if (!hptr) {
*hostname = NULL;
return hosterror + APR_OS_START_SYSERR;
}
#endif /* !defined(GETHOSTBYNAME_R_HOSTENT_DATA) */
#else
struct hostent *hptr;
hptr = gethostbyaddr((char *)&sockaddr->sa.sin.sin_addr, x
sizeof(struct in_addr), AF_INET);
#endif
if (hptr) {
*hostname = sockaddr->hostname = apr_pstrdup(sockaddr->pool, hptr->h_name);
return APR_SUCCESS;
}
*hostname = NULL;
#if defined(WIN32)
return apr_get_netos_error();
#elif defined(OS2)
return h_errno;
#else
return h_errno + APR_OS_START_SYSERR;
#endif
#endif
函数中众多的预定义让人眼花缭乱。不过最主要的预定义处理还在于对gethostbyaddr()函数的调用。从上面的代码中可以看出,gethostbyaddr有一个函数变形gethostbyaddr_r,而且不同平台下的gethostbyaddr_t函数的参数也不相同,要了解详细的原因,必须了解一些函数可重入的概念。
所谓可重入函数是指一个可以被多个任务调用的函数,任务在调用时候不必担心数据会出错;通常情况下下面的函数是不可重入的:
(1)、函数体内使用了静态的数据结构;
(2)、函数体内调用了malloc()或者free()函数;
(3)、函数体内调用了标准I/O函数。
通常情况下,在一个UNIX进程中发生重入问题的条件是:从主程序中和某个信号处理函数中同时调用某个不可重入函数.。另外在多线程应用中也会出现函数重入的问题。不幸的是由于历史的原因,我们经常使用的gethostbyaddr也是一个不可重入的函数,因为它们都返回指向同一个静态结构的指针。关于gethostbyaddr的重入问题,《Unix网络编程 第一卷:套接口API》中文版第二版的第207页中有一段描述,摘抄如下:
不幸的是,重入问题比他表面看起来更要严重。首先,关于gethostbyname和gethostbyaddr的重入问题无标准可循。POSIX规范声明这两个函数不必是可重入的。Unix98只说这两个函数必须是线程安全的。
其次,关于_r函数也没有标准可循。Solaris 2.X,Digital Unix 4.0和HP-UX 10.30都提供了可重入版本的gethostbyaddr_r函数,不过它们的参数并不相同,不同版本的gethostbyaddr_r函数原型如下表所示:
操作系统平台 函数原型
solaris struct hostent* gethostbyaddr_r(const char *addr, int len, int type,
struct hostent *result, char *buf, int buflen, int * h_errnop);
AIX,HP-UX,Digital Unix int gethostbyaddr_r(const char *addr, int len, int type, struct hostent *result,
struct hostent_data *buffer);
Linux glibc2+ int gethostbyaddr_r(const char *addr,int len, int type,struct hostent *result,
char* buf, int buflen, struct hostent *hs, int* h_errnop);
大部分gethostbyaddr_r函数的前四个参数都相同,第一个是需要转换的地址;第二个地址的字节大小,用sizeof(struct in_addr)表示;第三个是需要转换地址的协议族,或者是AF_INET,或者是AF_INET6;第四个则是描述主机的hostent结构。区别通常在后几个参数:
对于Solaris,Irix等操作系统而言,后面还需要三个额外的参数,buf是由调用者分配的并且大小为buflen的缓冲区,该缓冲区用于存放规范主机名称,别名指针数组,各个别名字符串,地址指针数组以及各个实际地址。如果初出错,错误码通过h_errnop指针返回,注意不是我们通常所说的h_errno返回。
对于AIX,HP-UX,Digital Unix等平台而言,后面的三个参数则被组合为一个新的数据结构hostent_data,指向该结构的指针构成本函数的第三个和最后一个参数。Apache中默认的缓冲区大小为GETHOSTBYNAME_BUFLEN,即512字节。
对于Linux glibc2+而言,gethostbyaddr_r的参数与前两者又存在一定的差异,它共计有八个参数,与Solaris平台相比多了struct hostent* hs参数。
如果操作系统平台不支持可重入的gethostaddr_r函数,那么只能使用不可重入的gethostbyaddr函数,如x所示。
返回的主机名称保存在hostent结构中,如果查询成功,从hostname参数中返回即可。
9.1.3.3 IP地址解析
APR_DECLARE(apr_status_t) apr_parse_addr_port(char **addr,
char **scope_id,
apr_port_t *port,
const char *str,
apr_pool_t *p);