1. Web基础

webclient和server之间的交互使用的是一个基于文本的应用级协议HTTP(超文本传输协议)。

一个webclient(即浏览器)打开一个到server的因特网连接,而且请求某些内容。server响应所请求的内容,然后关闭连接。浏览器读取这些内容。并把它显示在屏幕上。

对于webclient和server而言。内容是与一个MIME类型相关的字节序列。

常见的MIME类型:

MIME类型 描写叙述
text/html HTML页面
text/plain 无格式文本
image/gif GIF格式编码的二进制图像
image/jpeg JPEG格式编码的二进制图像

webserver以两种不同的方式向客服端提供内容:

1. 静态内容:取一个磁盘文件。并将它的内容返回给client

2. 动态内容:执行一个可执行文件,并将它的输出返回给client

统一资源定位符:URL

http://www.google.com:80/index.html

表示因特网主机 www.google.com 上一个称为 index.html 的HTML文件。它是由一个监听port80的Webserver所管理的。

HTTP默认port号为80

可执行文件的URL能够在文件名称后包含程序參数, “?”字符分隔文件名称和參数,而且每一个參数都用“&”字符分隔开。如:

http://www.ics.cs.cmu.edu:8000/cgi-bin/adder?

123&456

表示一个 /cgi-bin/adder 的可执行文件,带两个參数字符串为 123 和 456

确定一个URL指向的是静态内容还是动态内容没有标准的规则,常见的方法就是把全部的可执行文件都放在 cgi-bin 文件夹中

2. HTTP

HTTP标准要求每一个文本行都由一对回车和换行符来结束

(1)HTTP请求

一个HTTP请求:一个请求行(request line) 后面尾随0个或多个请求报头(request header), 再尾随一个空的文本行来终止报头

请求行: <method> <uri> <version>

HTTP支持很多方法。包含 GET,POST,PUT,DELETE,OPTIONS,HEAD,TRACE。

URI是对应URL的后缀,包含文件名称和可选參数

version 字段表示该请求所遵循的HTTP版本号

请求报头:<header name> : <header data> 为server提供了额外的信息。比如浏览器的版本号类型

HTTP 1.1中 一个IP地址的server能够是 多宿主主机。比如 www.host1.com www.host2.com 能够存在于同一server上。

HTTP 1.1 中必须有 host 请求报头,如 host:www.google.com:80 假设没有这个host请求报头,每一个主机名都仅仅有唯一IP,IP地址非常快将用尽。

(2)HTTP响应

一个HTTP响应:一个响应行(response line) 后面尾随0个或多个响应报头(response header)。再尾随一个空的文本行来终止报头,最后尾随一个响应主体(response body)

响应行:<version> <status code> <status message>

status code 是一个三位的正整数

状态代码 状态消息 描写叙述
200 成功 处理请求无误
301 永久移动 内容移动到位置头中指明的主机上
400 错误请求 server不能理解请求
403 禁止 server无权訪问所请求的文件
404 未发现 server不能找到所请求的文件
501 未实现 server不支持请求的方法
505 HTTP版本号不支持 server不支持请求的版本号

两个最重要的响应报头:

Content-Type 告诉client响应主体中内容的MIME类型

Content-Length 指示响应主体的字节大小

响应主体中包含着被请求的内容。

3.服务动态内容

(1) client怎样将程序參数传递给server

GET请求的參数在URI中传递, “?”字符分隔了文件名称和參数,每一个參数都用一个”&”分隔开,參数中不同意有空格,必须用字符串“%20”来表示

HTTP POST请求的參数是在请求主体中而不是 URI中传递的

(2)server怎样将參数传递给子进程

GET /cgi-bin/adder?123&456 HTTP/1.1

它调用 fork 来创建一个子进程。并调用 execve 在子进程的上下文中执行 /cgi-bin/adder 程序

在调用 execve 之前,子进程将CGI环境变量 QUERY_STRING 设置为”123&456”, adder 程序在执行时能够用unix getenv 函数来引用它

(3)server怎样将其它信息传递给子进程

环境变量 描写叙述
QUERY_STRING 程序參数
SERVER_PORT 父进程侦听的port
REQUEST_METHOD GET 或 POST
REMOTE_HOST client的域名
REMOTE_ADDR client的点分十进制IP地址
CONTENT_TYPE 仅仅对POST而言。请求体的MIME类型
CONTENT_LENGTH 仅仅对POST而言,请求体的字节大小

(4) 子进程将它的输出发送到哪里

一个CGI程序将它的动态内容发送到标准输出。在子进程载入并执行CGI程序之前,它使用UNIX dup2 函数将它标准输出重定向到和client相关连的已连接描写叙述符

因此,不论什么CGI程序写到标准输出的东西都会直接到达client

4. 综合: Tiny web server源代码及分析

(1) main程序

Tiny是一个迭代server,监听在命令行中传递来的port上的连接请求,在通过调用 open_listenfd 函数打开一个监听套接字以后。执行无限server循环,不断接受连接请求(第16行)。执行事务(第17行),并关闭连接它的那一端(第18行)

int main(int argc, char **argv)
{
int listenfd, connfd, port, clientlen;
struct sockaddr_in clientaddr; if (argc != 2) {
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(1);
}
port = atoi(argv[1]); listenfd = Open_listenfd(port);
while (1) {
clientlen = sizeof(clientaddr);
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
doit(connfd);
Close(connfd);
}
}

(2) doit函数

doit函数处理一个HTTP事物,首先读和解析请求行(request line)(第11-12行),注意,我们使用rio_readlineb函数读取请求行。

Tiny仅仅支持GET方法,假设client请求其它方法,发送一个错误信息。

然后将URI解析为一个文件名称和一个可能为空的CGI參数字符串。而且设置一个标志表明请求的是静态内容还是动态内容(第21行)

假设请求的是静态内容。就验证是否为普通文件,有读权限(第29行)

假设请求的是动态内容,就验证是否为可执行文件(第37行),假设是,就提供动态内容(第42行)

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); //line:netp:doit:readrequest
sscanf(buf, "%s %s %s", method, uri, version); //line:netp:doit:parserequest
if (strcasecmp(method, "GET")) { //line:netp:doit:beginrequesterr
clienterror(fd, method, "501", "Not Implemented",
"Tiny does not implement this method");
return;
} //line:netp:doit:endrequesterr
read_requesthdrs(&rio); //line:netp:doit:readrequesthdrs /* Parse URI from GET request */
is_static = parse_uri(uri, filename, cgiargs); //line:netp:doit:staticcheck
if (stat(filename, &sbuf) < 0) { //line:netp:doit:beginnotfound
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)) { //line:netp:doit:readable
clienterror(fd, filename, "403", "Forbidden",
"Tiny couldn't read the file");
return;
}
serve_static(fd, filename, sbuf.st_size); //line:netp:doit:servestatic
}
else { /* Serve dynamic content */
if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) { //line:netp:doit:executable
clienterror(fd, filename, "403", "Forbidden",
"Tiny couldn't run the CGI program");
return;
}
serve_dynamic(fd, filename, cgiargs); //line:netp:doit:servedynamic
}
}

(4)read_requesthdrs 函数

Tiny不使用请求报头中的不论什么信息。仅仅调用 read_requesthdrs函数来读取并忽略这些报头。

注意。终止请求报头的空文本行是由 回车和换行符组成的。在第6行中检查

void read_requesthdrs(rio_t *rp)
{
char buf[MAXLINE]; Rio_readlineb(rp, buf, MAXLINE);
while(strcmp(buf, "\r\n")) { //line:netp:readhdrs:checkterm
Rio_readlineb(rp, buf, MAXLINE);
printf("%s", buf);
}
return;
}

(5)parse_uri 函数

Tiny假设静态内容的主文件夹就是当前文件夹,可执行文件的主文件夹是 ./cgi-bin/ 不论什么包含字符串 cgi-bin 的URI都觉得是对动态内容的请求。

首先将URI解析为一个文件名称和一个可选的CGI參数字符串。

假设请求的是静态内容(第5行)。就清除CGI參数串(第6行)。然后将URI转换为一个相对的unix 路径名,比如 ./index.html

假设URI是用’/’ 结尾的(第9行) ,我们就把默认的文件名称加在后面(第10行)

假设请求的是动态内容(第13行),就会抽取全部的CGI參数(第14-20行),并将URI剩下的部分转换为一个对应的unix文件名称(第21-22行)

int parse_uri(char *uri, char *filename, char *cgiargs)
{
char *ptr; if (!strstr(uri, "cgi-bin")) { /* Static content */ //line:netp:parseuri:isstatic
strcpy(cgiargs, ""); //line:netp:parseuri:clearcgi
strcpy(filename, "."); //line:netp:parseuri:beginconvert1
strcat(filename, uri); //line:netp:parseuri:endconvert1
if (uri[strlen(uri)-1] == '/') //line:netp:parseuri:slashcheck
strcat(filename, "home.html"); //line:netp:parseuri:appenddefault
return 1;
}
else { /* Dynamic content */ //line:netp:parseuri:isdynamic
ptr = index(uri, '?'); //line:netp:parseuri:beginextract
if (ptr) {
strcpy(cgiargs, ptr+1);
*ptr = '\0';
}
else
strcpy(cgiargs, ""); //line:netp:parseuri:endextract
strcpy(filename, "."); //line:netp:parseuri:beginconvert2
strcat(filename, uri); //line:netp:parseuri:endconvert2
return 0;
}
}

(6)serve_static 函数

Tiny提供四种不同的静态内容:HTML文件、无格式的文本文件、GIF编码格式图片、JPEG编码格式图片

serve_static 函数发送一个HTTP响应,其主体包含一个本地文件的内容。

首先我们通过检查文件名称的后缀来推断文件类型(第7行)。而且发送响应行和响应报头给client(第8-12行)。

注意用一个空行终止报头

第16行,我们使用 unix mmap函数将被请求文件映射到一个虚拟问存储器空间,调用mmap将文件srcfd的前filesize个字节映射到一个从地址srcp開始的私有仅仅读虚拟存储器区域。

一旦文件映射到存储器,就不再须要它的描写叙述符了,关闭这个文件(第17行)。

第18行执行的是到client的实际文件传动。rio_writen 函数拷贝从srcp位置開始的filesize个字节(已经被映射到了所请求的文件) 到client的已连接描写叙述符。

第19行释放了映射的虚拟存储器区域,避免潜在的存储器泄漏

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); //line:netp:servestatic:getfiletype
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)); //line:netp:servestatic:endserve /* 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);//line:netp:servestatic:mmap
Close(srcfd); //line:netp:servestatic:close
Rio_writen(fd, srcp, filesize); //line:netp:servestatic:write
Munmap(srcp, filesize); //line:netp:servestatic:munmap
} /*
* get_filetype - derive file type from file name
*/
void get_filetype(char *filename, char *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");
}

(7)serve_dynamic 函数

Tiny通过派生一个子进程并在子进程的上下文中执行一个CGI程序。来提供各种类型的动态内容。

serve_dynamic函数一開始就向client发送一个表明成功的响应行,,同一时候还包含带有信息的server报头。

第13行,子进程用来自请求URI的CGI參数初始化QUERY_STRING环境变量

第14行,子进程重定向它的标准输出到已连接文件描写叙述符

第15行,载入并执行CGI程序。由于CGI程序执行在子进程的上下文中,它能够訪问全部在调用execve函数之前就存在的打开文件和环境变量

第17行,父进程堵塞在对wait的调用中,等待子进程终止的时候。回收操作系统那个分配给子进程的资源

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
}

5.调试及执行

(1) 下载csapp.h 和 csapp.c

http://csapp.cs.cmu.edu/public/ics2/code/include/csapp.h

http://csapp.cs.cmu.edu/public/ics2/code/src/csapp.c

关于CSAPP代码下载的技巧:比方code/conc/sbuf.c,对应的下载地址在

http://csapp.cs.cmu.edu/public/ics2/code/conc/sbuf.c

(2) 编译

将全部源文件tiny.c、csapp.c和csapp.h放在同一个文件夹下。

$ gcc -o tiny tiny.c csapp.c -lpthread

注:加-lpthread是由于csapp.c中有些函数用了多线程库

(3) 执行前准备

  1. 将被訪问的文件放在tiny同级文件夹下(home.html、photo.jpg)
<html>
<head>
<title>Hello World</title>
</head>
<body>
<h1>Welcome to Tiny Web Server</h1>
</body>
</html>
  1. 将測试用CGI程序放到cgi-bin文件夹下。并编译成可执行程序
$ gcc -o adder adder.c

(4) 执行流程及其结果

  1. 执行Tiny程序,并指定port号(1024–49151可用,其它为知名port)
$ ./tiny 1024
  1. 浏览器訪问静态内容(home.html)

  2. 浏览器訪问不存在的内容

  3. 浏览器訪问动态内容

  4. 还能够訪问图片哦

(5) Telnet 測试

  1. 连接到Tinyserver
$ telnet localhost 1024
  1. 输入请求头(注意空行)
GET /home.html HTTP/1.0

  1. 验证结果(注意空行)
HTTP/1.0 200 OK
Server: Tiny Web Server
Content-length: 108
Content-type: text/html <html>
<head>
<title>Hello World</title>
</head>
<body>
<h1>Welcome to Tiny Web Server</h1>
</body>
</html>
Connection closed by foreign host.
  1. 错误的返回
HTTP/1.0 404 Not found
Content-type: text/html
Content-length: 143 <html><title>Tiny Error</title><body bgcolor=ffffff>
404: Not found
<p>Tiny couldn't find this file: .kkk
<hr><em>The Tiny Web Server</em>
Connection closed by foreign host.

(6) 提醒

须要注意的是 HTTP 协议的头部和数据之间有一个空行,假设浏览器无法查看到内容,而通过 Telnet 能够得到数据,则能够推断为少了一个空行。

CSAPP Tiny web server源代码分析及搭建执行的更多相关文章

  1. <深入理解计算机系统> CSAPP Tiny web 服务器

    本文是我学习<深入理解计算机系统>中网络编程部分的学习笔记. 1. Web基础       web客户端和服务器之间的交互使用的是一个基于文本的应用级协议HTTP(超文本传输协议).一个w ...

  2. Appium Android Bootstrap源代码分析之启动执行

    通过前面的两篇文章<Appium Android Bootstrap源代码分析之控件AndroidElement>和<Appium Android Bootstrap源代码分析之命令 ...

  3. 【原创】kafka server源代码分析(一)

    这个是Kafka server的核心包,里面的类也很多,我们还是一个一个分析 一.BrokerStates.scala 定义了目前一个kafka broker的7中状态 ——  1. NotRunni ...

  4. 【原创】kafka server源代码分析(二)

    十四.AbstractFetcherManager.scala 该scala定义了两个case类和一个抽象类.两个case类很简单: 1. BrokerAndFectherId:封装了一个broker ...

  5. tiny web服务器源码分析

    tiny web服务器源码分析 正如csapp书中所记,在短短250行代码中,它结合了许多我们已经学习到的思想,如进程控制,unix I/O,套接字接口和HTTP.虽然它缺乏一个实际服务器所具备的功能 ...

  6. NGINX Web Server Nginx web server

    原文地址:http://nginx.com/resources/admin-guide/web-server/ NGINX Web Server Nginx web server This secti ...

  7. ubuntu16.04-x64系统中Jexus web server部署.NetCore和端口分析引发的猜想!

    您有这样的牢骚么? 有一周没更新博客了,简单说下在干什么吧:主要是公司安排对接某旅游大公司的接口,接口数量倒也就10个左右,对接完后还需要加入到业务系统中和App端,因此还是需要花点时间的:时间上来说 ...

  8. Android系统进程间通信(IPC)机制Binder中的Client获得Server远程接口过程源代码分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6633311 在上一篇文章中,我 们分析了And ...

  9. 搭建一个Web Server站点

    题:搭建一个Web Server站点.安装web服务,并在本地创建index.html测试 1.安装http服务 yum -y install httpd 2.进入网站目录 cd /var/www/h ...

随机推荐

  1. 042 spring boot在启动之后,自动关闭

    在学校数据源的时候,还没有开始使用数据源,项目就关闭了. 为了学习数据源,就只能使用测试类. 但是,最近需要使用restful进行测试的时候,项目是关闭的,就很糟糕,不好进行测试. 1.日志如下: D ...

  2. Shiro笔记(五)Shiro授权

    Shiro授权 也叫访问控制,即在应用中控制谁能访问那些资源(如访问页面.编辑数据.页面操作等).在授权中需要了解几个关键对象:主体(subject).资源(resource).权限(Permissi ...

  3. UVA136 Ugly Numbers【set】【优先队列】

    丑数 丑数是指不能被2,3,5以外的其他素数整除的数.把丑数从小到大排列起来,结果如下: 1,2,3,4,5,6,8,9,10,12,15,… 求第1500个丑数. 提示:从小到大生成各个丑数.最小的 ...

  4. 从小白到区块链工程师:第一阶段:Go语言环境的搭建(1)

    一,Golang语言简介 2009年由谷歌公司推出,由C语言之父Ken Thompson主导研发.Go(又称Golang)是Google开发的一种静态强类型.编译型.并发型,并具有垃圾回收功能的编程语 ...

  5. 同一台电脑上个人的github账户如何与公司的gitlab账户共存

    前些天,写了篇博客 开发环境之git:团队协作git工作流与常用命令. 主要是回顾其中的第一小节基本配置. 但是对于很多程序员而言,我们不仅有公司的gitlab账户做公司的业务,也会有自己个人的git ...

  6. Java内存空间的分配及回收

    Java中内存分为: 栈:存放简单数据类型变量(值和变量名都存在栈中),存放引用数据类型的变量名以及它所指向的实例的首地址. 堆:存放引用数据类型的实例. Java的垃圾回收 由一个后台线程gc进行垃 ...

  7. centos6安装openresty

    1.安装依赖库 yum install readline-devel pcre-devel openssl-devel gcc 2.下载openresty wget --no-check-certif ...

  8. 潭州课堂25班:Ph201805201 django 项目 第三十二课 后台站点管理(课堂笔记)

    一.后台站点模版抽取 1.获取静态站点模版 可以使用git clone到本地 git clone https://github.com/almasaeed2010/AdminLTE.git 也可以在g ...

  9. Linux下redis 的部署、主从与集群

    老男孩Python全栈6期——redis--------------------------Linux 操作系统 默认的内存管理机制RSS:page cache:anno page:Linux操作系统 ...

  10. POJ 1905 Expanding Rods 木棍膨胀

    描述 当长度为L的一根细木棍的温度升高n度,它会膨胀到新的长度L'=(1+n*C)*L,其中C是热膨胀系数. 当一根细木棍被嵌在两堵墙之间被加热,它将膨胀形成弓形的弧,而这个弓形的弦恰好是未加热前木棍 ...