显示标签为“APR”的博文。显示所有博文
显示标签为“APR”的博文。显示所有博文

2009年3月5日星期四

IPV4和IPV6在APR中的用法

由于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
}
从实现代码可以看出,函数的内部实际的地址转换过程是由函数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()以及新的地址描述结构struct addrinfo。APR中通过宏HAVE_GETADDRINFO
判断是否支持IPV6地址的解析。目前Window 2000/XP以上的操作系统都能支持新特性。为此APR
中根据系统平台的特性采取不同的方法完成地址解析。
首先我们来看支持IPV6地址解析平台下的实现代码,find_address函数的实现如下:
static apr_status_t find_addresses(apr_sockaddr_t **sa,
const char *hostname, apr_int32_t family,
apr_port_t port, apr_int32_t flags,
apr_pool_t *p)
{
if (flags & APR_IPV4_ADDR_OK) {
apr_status_t error = call_resolver(sa, hostname, AF_INET, port, flags, p);
#if APR_HAVE_IPV6
if (error) {
family = AF_INET6; /* try again */ u
}
else
#endif
return error;
}
#if APR_HAVE_IPV6
else if (flags & APR_IPV6_ADDR_OK) {
apr_status_t error = call_resolver(sa, hostname, AF_INET6, port, flags, p);
if (error) { v
family = AF_INET; /* try again */
}
else {
return APR_SUCCESS;
}
}
#endif
return call_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中的宏处理比较的多,因此我们将分开描述:
static apr_status_t call_resolver(apr_sockaddr_t **sa,
const char *hostname, apr_int32_t family,
apr_port_t port, apr_int32_t flags,
apr_pool_t *p)
{
struct addrinfo hints, *ai, *ai_list;
apr_sockaddr_t *prev_sa;
int error;
char *servname = NULL;
memset(&hints, 0, sizeof(hints));
hints.ai_family = family;
hints.ai_socktype = SOCK_STREAM;
#ifdef HAVE_GAI_ADDRCONFIG
if (family == APR_UNSPEC) {
hints.ai_flags = AI_ADDRCONFIG;
}
#endif
在了解上面的代码之前我们首先简要的了解一些getaddrinfo函数的用法,该函数定义如下:
int getaddrinfo(const char *hostname, const char *service, const struct addinfo
*hints,struct addrinfo **result);
hostname是需要进行地址解析的主机名称或者是二进制的地址串(IPV4的点分十进制或者Ipv6
的十六进制数串),service则是一个服务名或者是一个十进制的端口号数串。其中hints是
addfinfo结构,该结构定义如下:
struct addrinfo {
int ai_flags; /* AI_PASSIVE, AI_CANONNAME,
AI_NUMERICHOST */
int ai_family; /* PF_xxx */
int ai_socktype; /* SOCK_xxx */
int ai_protocol; /* 0 or IPPROTO_xxx for IPv4 and IPv6 */
size_t ai_addrlen; /* length of ai_addr */
char *ai_canonname; /* canonical name for nodename */
struct sockaddr *ai_addr; /* binary address */
struct addrinfo *ai_next; /* next structure in linked list */
};
hints参数可以是一个空置针,也可以是一个指向某个addrinfo结构的指针,调用者在该结构
中填入关于 期望返回的信息类型的暗示,这些暗示将控制内部的转换细节。比如,如果指定的
服务器既支持TCP,也支持UDP,那么调用者可以把hints结构中的 ai_socktype成员设置为
SOCK_DGRAM,使得返回的仅仅是适用于数据报套接口的信息。
hints结构中调用者可以设置的成员包括ai_flags,ai_family,ai_socktype和ai_protocol。
其中,ai_flags成员可用的标志值及含义如下:


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) {
#ifdef AI_PASSIVE
hints.ai_flags |= AI_PASSIVE;
#endif
#ifdef OSF1
hostname = family == AF_INET6 ? "::" : "0.0.0.0";
servname = NULL;
#ifdef AI_NUMERICHOST
hints.ai_flags |= AI_NUMERICHOST;
#endif
#else
#ifdef _AIX
if (!port) {
servname = "1";
}
else
#endif /* _AIX */
servname = apr_itoa(p, port);
#endif /* OSF1 */
}
#ifdef HAVE_GAI_ADDRCONFIG
if (error == EAI_BADFLAGS && family == APR_UNSPEC) {
hints.ai_flags = 0;
error = getaddrinfo(hostname, servname, &hints, &ai_list);
}
#endif
if (error) {
#ifndef WIN32
if (error == EAI_SYSTEM) {
return errno;
}
else
#endif
{
#if defined(NEGATIVE_EAI)
error = -error;
#endif
return error + APR_OS_START_EAIERR;
}
}

2008年12月4日星期四

13.1 server side programming(急用,先写)

A typical server process opens a listen port, and listen to the port for any client process to connect. Then, it accepts the client's connection, and communicate with the client using their network protocol. Although the network protocol depends on your application, the basic structure of server code is almost same.
典型的服务器进程是打开一个端口,然后为客户端连接侦听它。这样我们就可以接受客户端连接,和客户端进行各种网络协议的通信。尽管客户端应用各种网络协议,但最基本的服务器代码还是差不多的。

At first, we have to create a socket address object, apr_sockaddr_t. In traditional socket programming, socket address structure would cause confusion. In contrast, libapr's socket address structure is simple. It can hide chaos among various platforms and IPv4/IPv6 stacks. We can create the object by apr_sockaddr_info_get(). The prototype declaration is as follows:
首先,我们必须要创建一个socket地址对象,apr_sockaddr_t。socekt地址结构常常导致传统项目混乱,相比之下libapr的socket地址显得简单多了。它能隐藏不同平台,IPv4/IPv6之间细节的处理。我们能用 apr_sockaddr_info_get()创建一个对象,典型的用法如下:

/* excerpted from apr_network_io.h */

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);
The first argument is result argument. The second argument is a hostname, or an IP address. I'll describe it later. The third argument is address family. It is usually APR_INET. If you intend to use IPv6, please specify APR_INET6. The fourth argument is a port number. Server side program should specify the port number to listen. For example, if you're creating a Web server, you might have to specify number 80. As you will see, client side program specifies the port number of the remote server. So, if you're creating a Web browser, you might have to specify number 80, too. Note that you can set the port number to zero. If so, the system selects a port number, which is available. The fifth argument is flags. In most cases, it is 0. The final argument is memory pool to use.

第一个参数是返回结果;第二个参数是主机地址,或者是IP地址,在后面将详细说明。第三个参数是个地址家族,它常常是APR_INET。如果你使用IPv6,请使用APR_INET6;第四个参数是端口号码,服务器端是侦听端口号。例如,你创建一个Web服务器,应该使用端口80,因为Web浏览器默认使用端口80。注意你也可以使用端口0,这样的话,系统将自动分配一个可用的端口号;第五个参数是个标记,在大多数情况写0;最后一个参数是正在使用的内存池。

We are back to the second argument. As you will see in client side programming below, client program generally specifies the server(remote) hostname or IP address. In contrast, server program should specifies its local address, and it binds the socket address object to a socket by apr_socket_bind().

我们再回到第二个参数。在后面你将看到客户端程序怎么使用远程服务器主机名或者IP地址。这里,服务器端应该通过 apr_socket_bind()去指定socket地址对象。

What value is allowed as a local address? One choice is a solid address or hostname. Namely, if you are creating a yahoo server, you can specify "www.yahoo.com" or "66.94.230.38". In general, such values are supplied by configuration files. Second choice is loopback address, i.e. "127.0.0.1" or "localhost". It works and it's valid. However, in such a case, only a client process running on the same host can connect to the server. If your purpose is to allow only local processes to connect to your server, specifying loopback address is a right choice.
局部地址应该选取个什么样的值呢?第一个办法是固定IP地址,或者服务器名,例如创建一个yahoo服务器,可以选择“www.yahoo.com”或者"66.94.230.38"。在一般情况下,这样的值需要通过配置文件完成。第二个办法是使用有效的Loopback地址"127.0.0.1" or "localhost"(回环地址,这个名词特别扭),这样的话,只有运行在服务器上的程序才能连接到服务器。如果服务器程序的想法就是只让本机访问的话,这样做无可厚非。

The other choice is to specify NULL or APR_ANYADDR(="0.0.0.0"). They imply that the server will bind all network interfaces to a socket. Accordingly, in such a case, any client process can connect to the server via a solid address or loopback address. Internally, NULL is better than APR_ANYADDR. As a result, it's usually good to specify NULL as the second argument of apr_sockaddr_info_get(). There is one exception. When the server host is multihome host, i.e. it has multiple solid IP addresses, and you don't want some IP addresses available from remote hosts, you shouldn't specify NULL. You must bind solid IP addresses to the listening socket.

另外一个办法就是使用NULL或者APR_ANYADDR(="0.0.0.0")。它能让服务器绑定网络接口到socket。相应的,这种情况下,任何客户端都能访问服务器物理地址或者Loopback 地址。通常NULL比APR_ANYADDR要方便。我们常常使用NULL作为apr_sockaddr_info_get()的第二个参数。但是这里有个例外,当服务器是多IP的机器,我们又需要屏蔽其中一些IP段地址,我们只能绑定侦听的绑定的物理地址。

Next, we create a socket object, apr_socket_t. In traditional socket progamming, socket type is just integer, and it acts as a file descriptor. apr_socket_t is opaque type and it hides such the OS dependencies. We can create socket object by apr_socket_create(). The prototype declaration is as follows:

下面,创建一个socket对象,apr_socket_t。在传统项目中,socket类型是一个整形,或者像一个文件描述符。这里apr_socket_t是一个不透明的类型,并且隐藏了操作系统的属性。我们可以通过apr_socket_create()创建Socket对象,属性定义如下:
/* excerpted from apr_network_io.h */

APR_DECLARE(apr_status_t) apr_socket_create(apr_socket_t **new_sock,
int family, int type,
int protocol,
apr_pool_t *cont);


The first argument is result argument. The second argument is address family. It is same as one of apr_sockaddr_info_get()'s third argument. Later, we make a relation between socket address object and socket object. In which, if they have different address family, it fails. The third and fourth arguments are socket type and protocol type. In general, all you have to know is two combinations. One is SOCK_STREAM as type and APR_PROTO_TCP as protocol. The other is SOCK_DGRAM as type and APR_PROTO_UDP as protocol. The final argument is memory pool to use.
第一个参数是结果返回参数;第二个参数是地址家族,它和 apr_sockaddr_info_get()第三个参数作用相同,之后我们将建立Socket地址对象和Socket对象之间的关系。他们必须有相同的地址家族。第三个和第四个参数是Socket类型和通信协议类型。总之,开发者必须知道这两个类型。一通常,一个是SOCK_STREAM,另外一个是APR_PROTO_UDP。最后个参数是使用的内存池。

Now, let's take a look at server-sample.c. The following is a typical code of server side to create a TCP listening socket.
让我们看看 server-sample.c 。下面是服务器端建立TCP侦听的典型代码:
/* excerpted from server-sample.c, but I omitted error checks */

apr_sockaddr_t *sa;
apr_socket_t *s;

apr_sockaddr_info_get(&sa, NULL, APR_INET, DEF_LISTEN_PORT, 0, mp);
apr_socket_create(&s, sa->family, SOCK_STREAM, mp);
apr_socket_bind(s, sa);
apr_socket_listen(s, DEF_SOCKET_BACKLOG);

There are two application dependent constant numbers, DEF_LISTEN_PORT and DEF_SOCKET_BACKLOG. Don't care about them.
两个常量DEF_LISTEN_PORT 和DEF_SOCKET_BACKLOG,不用管他们。

You can find two new APIs, apr_socket_bind() and apr_socket_listen(). By calling apr_socket_bind(), we can make a relation between socket address object(apr_sockaddr_t) and socket object(apr_socket_t). We call it binding the address to the socket. Then, by calling apr_socket_listen(), we change the socket's status to listening. It means we allow the socket to accept connections from remote clients. The second argument of apr_socket_listen() is length of the internal queue. The queue is a waiting queue of remote clients. They wait in the queue in (OS)kernel until the application accepts them. The length is historically called backlog. If you are not sure about proper value, I suggest you to use 'SOMAXCONN', which is a system default max value.
介绍apr_socket_bind() 和 apr_socket_listen()这两个函数,apr_socket_bind() 这个函数负责建立Socket地址对象和Socket对象之间的关系。apr_socket_listen()改变Socket的状态,使得Socket可以处理远程客户端连接请求。apr_socket_listen()的第二个参数是远程客户端请求的队列长度,这个队列由操作系统维护,直到应用程序处理他们。这个长度代表了能够积压的最大限度。如果程序没有特别要求,建议使用SOMAXCONN,它表示才做系统默认的最大值。

Next, we have to handle new connections from remote clients. At first, we have to call apr_socket_accept().
接着我们将处理远程客户端的请求。首先,使用apr_socket_accept()这个函数。

/* excerpted from server-sample.c, but I omit error checks */

apr_socket_t *ns;/* accepted socket */
apr_socket_accept(&ns, s, mp);

The second argument of apr_socket_accept() is the listening socket that we create above. Here, we get another socket object, named 'ns', accepted socket. From socket object creation's perspective, apr_socket_accept() is similar to apr_socket_create(). Namely, apr_socket_accept() also creates a new socket object. The first argument is result argument. Note that the newly created socket object is completely different from the listening socket object, which is passed as second argument to apr_socket_accept(). After returing from apr_socket_accept(), the listening socket is still just listening socket. It means we can continue to call apr_socket_accept(), and when the other remote client connects to the server, we're going to have a yet another socket object from apr_socket_accept(). After apr_socket_accept(), we have to handle two sockets independently. In general, we have the listening socket keep listening, and we have the accepted socket talk network protocol with the remote client. In server-sample.c, we send a simple HTTP request and receive the response.

apr_socket_accept() 第二个参数是我们之前创建的侦听Socket。apr_socket_accept()和 apr_socket_create()相似,第一个参数也是返回结果,创建了新的Socket对象。值得注意的是这个新的Socket和apr_socket_accept() 第二个参数是两个完全不同的Socket对象。apr_socket_accept() 第二个参数仅仅是个侦听Socket,需要继续去侦听新的客户端请求。而新的Socket对象将和已经相应的请求进行通话,他处理请求和发回响应。

To send and receive using socket, we call apr_socket_send() and apr_socket_recv(). Their prototype declarations are as follows:
接受请求和发回响应,需要apr_socket_send() and apr_socket_recv()这两个函数,他们的声明如下:
/* excerpted from apr_network_io.h */

APR_DECLARE(apr_status_t) apr_socket_send(apr_socket_t *sock, const char *buf,
apr_size_t *len);
APR_DECLARE(apr_status_t) apr_socket_recv(apr_socket_t *sock,
char *buf, apr_size_t *len);


They are similar to apr_file_read() and apr_file_write() described above. The first and second argumnts are needless to explain. The third argument of both is value-result argument. By which, we specify the input buffer's length on entry and get the output result's length on exit. apr_socket_send() returns the length of sent bytes. apr_socket_recv() returns the length of received bytes.
这两个函数和之前的 apr_file_read(), apr_file_write() 两个函数相似,第一和第二个参数不需要解释了,第三个参数同时是输入和返回结果。 先指定Socket对象,缓冲,缓冲长度,然后得到结果长度。apr_socket_send()返回的是发送字节数。apr_socket_recv()得到的是接受字节数。

2. libapr skeleton code

I believe it is a good idea to write 'skeleton code' at first, when you start to learn a new library or a new framework programming. 'skeleton code' is the smallest source code, but you can compile and execute it (although it usually do nothing useful).

当我们学新的LIB或者框架时,龙骨代码是个不错的点子,这玩意包含了代码的最小单元,但是却不做任何事情。

Fortunately, libapr's skeleton code is much simpler than other modern frameworks. Let's take a look at apr-skeleton.c. We call apr_initialize() at the initialization, and call apr_terminate() at the finalization. That's all. As you can imagine, the code does nothing.

运气不错,APR的龙骨代码比起其他框架非常精炼,打开apr-skeleton.c,

int main(int argc, const char *argv[])
{
apr_status_t rv;

/* per-process initialization */
rv = apr_initialize();
if (rv != APR_SUCCESS) {
assert(0);
return -1;
}

/* application impl. */

apr_terminate();
return 0;
}


调用apr_initialize()进行初始化,调用apr_terminate()完成,就这些,也正如你所想,这段代码什么也没做。

Here, we have some libapr programming styles and rules:

* naming rule is very simple and clear.
* opaque data types are commonly used (a.k.a. incomplete types)
* most of return types are apr_status_t. As a result, result-arguments are commonly used.
* memory pool rules

我们给出一些libapr编程的风格和规则:
* 命名规则非常简单和清楚。
* 不透明类型被广泛地使用。(又名不完整类型)
* 返回结果或是结果输出参数大多数返回类型为apr_status_t。
* 内存池规则。

We are able to see these styles in the following code.

/* excerpted from mp-sample.c */

apr_status_t rv;
apr_pool_t *mp;
rv = apr_pool_create(&mp, NULL);

I will describe the meaning of the code. Here, please take a look at only style. You can see apr_ prefix. The apr_ prefix indicates that the symbol is in libapr naming scope. You can see _t suffix. It indicates that the symbol is a type name.

我们经常能看见以下这种代码:
/* excerpted from mp-sample.c */

apr_status_t rv;
apr_pool_t *mp;
rv = apr_pool_create(&mp, NULL);
apr_为前缀表示是LIBAPR的代码,_t表示是一种抽象类型,多数都是结构体。

apr_pool_t is opaque type. It means the type's structure is not public. By OO(Object Oriented) terminology, all member variables are private. You can't touch them directly. Furthermore, you can't see them in public header files. All you can do for the type is to call APIs such as apr_foo_bar() functions. Most importantly, you can't allocate their instance memories directly. All you can do is to call construct APIs. Only libapr knows how to construct and destruct the objects.
apr_pool_t是一个分装的类,这意味着类型的结构体不是公开的,根据OO(面向对象)的思维,所有成员变量都是私有的。我们不能直接操作他们,此外,我们也没必要去关心公共头文件。例如我们会用到名字像apr_foo_bar()这样的函数,要注意的是,我们并不需要关心怎么去给他们分配内存,当我们需要调用这些API的时候,libapr知道怎么去构造和销毁他们。

As you see, apr_pool_create()'s return type is apr_status_t. apr_status_t is either status code or error code. apr_status_t is a commonly used as return types of most APIs. Accordingly, we can get results from functions by arguments. Such arguments are called result-argument. There are many result-arguments in the libapr world.
正如你所见,apr_pool_create()返回类型是 apr_status_t, apr_status_t是一种状态码和错误码。 apr_status_t是最常用的返回类型。相应的,我们能通过各种参数调用函数来得到各种类型的结果。当然,libapr也使用了相当多的返回参数。

In general, if you see apr_foo_t type, you will see apr_foo_bar() functions, which are related to apr_foo_t type. The following code is a typical pseudo code.

/* pseudo code of libapr. error checks omitted */
apr_status_t rv;
apr_foo_t *foo;
rv = apr_foo_create(&foo, args...);/* create a @foo object by @args */
rv = apr_foo_do_something(foo, args...); /* do something with @foo */
apr_foo_destroy(foo);

一般情况下,如果看见apr_foo_t类型,一般会有apr_foo_bar()方法,他使用到apr_foo_t类型,如这种很典型的代码。

1. Tutorial Availability

Please look for updates on http://dev.ariel-networks.com/apr/.

转载出自

http://dev.ariel-networks.com/apr/


该系列均是出自这里,后面的内容不再特殊说明