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()得到的是接受字节数。

没有评论: