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