Analysis (in-depth computer system) TINY Web server

/* $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 */

 

Figure of running result:

The header file #include"csapp.h" download link: http://csapp.cs.cmu.edu/public/code.html

You need to click on the files csapp.h and csapp.c to copy the source code directly. (tiny.c csapp.h csapp.c needs to be placed in the same folder)

Then compile gcc -c csapp.c  

                gcc -o main csapp.o tiny.c

 

 

 

Guess you like

Origin blog.csdn.net/weixin_41237676/article/details/100060846
Recommended