在了解APR中對IP地址的封裝之前,我們首先看一下通常情況下對IP地址的使用情況。下面的代碼掩飾了簡單的服務器端套接字的地址初始化過程:
struct sockaddr_in server_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, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
accept(sockfd, (struct sockaddr *)&remote_addr, &sin_size);
Socket API中提供了三種類型的地址:sockaddr,sockaddr_in和sockaddr_un。sockaddr是通用的套接字結構,sockaddr_in為Internet協議族的地址描述結構,sockaddr_un則是Unix協議組的地址描述結構。sockaddr_in結構中的sa_family決定是sockaddr_in還是sockaddr_un。
如果直接使用Socket API提供的地址結構,則至少存在下面的幾個問題:
1、在網絡應用程序中,對于internet地址,如上面的程序代碼所示,通常總是使用sockaddr_in描述,而在一些Socket API函數中則使用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中:
struct apr_sockaddr_t {
apr_pool_t *pool;
/*第一部分*/
char *hostname;
char *servname;
/*第二部分*/
apr_port_t port;
apr_int32_t family;
union {
struct sockaddr_in sin;
#if APR_HAVE_IPV6
struct sockaddr_in6 sin6;
#endif
#if APR_HAVE_SA_STORAGE
struct sockaddr_storage sas;
#endif
} sa;
/*第三部分*/
apr_socklen_t salen;
int ipaddr_len;
int addr_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(struct sockaddr_in),對于IPV6,則是sizeof(struct sockaddr_in6);ipiaddr_len則是對應得IP地址結構的長度,對于Ipv4總是sizeof(struct in_addr),而對于Ipv6,則是sizeof(struct in6_addr);addr_str_len則是IP地址緩沖的長度,對于Ipv4,該值為14,而對于IPV6,則是46。這三個地址的含義完全不同。
Ipaddr_ptr指針指向sockaddr結構中的IP地址結構,通常情況下,它的初始化使用下面的代碼:
apr_socketaddr_t addr;
addr->ipaddr_ptr = &(addr->sa.sin.sin_addr);
對于一些服務器而言,可能會使用多個IP地址。這些IP地址之間通過next指針形成單鏈表結構。
從next可以看出各個socket地址之間可以形成鏈表。
9.1.2子網掩碼結構
與此同時,APR中也定義了數據結構apr_ipsubnet_t來描述IP地址掩碼,當然由于IP地址分為Ipv4和Ipv6,因此掩碼描述也可以分為兩種,apr_ipsubnet_t結構定義在文件apr_sockaddr.c中,屬于內部數據結構,具體如下:
struct apr_ipsubnet_t {
int family;
#if APR_HAVE_IPV6
apr_uint32_t sub[4]; /* big enough for IPv4 and IPv6 addresses */
apr_uint32_t mask[4];
#else
apr_uint32_t sub[1];
apr_uint32_t mask[1];
#endif
};
family是當前掩碼所屬于的地址族,APR_INET表示Ipv4,而APR_INET6則表示Ipv6。
對于Ipv4而言,該結構演變為如下:
struct apr_ipsubnet_t {
int family;
apr_uint32_t sub[1];
apr_uint32_t mask[1];
};
而對于Ipv6,則該結構可以演變為如下:
struct apr_ipsubnet_t {
int family;
apr_uint32_t sub[4]; /* big enough for IPv4 and IPv6 addresses */
apr_uint32_t mask[4];
};
9.1.3 Socket地址處理接口
為了處理Socket地址,APR中提供了四個操作接口,這些接口定義在apr_network_io.h中,而實現則sockaddr.c中。這四個接口分別是:
9.1.3.1地址獲取
由于APR中僅僅使用apr_sockaddr_t結構描述套接字地址,因此其余的各類描述信息最終都要轉換為該結構,APR中提供apr_sockaddr_info_get函數實現該功能:
APR_DECLARE(apr_status_t) apr_sockaddr_info_get(apr_sockaddr_t **sa,
const char *hostname,
apr_int32_t family,
apr_port_t port,
apr_int32_t flags,
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_t masked;
*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)) {
return APR_EINVAL;u
}
#if !APR_HAVE_IPV6
if (flags & APR_IPV6_ADDR_OK) {
return APR_ENOTIMPL;
}
#endif
}
#if !APR_HAVE_IPV6
if (family == APR_UNSPEC) {
family = APR_INET; v
}
#endif
return find_addresses(sa, hostname, family, port, flags, p); w
}