您的位置:首页 > 理论基础

深入理解计算机系统英文版(从672页开始翻译-要做实验没办法)

2011-04-01 20:00 302 查看
12.8 Putting it Together: TheTINYWeb
Server

We will conclude our discussion of network programming by developing a small but functioning Web
servercalled TINY. TINYis an interesting program. It combines
many of the ideas that we have learned about concurrency, Unix I/O, the sockets interface, and HTTP in only 250 lines of code. While it lacks the functionality, robustness, and security of a real server, it is powerful enough to serve both static and dynamic
content to real Web browsers. We encourage you to study it and implement it yourself. It is quite exciting (even for the authors!) to point a real browser at your own server and watch it display a complicated Web
page with text and graphics.
我们将通过开发一个叫做tiny的小但是起作用的web服务,来理解网络编程。Tiny是一个有趣的程序。它包含了许多我们已经一致了解的思路,Unix
I/O,sockets接口,还有HTTP,这些包含在仅仅250行的代码中。尽管它缺乏一个真正的服务的实用性,健壮性,和安全性,它仍然足够给力,给真正的web浏览器提供静态和动态的内容服务。我们鼓励你自己学习它,运用它。它足够令人兴奋(即使对于作者来说!)让浏览器指向你自己的服务,并且观看它展示一个复杂的包含文本和图片的网页。

code/net/tiny/cgi-bin/adder.c
1#include "csapp.h"
2
3int main(void) {
4char *buf, *p;
5char arg1[MAXLINE], arg2[MAXLINE], content[MAXLINE];
6int n1=0, n2=0;
7
8/* extract the two arguments */
9if ((buf = getenv("QUERY_STRING")) != NULL) {
10p = strchr(buf, ’&’);
11*p = ’/0’;
12strcpy(arg1, buf);
13strcpy(arg2, p+1);
14n1 = atoi(arg1);
15n2 = atoi(arg2);
16}
17
18/* make the response body */
19sprintf(content, "Welcome to add.com: ");
20sprintf(content, "%sTHE Internet addition portal./r/n<p>", content);
21sprintf(content, "%sThe answer is: %d + %d = %d/r/n<p>",
22content, n1, n2, n1 + n2);
23sprintf(content, "%sThanks for visiting!/r/n", content);
24
25/* generate the HTTP response */
26printf("Content-length: %d/r/n", strlen(content));
27printf("Content-type: text/html/r/n/r/n");
28printf("%s", content);
29fflush(stdout);
30exit(0);
31}

1unix> telnet kittyhawk.cmcl.cs.cmu.edu 8000Client:
open connection
2Trying 128.2.194.242...
3Connected to kittyhawk.cmcl.cs.cmu.edu.
4Escape character is ’ˆ]’.
5GET /cgi-bin/adder?15000&213 HTTP/1.0Client:
request line
6 Client: empty line terminates headers
7HTTP/1.0 200 OK
Server: response line
8Server: Tiny Web Server
Server: identify server
9Content-length: 115
Adder: expect 115 bytes in response body
10Content-type: text/html
Adder: expect HTML in response body
11 Adder: empty line terminates headers
12Welcome to add.com: THE Internet addition portal.Adder:
first HTML line
13<p>The answer is: 15000 + 213 = 15213Adder:
second HTML line in response body
14<p>Thanks for visiting!
Adder: third HTML line in response body
15Connection closed by foreign host.Server:
closes connection
16unix>
Client: closes connection and terminates

Figure 12.45:An HTTP transaction that serves dynamic HTML content


TheTINYmain
Routine
Figure 12.46 shows TINY’s
main routine. TINY is an iterative server that listens for connection requests on the port that is passed in the command line. After opening a listening socket
(line 28) by calling the open_listenfdfunction from Figure 12.46, TINYexecutes
the typical infinite server loop, repeatedly accepting a connection request (line 31) and performing a transaction (line 32).
Figure 12.46显示了tiny的主函数。Tiny是一个持续的服务,监听来自命令行设置的端口的请求。通过调用来自Figure
12.4的open_listenfd函数来打开一个正在监听的socket
(28行)。Tiny执行了不间断的server循环,不停的接受一个个连接请求,并且实现交互。

Thedoit
Function
Thedoit
function in Figure 12.47 handles one HTTP transaction. First, we read and parse the request line(lines 9-10). Notice that we are using the robust
readlinefunction from Figure 12.16 to read the request line.
Figure 12.47的doit函数处理了一个HTTP交互。首先,我们读取并且转换这个请求行(request
line)。请注意,我们一直使用健壮的来自Figure 12.16readline函数来读取request
line
TINYonly
supports the GET method. If the client requests another method (such as POST), we send it an error message and return to the main routine (lines 11-15), which then closes the connection and awaits the next connection request. Otherwise, we read and (as we
shall see) ignore any request headers (line 16).
Tiny仅仅支持GET方法。如果客户端使用其他方法(例如POST)请求服务,我们将发送一个错误消息,并且等待下一次连接请求。另外,我们忽视并且(我们应该看到)忽视任何的request
headers
Next, we parse the URI into a filename and a possibly empty CGI argument string, and we set a flag
that indicates whether the request is for static or dynamic content (line 19). If the file does not exist on disk, we immediately send an error message to the client and return (lines 20-24).
另外,我们将URI(统一资源标识符)转换成一个文件名和一个可能的空的CGI参数字符串,而且,我们会设置一个标志,它指出了这个请求是为了静态的还是动态的内容。如果这个文件在磁盘上不存在,我们立刻会发送一个错误消息给客户端并且返回(l20-24行).
Finally, if the request is for static content (lines 26), we verify that the file is a regular
file (i.e., not a directory file or a FIFO) and that we have read permission (line 27). If so, we serve the static content (line 32) to the client. Similarly, if the request is for dynamic content (line 34), we verify that the file is executable (line 35),
and if so we go ahead and serve the dynamic content (line 40).
最后,如果这个请求是为了静态内容(26行),我们将核实这个文件是一个正常的文件(不是一个路径文件或者一个FIFO,等等)而且我们有读取权限。如果都符合了,我们给客户端提供静态内容服务。同样的,如果这个请求是为了动态内容(34行),我们将核实这个文件是可执行的,而且符合的话,我们会开始提供动态内容服务。

1/*
2* tiny.c - A simple HTTP/1.0 Web server that uses the GET method
3* to serve static and dynamic content.
4*/
5#include "csapp.h"
6
7void doit(int fd);
8void read_requesthdrs(int fd);
9int parse_uri(char *uri, char *filename, char *cgiargs);
10void serve_static(int fd, char *filename, int filesize);
11void get_filetype(char *filename, char *filetype);
12void serve_dynamic(int fd, char *filename, char *cgiargs);
13void clienterror(int fd, char *cause, char *errnum,
14char *shortmsg, char *longmsg);
15
16int main(int argc, char **argv)
17{
18int listenfd, connfd, port, clientlen;
19struct sockaddr_in clientaddr;
20
21/* check command line args */
22if (argc != 2) {
23fprintf(stderr, "usage: %s <port>/n", argv[0]);
24exit(1);
25}
26port = atoi(argv[1]);
27
28listenfd = open_listenfd(port);
29while (1) {
30clientlen = sizeof(clientaddr);
31connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
32doit(connfd);
33Close(connfd);
34}
35}

Figure 12.46:The
TINY Web server

1void doit(int fd)
2{
3int is_static;
4struct stat sbuf;
5char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
6char filename[MAXLINE], cgiargs[MAXLINE];
7
8/* read request line and headers */
9Readline(fd, buf, MAXLINE);
10sscanf(buf, "%s %s %s/n", method, uri, version);
11if (strcasecmp(method, "GET")) {
12clienterror(fd, method, "501", "Not Implemented",
13"Tiny does not implement this method");
14return;
15}
16read_requesthdrs(fd);
17
18/* parse URI from GET request */
19is_static = parse_uri(uri, filename, cgiargs);
20if (stat(filename, &sbuf) < 0) {
21clienterror(fd, filename, "404", "Not found",
22"Tiny couldn’t find this file");
23return;
24}
25
26if (is_static) { /* serve static content */
27if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) {
28clienterror(fd, filename, "403", "Forbidden",
29"Tiny couldn’t read the file");
30return;
31}
32serve_static(fd, filename, sbuf.st_size);
33}
34else { /* serve dynamic content */
35if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) {
36clienterror(fd, filename, "403", "Forbidden",
37"Tiny couldn’t run the CGI program");
38return;
39}
40serve_dynamic(fd, filename, cgiargs);
41}
42}
code/net/tiny/tiny.c
Figure 12.47: TINYdoit:
Handles one HTTP transaction.


TheclienterrorFunction
TINYlacks
many of the robustness features of a real server. However it does check for some obvious errors and reports them to the client. Theclienterror
function in Figure 12.48 sends an HTTP response to the client with the appropriate status code and status message in the response line, along with an HTML file in the
response body that explains the error to the browser’s user.
Tiny缺乏一个真正的服务的很多方面的健壮性。即使这样,它也检查了许多明显的错误,并且将它们报告给客户端。这个Figure
12.48 的clienterror函数发送了一个HTTP
response给客户端,包含了合适的状态码和状态消息在response line中,与response
body中的html文件一起向浏览器使用者解释了一个错误。
code/net/tiny/tiny.c
1void clienterror(int fd, char *cause, char *errnum,
2char *shortmsg, char *longmsg)
3{
4char buf[MAXLINE], body[MAXBUF];
5
6/* build the HTTP response body */
7sprintf(body, "<html><title>Tiny Error</title>");
8sprintf(body, "%s<body bgcolor=""ffffff"">/r/n", body);
9sprintf(body, "%s%s: %s/r/n", body, errnum, shortmsg);
10sprintf(body, "%s<p>%s: %s/r/n", body, longmsg, cause);
11sprintf(body, "%s<hr><em>The Tiny Web server</em>/r/n", body);
12
13/* print the HTTP response */
14sprintf(buf, "HTTP/1.0 %s %s/r/n", errnum, shortmsg);
15Writen(fd, buf, strlen(buf));
16sprintf(buf, "Content-type: text/html/r/n");
17Writen(fd, buf, strlen(buf));
18sprintf(buf, "Content-length: %d/r/n/r/n", strlen(body));
19Writen(fd, buf, strlen(buf));
20Writen(fd, body, strlen(body));
21}
code/net/tiny/tiny.c
Figure 12.48: TINYclienterror:
Sends an error message to the client.


Recall that an HTML response should indicate the size and type the content in the body. Thus, we
have opted to build the HTML content as a single string (lines 7-11) so that we can easily determine its size (line 18). Also, notice that we are using the robustwriten
function from Figure 12.15 for all output.
回想一下,一个HTML回复应该指明body中内容的大小和类型。因此,我们选择创建一个HTML
content在一个单独的字符串中(7-11行),这样我们能够容易的指导它的大小(18行)。同时,注意到这个,我们一直为所有的输出使用健壮的writen方法


Theread requesthdrsFunction
TINYdoes
not use any of the information in the request headers. It simply reads and ignores them by calling theread_requesthdrs
function in Figure 12.49. Notice that the empty text line that terminates the request headers consists of a carriage return and line feed pair, which we check for in
line 6.
Tiny没有在request
headers中使用任何信息。它通过调用read_requesthdrs函数简单的读取并且忽视它们(Figure
12.49)。注意到,由回车和换行字符组成的空行终结了request headers部分。
658 CHAPTER 12. NETWORK PROGRAMMING
code/net/tiny/tiny.c
1void read_requesthdrs(int fd)
2{
3char buf[MAXLINE];
4
5Readline(fd, buf, MAXLINE);
6while(strcmp(buf, "/r/n"))
7Readline(fd, buf, MAXLINE);
8return;
9}
code/net/tiny/tiny.c
Figure 12.49: TINYread
requesthdrs: Reads and ignores request headers.

Theparse uriFunction
TINYassumes
that the home directory for static content is the current Unix directory’.’, and that the home directory
for executables is./cgi-bin. Any URI that contains the stringcgi-bin
is assumed to denote a request for dynamic content. The default file name is./home.html.
Tiny假设静态内容的主路径是Unix当前路径’.’,而可执行文件的主路径是./cgi-bin。任何包含cgi-bin字符串的URI都会被认为是对动态内容的请求。默认的文件名是./home.html.
Theparse_uri
function in Figure 12.50 implements these policies. It parses the URI into a filename and an optional CGI argument string. If the request is for static content (line
5) we clear the CGI argument string (line 6), and then convert the URI into a relative Unix pathname such as./index.html
(lines 7-8). If the URI ends with a’/’
character (line 9), then we append the default file name (lines 9). On the other hand, if the request is for dynamic content (line 13), we extract any CGI arguments (line
14-20) and convert the
remaining portion of the URI to a relative Unix file name (lines 21-22).
Figure 12.50的parse_uri函数实现了如下策略。它将URI转换成一个文件名,和一个可以选择的CGI参数字符串。如果请求是为了静态的内容(5行),我们清空CGI字符串(6行),然后将URI转换成一个相关的UNIX路径名,例如./index.html(7-8行)。如果URI以字符’/’结束(9行),那么我们将加上默认的文件名(9行)。另一方面,如果请求是为了动态内容(13行),我们将提取CGI参数(14-20行),并且将URI剩余的部分转换成相关的Unix文件名(21-22行)。

Theserve staticFunction
TINYserves
4 different types of static content: HTML files, unformatted text files, and images encoded in GIF and JPG formats. These file types account for the majority of static content served over the Web.
Tiny提供了四个不同类型的静态文件:HTML文件,无格式文本文件,GIF和JPG格式编码的图像文件。这些文件类型组成了在web上提供服务的主要的静态内容。
Theserve static
function in Figure 12.51 sends an HTTP response whose body contains the contents of a local file. First, we determine the file type by inspecting the suffix in the filename
(line 7), and then send the response line and response headers to the client (lines 6-12). Notice that we are using thewriten
function from Figure 12.15 for all output on the descriptor. Notice also that a blank line terminates the headers (line 12).
Figure 12.51的Serve_static函数发送了一个HTTP
response,它的body包含了本地文件的内容。首先,我们通过查看文件名后缀(7行)检测文件类型,然后发送一个response
line 和response headers给客户端(6-12行)。请注意,我们一直使用Figure
12.15的writen函数来执行所有的输出。还要注意到,一个空行结束了headers。
Next, we send the response body by copying the contents of the requested file to the connected
descriptor fd
(lines 15-19). The code here is somewhat subtle and needs to be studied carefully.
然后,我们发送response body通过复制所请求的文件的内容给连接描述符fd(15-19行)。这儿的代码是充满玄机的,需要仔细学习。
Line 15 opensfilename
for reading and gets its descriptor. In line 16, the Unixmmap
function maps the requested file to a virtual memory area. Recall from our discussion ofmmap
in Section 10.8 that the call tommap
maps the firstfilesize
bytes of filesrcfd
to a private read-only area of virtual memory that starts at addresssrcp.
第15行打开了文件filename来读取,并获得它的描述符。在第16行中,Unix
mmap函数映射了所请求的文件到虚拟内存空间。记起我们曾在10.8章讨论过的mmap,调用mmap将文件srcfd的第一个filesize比特大小的内容映射到只读的开始于地址srcp的虚拟内存区域。
Once we have mapped the file to memory, we no longer need its descriptor, so we close the file
(line 17).
一旦我们将文件映射到内存,我们不再需要它的描述符,这样我们就可以关闭文件(17行)。

12.8. PUTTING IT TOGETHER: THE TINY WEB SERVER 659
code/net/tiny/tiny.c
1int parse_uri(char *uri, char *filename, char *cgiargs)
2{
3char *ptr;
4
5if (!strstr(uri, "cgi-bin")) { /* static content */
6strcpy(cgiargs, "");
7strcpy(filename, ".");
8strcat(filename, uri);
9if (uri[strlen(uri)-1] == ’/’)
10strcat(filename, "home.html");
11return 1;
12}
13else { /* dynamic content */
14ptr = index(uri, ’?’);
15if (ptr) {
16strcpy(cgiargs, ptr+1);
17*ptr = ’/0’;
18}
19else
20strcpy(cgiargs, "");
21strcpy(filename, ".");
22strcat(filename, uri);
23return 0;
24}
25}
code/net/tiny/tiny.c
Figure 12.50: TINYparse
uri: Parses an HTTP URI.
660 CHAPTER 12. NETWORK PROGRAMMING
code/net/tiny/tiny.c
1void serve_static(int fd, char *filename, int filesize)
2{
3int srcfd;
4char *srcp, filetype[MAXLINE], buf[MAXBUF];
5
6/* send response headers to client */
7get_filetype(filename, filetype);
8sprintf(buf, "HTTP/1.0 200 OK/r/n");
9sprintf(buf, "%sServer: Tiny Web Server/r/n", buf);
10sprintf(buf, "%sContent-length: %d/n", buf, filesize);
11sprintf(buf, "%sContent-type: %s/r/n/r/n", buf, filetype);
12Writen(fd, buf, strlen(buf));
13
14/* send response body to client */
15srcfd = Open(filename, O_RDONLY, 0);
16srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
17Close(srcfd);
18Writen(fd, srcp, filesize);
19Munmap(srcp, filesize);
20}
21
22/*
23* get_filetype - derive file type from file name
24*/
25void get_filetype(char *filename, char *filetype)
26{
27if (strstr(filename, ".html"))
28strcpy(filetype, "text/html");
29else if (strstr(filename, ".gif"))
30strcpy(filetype, "image/gif");
31else if (strstr(filename, ".jpg"))
32strcpy(filetype, "image/jpg");
33else
34strcpy(filetype, "text/plain");
35}
code/net/tiny/tiny.c
Figure 12.51: TINYserve
static: Serves static content to a client.

12.8. PUTTING IT TOGETHER: THE TINY WEB SERVER 661
Failing to do this would introduce a potentially fatal memory leak.
做这个失败的话,将会导致一个潜在的致命的内存溢出。
Line 18 performs the actual transfer of the file to the client. Thewriten
function copies thefilesize
bytes starting at locationsrcp
(which of course is mapped to the requested file) to the client’s connected descriptor. Finally, line 19 frees the mapped virtual memory area. This is important to avoid
a potentially fatal memory leak.
第18行表现了文件到客户端实际的传输。Writen函数复制了开始于位置srcp(这个course代表了被请求的文件)的filesize比特的内容给客户的连接描述符。最后,第19行释放了映射的虚拟内存空间。这对于避免潜在的致命的内存溢出是至关重要的。

Theserve dynamicFunction
TINYserves
any type of dynamic content by forking a child process, and then running a CGI program in the context of the child.
Tiny提供一些类型的动态内容,通过创建一个子进程,然后在子进程的内容中运行一个CGI程序。
Theserve dynamic
function in Figure 12.52 begins by sending a response line indicating success to the client (lines 6-7), along with an informationalServer
header (lines 8-9). The CGI program is responsible for sending the rest of the response. Notice that this is not as robust as we might wish, since it doesn’t allow for
the possibility that the CGI program might encounter some error.
Figure 12.52的Serve_dynamic函数通过发送一个指示成功的response
line给客户开始(6-7行),和包含一些信息的server
headers(8-9行)。这个CGI程序对于发送剩下的response是重要的。请注意,这不是像我们希望的那样健壮,它不允许CGI程序出错的可能性。
code/net/tiny/tiny.c
1void serve_dynamic(int fd, char *filename, char *cgiargs)
2{
3char buf[MAXLINE];
4
5/* return first part of HTTP response */
6sprintf(buf, "HTTP/1.0 200 OK/r/n");
7Writen(fd, buf, strlen(buf));
8sprintf(buf, "Server: Tiny Web Server/r/n");
9Writen(fd, buf, strlen(buf));
10
11if (Fork() == 0) { /* child */
12/* real server would set all CGI vars here */
13setenv("QUERY_STRING", cgiargs, 1);
14Dup2(fd, STDOUT_FILENO); /* redirect output to client */
15Execve(filename, NULL, environ); /* run CGI program */
16}
17Wait(NULL); /* parent reaps child */
18}
code/net/tiny/tiny.c
Figure 12.52: TINYserve
dynamic: Serves dynamic content to a client.

After sending the first part of the response, we fork a new child process (line 11). The child
initializes the QUERY STRING environment variable with the CGI arguments from the request URI (line 13). Notice that a real server would set the other CGI environment variables here as well. For brevity, we have omitted this step.
发送了response的第一部分之后,我们创建了一个新的子进程(11行)。这个子进程用来自request
URI的CGI参数初始化了QUERY
STRING环境变量(13行)。请注意,一个真正的服务也会在这设置其他的CGI环境变量。简单的说,我们省略了这个步骤。
Next, the child redirects the child’s standard output to the connected file descriptor (line 14),
and then loads and runs the CGI program (line 15). Since the CGI program runs in the context of the child, it has access to the same open descriptors and environment variables that existed before the call to theexecve
function. Thus, everything that the CGI program writes to standard output goes directly to the client process, without any intervention from the parent process.
接下来,这个子进程重定向了子进程标准输出到连接的文件的描述符(14行)。然后装载并运行CGI程序(15行)。CGI程序运行在子进程的上下文环境后,在调用execve函数之前,它访问了相同的打开描述符和存在的环境变量。因此,CGI程序写到标准输出的所有东西都会定向到client进程中,父进程对此没有任何干涉。
Meanwhile, the parent blocks in a call towait,
waiting to reap the child when it terminates (line 17).
同时,父进程被阻塞了只能等待,等待子进程结束时,重新运行(17行)。
Practice Problem 12.8:
A. Is the TINYdoit
routine reentrant? Why or why not?
B. If not, how would you make it reentrant?

12.9 Summary
In this chapter we have learned some basic concepts about network applications. Network applications
use the client-server model, where servers perform services on behalf of their clients. The Internet provides network applications with two key mechanisms: (1) A unique name for each Internet host, and (2) a mechanism for establishing a connection to a server
running on any of those hosts. Clients and servers establish connections by using the sockets interface, and they communicate over these connections using standard Unix file I/O functions.
在本章中,我们学习了网络程序的一些基本概念。网络程序使用客户端-服务器模型,服务器为它们的客户端提供服务。因特网提供网络应用程序通过两个主要的途径:(1)每台因特网主机一个唯一的名字,(2)一种运行在所有主机上的与服务器建立连接的机制。客户端和服务端通过使用sockets接口建立连接,而且他们通过连接使用标准Unix文件I/O函数通信。
There are two basic design options for servers. An iterative server handles one request at a time.
A concurrent server can handle multiple requests concurrently. We investigated two designs for concurrent servers, one that forks a new process for each request, the other that creates a new thread for each request. Other designs are possible, such as using
the Unix select
function to explicitly manage the concurrency, or avoiding the per-connection overhead by pre-forking a set of child processes to handle connection requests.
服务器有两个基本的设计选择。一个迭代的服务处理每个请求一次。一个并发的服务能同时处理多个请求。我们为并发的服务调查了两种设计,一种设计给每个请求创建一个新的进程,另一种给每个请求创建一个新的线程。其他的设计也是可能的,例如使用Unix
select函数去明确的管理并发性,或者避免每个连接的管理费用,通过先创建一系列的子进程来处理连接请求。
Finally, we studied the design and implementation of a simple but functional Web server. In a few
lines of code, it ties together many important systems concepts such as Unix I/O, memory mapping, concurrency, the sockets interface, and the HTTP protocol.
最后,我们学习了这些设计并实现一个简单但起作用的web服务。在很少的代码中,它包含了许多重要的系统概念,例如Unix
I/O,内存映射,并发性,sockets接口,和HTTP协议。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: