📄 apr-tutorial.sgml
字号:
/* excerpted from <htmlurl url="../sample/dso-sample.c" name="dso-sample.c">, but I omitted error checks */<tscreen><verb>typedef double (*pow_fn_t)(double x, double y);pow_fn_t pow_fn;/* seek pow(3) function from libm.so */apr_dso_sym((apr_dso_handle_sym_t*)&pow_fn, dso_h, "pow");/* call pow(3) */printf("%d ^ %d = %f\n", 2, 2, pow_fn(2, 2));</verb></tscreen>If your program has plugins, you have to define the symbol names and their interfaces. Then, plugin developers must follow them.Finally, we call apr_dso_unload() to release the loadable module. It could reduce memory consumption.<sect>Network programming<p>libapr provides APIs to support network programming. The APIs are based on traditional socket programming scheme. If you are familiar with bind(2), listen(2), accept(2) and connect(2), you can easily understand it. I show you how to use the APIs with three categories, server side progamming, client side progaramming, and multiplex process programming.<sect1>server side programming<p>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:/* excerpted from apr_network_io.h */<tscreen><verb>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);</verb></tscreen>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.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(). 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. 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.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:/* excerpted from apr_network_io.h */<tscreen><verb>APR_DECLARE(apr_status_t) apr_socket_create(apr_socket_t **new_sock, int family, int type, int protocol, apr_pool_t *cont);</verb></tscreen>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.Now, let's take a look at <htmlurl url="../sample/server-sample.c" name="server-sample.c">. The following is a typical code of server side to create a TCP listening socket./* excerpted from <htmlurl url="../sample/server-sample.c" name="server-sample.c">, but I omitted error checks */<tscreen><verb>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);</verb></tscreen>There are two application dependent constant numbers, DEF_LISTEN_PORT and DEF_SOCKET_BACKLOG. Don't care about them. 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.Next, we have to handle new connections from remote clients. At first, we have to call apr_socket_accept()./* excerpted from <htmlurl url="../sample/server-sample.c" name="server-sample.c">, but I omit error checks */<tscreen><verb>apr_socket_t *ns;/* accepted socket */apr_socket_accept(&ns, s, mp);</verb></tscreen>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 <htmlurl url="../sample/server-sample.c" name="server-sample.c">, we send a simple HTTP request and receive the response. To send and receive using socket, we call apr_socket_send() and apr_socket_recv(). Their prototype declarations are as follows:/* excerpted from apr_network_io.h */<tscreen><verb>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);</verb></tscreen>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.We will discuss about return values of those APIs later. <sect1>client side programming<p>Comparing to server side programming, client side programming looks simpler. A new API to note is only one, apr_socket_connect()./* excerpted from apr_network_io.h */<tscreen><verb>APR_DECLARE(apr_status_t) apr_socket_connect(apr_socket_t *sock, apr_sockaddr_t *sa);</verb></tscreen>This is similar to apr_socket_bind() on those arguments list. Like apr_socket_bind(), apr_socket_connect() makes a relation between socket object and socket address object. The difference is that the socket address object is related to the remote server address. Let's take a look at <htmlurl url="../sample/client-sample.c" name="client-sample.c">. The following is a typical code of client side program./* excerpted from <htmlurl url="../sample/client-sample.c" name="client-sample.c">, but I omit error checks */<tscreen><verb>apr_sockaddr_t *sa;apr_socket_t *s;apr_sockaddr_info_get(&sa, DEF_REMOTE_HOST, APR_INET, DEF_REMOTE_PORT, 0, mp);apr_socket_create(&s, sa->family, SOCK_STREAM, mp);apr_socket_connect(s, sa);</verb></tscreen>The second and fourth arguments of apr_sockaddr_info_get() are remote server's IP address(or hostname) and port number. The others are needless to explain, because they are same as server side. apr_socket_create() is also needless to explain, because it's also same as server side. By calling apr_socket_connect(), we begin to connect to the remote server. I'll explain return values of these APIs.After connecting to the server, the client program talks network protocol with the server through the socket. As you can see in <htmlurl url="../sample/client-sample.c" name="client-sample.c">, the code is same as server side. We can use apr_socket_send() and apr_socket_recv().<sect1>multiplex processing (poll)<p>Go back to <htmlurl url="../sample/server-sample.c" name="server-sample.c"> again. It has a loop to keep calling apr_socket_accept(), so that the server process can accept multiple clients. However, it doesn't call apr_socket_accept() until it completes the session between one client. Obviously, when multiple clients connect to the server simultaneously, the server handles only one client at a moment. Such a server is normally useless. We need concurrent server. In general, there are three programming models to develop concurrent server; multi-process model, multi-threaded model, and multiplexing model. Here, we focus on the final one.Historically, multiplexing model relies on select(2) or poll(2) APIs, and the code is often based on event-driven style. The essential idea is that we check multiple sockets whether they are ready to read or write. Then, when we know some of the sockets are ready to read/write, we do actual read/write with them. In addition, we need non-blocking sockets for multiplex processing. I'll describe how to use non-blocking sockets in the next section.Let's take a look at <htmlurl url="../sample/pollset-sample.c" name="pollset-sample.c">. The creation of the listening socket is almost same as <htmlurl url="../sample/server-sample.c" name="server-sample.c">. The different is that we make it non-blocking. Then, we create a pollset obejct by apr_pollset_create(). pollset is a set of sockets to watch. The prototype declaration is as follows:/* excerted from apr_poll.h */<tscreen><verb>APR_DECLARE(apr_status_t) apr_pollset_create(apr_pollset_t **pollset, apr_uint32_t size, apr_pool_t *p, apr_uint32_t flags);</verb></tscreen>The first argument is result argument. The second argument is size of pollset. Unfortunately, pollset is not a dynamic-sized set. We have to specify the size of set at creation. The third argument is memory pool to use. The fourth argument is flag, but it's a reserved argument now.After creating pollset object, we can add sockets to the pollset by apr_pollset_add(). In the following example, we add the first and second sockets to the pollset to know whether the socket is ready to read. Then we remove a socket from the pollset because we are not interested in its readability. Finally, we add the third socket to know whether it is ready to write. /* excerpted from <htmlurl url="../sample/pollset-sample.c" name="pollset-sample.c"> */<tscreen><verb>/* we watch @lsock(listening socket) to know whether it is ready to read(APR_POLLIN) */apr_pollfd_t pfd = { mp, APR_POLL_SOCKET, APR_POLLIN, 0, { NULL }, NULL };pfd.desc.s = lsock;apr_pollset_add(pollset, &pfd);/* we watch @ns(connected socket) to know whether it is ready to read(APR_POLLIN) */apr_pollfd_t pfd = { mp, APR_POLL_SOCKET, APR_POLLIN, 0, { NULL }, serv_ctx };pfd.desc.s = ns;apr_pollset_add(pollset, &pfd);/* we are not interested in the socket's readability, so remove it from the pollset */apr_pollfd_t pfd = { serv_ctx->mp, APR_POLL_SOCKET, APR_POLLIN, 0, { NULL }, serv_ctx };pfd.desc.s = sock;apr_pollset_remove(pollset, &pfd);/* we watch @sock(connected socket) to know whether it is ready to write(APR_POLLOUT) */pfd.reqevents = APR_POLLOUT;apr_pollset_add(pollset, &pfd);</verb></tscreen>We need to look at apr_pollfd_t's definition. It is in apr_poll.h as follows:/* excerpted from apr_poll.h */<tscreen><verb>/** Union of either an APR file or socket. */typedef union { apr_file_t *f; /**< file */ apr_socket_t *s; /**< socket */} apr_descriptor;/** Poll descriptor set. */struct apr_pollfd_t { apr_pool_t *p; /**< associated pool */ apr_datatype_e desc_type; /**< descriptor type */ apr_int16_t reqevents; /**< requested events */ apr_int16_t rtnevents; /**< returned events */ apr_descriptor desc; /**< @see apr_descriptor */ void *client_data; /**< allows app to associate context */};</verb></tscreen>pollset is designed to work for files as same as for sockets, but we ignore files in this document. We set apr_pollfd_t::desc_type to APR_POLL_SOCKET, and we specify socket object as apr_pollfd_t::desc. apr_pollfd_t::reqevents and apr_pollfd_t::rtnevents have a relation, the former is input and the latter is output. The values are bit-wised flag of APR_POLLIN, APR_POLLOUT, etc. The following sample shows it.<tscreen><verb>/* if you want to know whether the socket is ready to read or write, you can specify the bit-wised flag as follows */apr_pollfd_t pfd = { mp, APR_POLL_SOCKET, APR_POLLIN|APR_POLLOUT, 0, { sock }, app_ctx };apr_pollset_add(pollset, &pfd);</verb></tscreen>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -