分析(深入计算机系统) 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

猜你喜欢

转载自blog.csdn.net/weixin_41237676/article/details/100060846
今日推荐