TINY Web服务器是一个具有有限(但必需的)功能服务器,能够支持静态访问和动态访问,支持静态访问的HTML文件,无格式文本文件,编码为GIF, PNG,JPG格式的图片。麻雀虽小,五脏俱全,十分适合linux 下网络编程的初学者。当你用浏览器访问自己搭建的服务器,显示出图片或者加载出运行的程序时,是一件十分值得高兴的事情。
TINY服务器需要三个文件,tiny.c, csapp.h, csapp.c。这三个文件都可以在csapp上下载。
然后将csapp.c 生成 csapp.o文件:gcc -c csapp.c
将 .o文件打包成静态库文件 csapp.a : ar -rc csapp.a csapp.o
生成可执行文件tiny: gcc -o tiny tiny.c csapp.a
运行该服务器,指定一个端口,必须是1024–49151之间的端口,其余端口不能使用。
比如我指定1024端口: ./tiny 1024
要测试服务器动态加载程序,在csapp上下载adder.c文件,在当前目录下创建文件夹cgi-bin, 将adder.c文件放在该文件夹里并生成可执行文件。
用TELNET 程序测试
在运行了TINY 服务器的基础上,打开另一个terminal,在linux shell输入:telnet localhost 1024
。
然后输入 HTTP 请求行:GET /cgi-bin/adder?100&20 HTTP/1.0
GET 是HTTP 方法
“/cgi-bin/adder?100&20” 是URI, 也就是包含了文件名和参数,其中?前边的是文件名,后边的是程序参数,每个参数用&分隔开。
HTTP/1.0 是HTTP版本
然后获得 HTTP 响应,可以看到我们传进去的参数求和之后获得了最后的结果,中间还有一些头部信息。如图
用浏览器测试
在tiny程序所在的目录放入一张图片test.jpg, 打开浏览器,输入http://localhost:1024/test.jpg, 然后可以看到图片被加载了出来。
测试程序的加载: 输入http://localhost:1024/cgi-bin/adder?100&20 然后看到如下结果,至此服务器的功能测试完毕。当然还可以在文件夹里放入html文件,可以加载出相应的内容。
源码分析:
- main 函数
#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, port, clientlen;
struct sockaddr_in clientaddr;
/* Check command line args */
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); //line:netp:tiny:accept
doit(connfd); //line:netp:tiny:doit
Close(connfd); //line:netp:tiny:close
}
}
在main函数里,关键是listenfd = Open_listenfd(port)
这一句,服务器在这里创建和返回一个监听描述符,这个描述符准备好在端口port上接受连接请求,这个函数是包装了socket() ,bind(), listen() 这三个套接字接口函数的辅助函数。
mian函数执行典型的无限服务器循环,不断地接受连接请求(Accept()函数实现的), 其中Accept() 函数就是套接字接口函数中的accept()加入一些出错提示信息封装成的函数,这是csapp.c中常用的做法。
最后执行一个doit 函数,用来处理一个HTTP事务。请注意,TINY只支持GET 方法。
- 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); //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
}
}
doit 函数 中使用了封装了系统I/O 的robust I/O, 这是csapp 特有的。 这个函数首先读取请求行的信息,得到方法 , URI, 版本号。
然后通过调用parse_uri() 函数来解析URI ,判断这个请求是动态内容还是静态内容。在该程序中,判断方法简单的处理为:要是URI里边有cgi-bin 的话,就是动态内容,否则就是静态内容。
如果是静态内容的话,就调用静态处理函数 serve_static() 函数, 否则调用 serve_dynamic()函数。
其中serve_dynamic()函数十分值得我们注意,它是通过派生一个子进程,并在子进程的上下文中运行这个CGI 程序。其中调用了在前面章节讲到的setenv()函数(用来初始化QUERY_STRING 环境变量),dup2() 函数(重定向标准输出到已连接文件描述符), execv() 函数(加载并运行CGI程序)。
到这里可以体会到这本书的精髓所在了,书中前面章节的讲解的一些函数和知识,在后边的章节就慢慢地用上了,作者十分注重在代码中解释这些复杂的问题,尽可能地减少枯燥的感觉。(不过涉及到操作系统的东西确实比较枯燥O(∩_∩)O~)