/* $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.h 和 csapp.c 點開直接複製源碼即可。(tiny.c csapp.h csapp.c 需要放在同一個文件夾下面)
然後編譯 gcc -c csapp.c
gcc -o main csapp.o tiny.c