深入理解計算機系統英文版(從672頁開始翻譯-要做實驗沒辦法)

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/Osockets接口,還有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.4open_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.47doit函數處理了一個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.50parse_uri函數實現瞭如下策略。它將URI轉換成一個文件名,和一個可以選擇的CGI參數字符串。如果請求是爲了靜態的內容(5行),我們清空CGI字符串(6行),然後將URI轉換成一個相關的UNIX路徑名,例如./index.html7-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文件,無格式文本文件,GIFJPG格式編碼的圖像文件。這些文件類型組成了在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.51Serve_static函數發送了一個HTTP response,它的body包含了本地文件的內容。首先,我們通過查看文件名後綴(7行)檢測文件類型,然後發送一個response line response headers給客戶端(6-12行)。請注意,我們一直使用Figure 12.15writen函數來執行所有的輸出。還要注意到,一個空行結束了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通過複製所請求的文件的內容給連接描述符fd15-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.52Serve_dynamic函數通過發送一個指示成功的response line給客戶開始(6-7行),和包含一些信息的server headers8-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 URICGI參數初始化了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協議。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章