分析(深入計算機系統) TINYWeb服務器

/* $begin tinymain */
/*
 * tiny.c - A simple, iterative HTTP/1.0 Web server that uses the 
 *     GET method to serve static and dynamic content.
 */
#include "csapp.h"

void doit(int fd);
void read_requesthdrs(rio_t *rp);
int  parse_uri(char *uri, char *filename, char *cgiargs);
void serve_static(int fd, char *filename, int filesize);
void get_filetype(char *filename, char *filetype);
void serve_dynamic(int fd, char *filename, char *cgiargs);
void clienterror(int fd, char *cause, char *errnum, 
		 char *shortmsg, char *longmsg);

int main(int argc, char **argv) 
{
    int  listenfd, connfd ;         		  // :監聽文件描述符     : 客服端連接文件描述符
    char hostname[30] , port[20] ;            // :存放客服端主機名  :端口 
    struct sockaddr_in clientaddr;            // :存放客服端 套接字 地址

    /* Check command line args */
    if (argc != 2) {
	fprintf(stderr, "usage: %s <port>\n", argv[0]);
	exit(1);
    }
    int sport = atoi(argv[1]); //atoi() 把字符串轉換成整數 , argv[1] 鍵入端口號

    listenfd = Open_listenfd(sport);  //創建一服務器端套接字 sport 爲該端口 (main 函數第二個參數,第一個參數是main函數地址)
    while (1) {
	int clientlen = sizeof(clientaddr);
	printf("wait to link ......\r\n " );	
	connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);//等待接收客服端請求
	
	/**
	 * clientaddr 中存放的客服端的套接字地址(這裏是ipv4) . getnameinfo 將套接字地址轉換成名稱 存入host 和 port字符串中無論是ipv4還是ipv6
	 *  但是clientaddr要強轉爲通用的存放ip的結構體sockaddr中即 SA(typedef struct sockaddr SA). 結果會發現兩個端口號不同(clientaddr.sin_port 和 port)
	 *  於是發現前者是網絡字節順序後者經過處理是主機字節順序(網絡字節順序是大端表示法 主機則不能確定) . 於是加上函數ntohl將網絡字節順序轉換成主機字節順序 
	 *  當然該 ip (clientaddr.sin_addr)能夠正常大出 可能是因爲inet_ntoa函數內部將字節順序改爲了主機字節順序
	 */
	printf("ipv4 %s ,port %d\n " ,inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));    //inet_ntoa() 將32位無符號整數轉換爲字符串
	//getnameinfo((SA *) &clientaddr , clientlen ,hostname , 13 , port , 6 , 2);                    //獲取客服端套接字地址存入hostname port中
	//printf( "hostname : %s , port : %s \n" , hostname , port);                                    //打印地址
	

	doit(connfd);                                             
	Close(connfd);                                           
    }
}
/* $end tinymain */

/*
 * doit - handle one HTTP request/response transaction
 */
/* $begin doit */
void doit(int fd) 
{
    int is_static;
    struct stat sbuf;
    char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
    char filename[MAXLINE], cgiargs[MAXLINE];
    rio_t rio; //讀緩衝區
    
    /* Read request line and headers */
    Rio_readinitb(&rio, fd);                             //將讀緩衝區和(客服端套接字)文件描述符關聯
    Rio_readlineb(&rio, buf, MAXLINE);                   //讀緩衝區rio讀取(一行)請求頭部到buf中 (由於該函數間接調用了read() 所以會阻塞)
    printf("%s " , buf);
    sscanf(buf, "%s %s %s", method, uri, version);       //讀取字符串buf中按自定格式寫入 後面三個參數
    if (strcasecmp(method, "GET")) {                     //忽略大小寫比較method和GET是否相同
       clienterror(fd, method, "501", "Not Implemented",
                "Tiny does not implement this method");
        return;
     }                                                    
    read_requesthdrs(&rio);                              //忽略到報文頭部其他內容(僅僅將該部分內容打印)

    /* Parse URI from GET request */
    is_static = parse_uri(uri, filename, cgiargs);       //判斷是否爲靜態網頁還是動態網頁
    if (stat(filename, &sbuf) < 0) {                     //通過文件名filename獲取文件信息,並保存在sbuf所指的結構體stat中
	clienterror(fd, filename, "404", "Not found",
		    "Tiny couldn't find this file");
	return;
    }                                                    //line:netp:doit:endnotfound

    if (is_static) { /* Serve static content */          
	if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) { //S_ISREG文件是否可爲普通文件 S_IXUSR是否有讀權限
	    clienterror(fd, filename, "403", "Forbidden",
			"Tiny couldn't read the file");
	    return;
	}
	serve_static(fd, filename, sbuf.st_size);        //靜態頁面處理
    }
    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");
	    return;
	}
	serve_dynamic(fd, filename, cgiargs);            //動態頁面處理
    }
}
/* $end doit */

/*
 * read_requesthdrs - read and parse HTTP request headers
 */
/* $begin read_requesthdrs */
void read_requesthdrs(rio_t *rp) 
{
    char buf[MAXLINE];
    
    do{          
	Rio_readlineb(rp, buf, MAXLINE);
	printf(" %s", buf);
    }while(strcmp(buf, "\r\n"));  //報文頭部和實際內容部分是用/r/n空開

    return;
}
/* $end read_requesthdrs */

/*
 * parse_uri - parse URI into filename and CGI args
 *             return 0 if dynamic content, 1 if static
 */
/* $begin parse_uri */
int parse_uri(char *uri, char *filename, char *cgiargs)  //判斷是靜態網頁還是動態網頁 如果是靜態網頁會以相對路徑來獲取文件如./abc.html 文件地址和參數放在filename cgiargr
{									 //s 動態網頁會解析參數
    char *ptr;

    if (!strstr(uri, "cgi-bin")) {  //strstr判斷字符串首次出現地址 沒出現返回NULL
	strcpy(cgiargs, "");                             
	strcpy(filename, ".");                           
	strcat(filename, uri);                           
	if (uri[strlen(uri)-1] == '/')                  
	    strcat(filename, "home.html");               
	return 1;
    }
    else {  /* Dynamic content */                       
	ptr = index(uri, '?');                           
	if (ptr) {
	    strcpy(cgiargs, ptr+1);
	    *ptr = '\0';
	}
	else 
	    strcpy(cgiargs, "");                        
	strcpy(filename, ".");                           
	strcat(filename, uri);                       
	return 0;
    }
}
/* $end parse_uri */

/*
 * serve_static - copy a file back to the client 
 */
/* $begin serve_static */
void serve_static(int fd, char *filename, int filesize) 
{
    int srcfd;
    char *srcp, filetype[MAXLINE], buf[MAXBUF];
 
    /* Send response headers to client */
    get_filetype(filename, filetype);       //獲取文件類型
    sprintf(buf, "HTTP/1.0 200 OK\r\n");    //line:netp:servestatic:beginserve
    sprintf(buf, "%sServer: Tiny Web Server\r\n", buf);
    sprintf(buf, "%sContent-length: %d\r\n", buf, filesize);
    sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, filetype);
    Rio_writen(fd, buf, strlen(buf));       //寫入響應頭

    /* Send response body to client */
    srcfd = Open(filename, O_RDONLY, 0);    //line:netp:servestatic:open
    srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);//內存映射 將文件中的內容映射到虛擬內存中去且只有讀訪問權限
    Close(srcfd);                           
    Rio_writen(fd, srcp, filesize);         
    Munmap(srcp, filesize);                 			//刪除開始的內存映射區域
}

/*
 * get_filetype - derive file type from file name
 */
void get_filetype(char *filename, char *filetype) //判斷文件類型放入filetype中
{
    if (strstr(filename, ".html"))
	strcpy(filetype, "text/html");
    else if (strstr(filename, ".gif"))
	strcpy(filetype, "image/gif");
    else if (strstr(filename, ".jpg"))
	strcpy(filetype, "image/jpeg");
    else
	strcpy(filetype, "text/plain");
}  
/* $end serve_static */

/*
 * serve_dynamic - run a CGI program on behalf of the client
 */
/* $begin serve_dynamic */
void serve_dynamic(int fd, char *filename, char *cgiargs) 
{
    char buf[MAXLINE], *emptylist[] = { NULL };

    /* Return first part of HTTP response */
    sprintf(buf, "HTTP/1.0 200 OK\r\n"); 
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "Server: Tiny Web Server\r\n");
    Rio_writen(fd, buf, strlen(buf));
  
    if (Fork() == 0) { /* child */ //line:netp:servedynamic:fork
	/* Real server would set all CGI vars here */
	setenv("QUERY_STRING", cgiargs, 1); //line:netp:servedynamic:setenv
	Dup2(fd, STDOUT_FILENO);         /* Redirect stdout to client */ //line:netp:servedynamic:dup2
	Execve(filename, emptylist, environ); /* Run CGI program */ //line:netp:servedynamic:execve
    }
    Wait(NULL); /* Parent waits for and reaps child */ //line:netp:servedynamic:wait
}
/* $end serve_dynamic */

/*
 * clienterror - returns an error message to the client
 */
/* $begin clienterror */
void clienterror(int fd, char *cause, char *errnum,   //沒有找到文件錯誤
		 char *shortmsg, char *longmsg) 
{
    char buf[MAXLINE], body[MAXBUF];

    /* Build the HTTP response body */
    sprintf(body, "<html><title>Tiny Error</title>");
    sprintf(body, "%s<body bgcolor=""ffffff"">\r\n", body);
    sprintf(body, "%s%s: %s\r\n", body, errnum, shortmsg);
    sprintf(body, "%s<p>%s: %s\r\n", body, longmsg, cause);
    sprintf(body, "%s<hr><em>The Tiny Web server</em>\r\n", body);

    /* Print the HTTP response */
    sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, shortmsg);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "Content-type: text/html\r\n");
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "Content-length: %d\r\n\r\n", (int)strlen(body));
    Rio_writen(fd, buf, strlen(buf));
    Rio_writen(fd, body, strlen(body));
}
/* $end clienterror */

 

運行結果圖:

其中頭文件 #include"csapp.h" 下載地址 :http://csapp.cs.cmu.edu/public/code.html

需要文件 csapp.hcsapp.c 點開直接複製源碼即可。(tiny.c csapp.h csapp.c 需要放在同一個文件夾下面)

然後編譯 gcc -c csapp.c  

                gcc -o main csapp.o tiny.c

 

 

 

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