深入理解计算机系统(第二版) 家庭作业 第十一章


11.6
A. 因为read_requesthdrs中已经打印出了请求报头,所以只要打印请求行即可。在doit函数中第一个sscanf语句之后添加下面的语句即可:printf("%s %s %s\n", method, uri, version);
B. 用火狐浏览器输出结果:
深入理解计算机系统(第二版) 家庭作业 第十一章_第1张图片
另外,如果要存成文件的话,可能需要另存为?
C. A的结果可以表明,浏览器使用HTTP/1.1
D. 请求行和报头如下:
GET /clockwise.gif HTTP/1.1
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:20.0) Gecko/20100101 Firefox/20.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive

User-Agent: 系统以及浏览器情况
Accept:可以接受的媒体;
Accept-Encoding:可以接受的编码方案;
Accept-Language:能够接受的语言;
其实上述内容是百度得到的


11.7
在get_filetype函数里面添加
else if(strstr(filename, ".mpg") || strstr(filename, ".mp4"))
     strcpy(filetype, "video/mpg"); 


11.8
并没有想到很好的方法。
因为结束子进程之前,我们不可以关闭已连接描述符。这就导致我们还是得让serve_dynamic或者是doit函数或者main函数中要等待子进程结束。迭代服务器的设计让这个问题变得比较无聊。
在main函数之前加入代码:
int chdEnded ;
#include
void child_signal(int sig)
{
    pid_t pid;
    while((pid = waitpid(-1, NULL, WNOHANG)) > 0)
    ;
    chdEnded = 1; 
在main函数中添加语句 signal(SIGCHILD, child_handle);
每次accept之前,让chdEnded = 0;
并且在doit()中的serve_dynamic之后添加:
while(!chdEnded) pause();//or do sth
删掉serve_dynamic里的wait(NULL);


11.9
serve_static中的存储器映射语句改为:
srcfd = open(filename, O_RDONLY, 0);
srcp = (char*)malloc(sizeof(char)*filesize);
rio_readn(srcfd, srcp, filesize);
close(srcfd);
rio_writen(fd, srcp, filesize);
free(srcp); 


11.10
HTML文件:
action="cgi-bin/adder" method="get">
Num1:  
 
Num2:  
 
因为提交的表单里面有参数名字(num1=x&num2=y),所以要修改相应的adder.c:
int parseNum(char *s)
{
    int i = strlen(s) - 1;
    while(i>0 && s[i-1]>='0'&&s[i-1]<='9' ) 
        i--;
    return atoi(s+i);
}

int main(void) {
    char *buf, *p;
    char arg1[MAXLINE], arg2[MAXLINE], content[MAXLINE];
    int n1=0, n2=0;

    /* Extract the two arguments */
    if ((buf = getenv("QUERY_STRING")) != NULL) {
        p = strchr(buf, '&');
        *p = 0;
        strcpy(arg1, buf);
        strcpy(arg2, p+1);
        n1 = parseNum(arg1);
        n2 = parseNum(arg2);
    }

    /* Make the response body */
    sprintf(content, "Welcome to add.com: ");
    sprintf(content, "%sTHE Internet addition portal.\r\n

", content);
    sprintf(content, "%sThe answer is: %d + %d = %d\r\n

",
            content, n1, n2, n1 + n2);
    sprintf(content, "%sThanks for visiting!\r\n", content);

    /* Generate the HTTP response */
    printf("Content-length: %d\r\n", (int)strlen(content));
    printf("Content-type: text/html\r\n\r\n");
    printf("%s", content);
    fflush(stdout);
    exit(0);

更好的做法是在本页显示,目前暂时不会。


11.11
想到的办法就是定义一个变量rmtd,表示请求的方法。
在client_error,serve_static和serve_dynamic中添加一个参数mtd(改的地方也比较多),表示方法。如果mtd为HEAD,就只打印头部。
结果如下:
bnuzhanyu@ubuntu:~/CSAPP11/cgi-bin$ telnet localhost 12345
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
HEAD / HTTP/1.1
HTTP/1.0 200 OK
Server: Tiny Web Server
Content-length: 2722
Content-type: text/html
Connection closed by foreign host.


bnuzhanyu@ubuntu:~/CSAPP11/cgi-bin$ telnet localhost 12345
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
HEAD /clockwise.gif HTTP/1.1
HTTP/1.0 200 OK
Server: Tiny Web Server
Content-length: 126150
Content-type: image/gif
Connection closed by foreign host.


11.12
还是使用rmtd表示方法。
这里需要改的还有读取报头。
用POST方法,表单的参数是在报头之后。
报头中有一项是Content-Length,读取出来,在报头读完之后,接着读取Content-Length个字节(注意最后添0),这些字节就是form用post方法提交的数据。
主要修改的就是doit方法和read_request方法。
下面的程序只能针对参数为文本的情况,且参数总长度最大不超过MAXLINE。

#define M_GET 0
#define M_POST 1
#define M_HEAD 2
#define M_NONE (-1)
void doit(int fd)
{
    int is_static;
    int rmtd = 0;
    struct stat sbuf;
    char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
    char filename[MAXLINE], cgiargs[MAXLINE];
    rio_t rio;

    /*for post*/
    int contentLen;
    char post_content[MAXLINE];

    /* Read request line and headers */
    rio_readinitb(&rio, fd);
    rio_readlineb(&rio, buf, MAXLINE);
    sscanf(buf, "%s %s %s", method, uri, version);
    printf("%s %s %s\n", method, uri, version);

    if(strcmp(method, "GET") == 0) rmtd = M_GET;
    else if(strcmp(method, "POST") == 0) rmtd = M_POST;
    else if(strcmp(method, "HEAD") == 0) rmtd = M_HEAD;
    else rmtd = M_NONE;

    if (rmtd == M_NONE) {
        clienterror(fd, method, "501""Not Implemented",
            "Tiny does not implement this method"rmtd);
        return;
    }

    contentLen = read_requesthdrs(&rio, post_content, rmtd);

    /* Parse URI from GET request */
    is_static = parse_uri(uri, filename, cgiargs);
    if (stat(filename, &sbuf) < 0) {
        clienterror(fd, filename, "404""Not found",
                "Tiny couldn't find this file"rmtd);
        return;
    }

    if (is_static) {/* Serve static content */
        if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) {
            clienterror(fd, filename, "403""Forbidden",
                    "Tiny couldn't read the file"rmtd);
            return;
        }
        serve_static(fd, filename, sbuf.st_size, rmtd);
    }

    else {/* Serve dynamic content */
        if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) {
            clienterror(fd, filename, "403""Forbidden",
                    "Tiny couldn't run the CGI program"rmtd);
            return;
        }
        if(rmtd == M_POST) strcpy(cgiargs, post_content);
        serve_dynamic(fd, filename, cgiargs, rmtd);
    }
}
 

int read_requesthdrs(rio_t *rp, char* content, int rmtd)
{
    char buf[MAXLINE];
    int contentLength = 0;
    char *begin;
    rio_readlineb(rp, buf, MAXLINE);

    while(strcmp(buf, "\r\n")) {
        rio_readlineb(rp, buf, MAXLINE);
        printf("%s", buf);
        if(rmtd == M_POST && strstr(buf, "Content-Length: ")==buf)
            contentLength = atoi(buf+strlen("Content-Length: "));

    }
    if(rmtd == M_POST){
        contentLength = rio_readnb(rp, content, contentLength);
        content[contentLength] = 0;
        printf("POST_CONTENT: %s\n", content);
    }
    return contentLength;


strstr那句也可以用strncmp代替(效率应该高一些)。
表单改成Post之后,运行效果:
深入理解计算机系统(第二版) 家庭作业 第十一章_第2张图片



11.13
为了测试EPIPE错误,我在read_requesthdrs里面添加了sleep(5)。
于是,在浏览器里请求之后,立即断开。进程出现错误:
GET /add.html HTTP/1.1
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:20.0) Gecko/20100101 Firefox/20.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Segmentation fault
bnuzhanyu@ubuntu:~/CSAPP11$

为了解决这个问题,我用了setjmp和longjmp。
当进程捕捉到SIGPIPE时,进入一个信号处理函数:
jmp_buf buf;
void epipe_signal(int sig)
{
    longjmp(buf, 1);

而在main函数中,doit部分需要这样改:
rc = setjmp(buf);
if(rc == 0) {
    doit(connfd);
    close(connfd);
}
这样做虽然能解决这个问题,然而,如果是在子进程中longjmp(也就是adder里sleep(5)时,浏览器关闭了),那么会不会有问题呢?
为了测试,我又在adder中加入了sleep(5)。
并且setjmp后面的语句改为:
if(rc == 0) {
    printf("rc=%d   pid=%d\n", rc, getpid());
    doit(connfd);
    close(connfd);
}
else printf("rc=%d   pid=%d\n", rc, getpid()); 
结果是,当我在adder的sleep期间关闭浏览器,没有输出rc的消息。
后来想起来,输出已经被重定向了,而且是子进程和父进程的输出都被重定向了(没懂为什么都重定向了)。即,当浏览器再次请求时,rc=0也不会输出。
后来在setjmp后添加了dup2(2,1),即将标准输出重定向到标准错误,就可以看见输出了。
奇怪的是,pid都是main的pid。这是不是说明,在子进程中调用longjmp仍然可以回到父进程。那么子进程去哪儿了呢?这个问题还是需要研究一下。
今天测试了一下,子进程的longjmp还是在子进程中。
所以,现在不知道之前那个程序为什么一直输出main的pid,这太奇怪了。
这也就证明我上面的写法是不正确的。

但是,想一想,觉得如果是在子进程中遇到关闭描述符的问题,那么可以直接退出这个进程啊,因为没必要再让这个进程存活下去了。所以,只要简单地判断当前进程是不是子进程,如果是,则exit,如果不是,则longjmp。
先用一个变量mainpid在主函数开始时赋值为getpid();
if(getpid() == mainpid) longjmp(buf,1); else exit(0);











你可能感兴趣的:(深入理解计算机系统(第二版) 家庭作业 第十一章)