Web 服务器,一个多么庄严而神圣的名词!没有接触之前,它神秘而不可侵犯;学习研究之后,它又是如此地平易近人。今天,让我们一起走近web服务器。
此时,想像一下,当你自己动手实现了一个服务器时,你会是怎么的兴奋;尤其是将一个真正的浏览器指向我们自己实现的服务器时,看着它显示自己本机上的文本及图片时,那将是怎样的激动时刻呀!
下面就来看看它的实现全过程:
Tiny的准备
Tiny的main程序
Tiny是一个迭代服务器,监听在命令行中确定的端口上的连接请求。在通过open_listenedfd函数打开一个监听套接字以后,Tiny执行典型的无限服务循环,反复地接受一个连接(accept)请求,执行事务(doit),最后关闭连接描述符(close)。
Tiny的doit函数
rio_readinitb(&rio,fd) :将程序的内部缓存区与描述符相关联。
rio_readlineb(&rio,buf,MAXLINE) :从内部缓存区读出一个文本行至buf中,以null字符来结束这个文本行。当然,每行最大的字符数量不能超过MAXLINE。
sscanf(buf,"%s %s %s",method,uri,version) :作为例子,一般此时buf中存放的是“GET / HTTP/1.1”,所以可知method为“GET”,uri为“/”,version为“HTTP/1.1”。其中sscanf的功能:把buf中的字符串以空格为分隔符分别传送到method、uri及version中。
strcasecmp(method,"GET") :忽略大小写比较method与“GET”的大小,相等的话返回0。
read_requesthdrs(&rio) :读并忽略请求报头。
parse_uri(uri,filename,cgiargs) :解析uri,得文件名存入filename中,参数存入cgiargs中。
stat(filename,&sbuf) :将文件filename中的各个元数据填写进sbuf中,如果找不到文件返回0。
S_ISREG(sbuf,st_mode) :此文件为普通文件。
S_IRUSR & sbuf.st_mode :有读取权限。
serve_static(fd,filename,sbuf.st_size) :提供静态服务。
serve_dynamic(fd,filename,cgiargs) :提供动态服务。
从doit函数中可知,我们的Tiny Web服务器只支持“GET”方法,其他方法请求的话则会发送一条错误消息,主程序返回,并等待下一个请求。否则,我们读并忽略请求报头。(其实,我们在请求服务时,直接不用写请求报头即可,写上只是为了符合HTTP协议标准)。
然后,我们将uri解析为一个文件名和一个可能为空的CGI参数,并且设置一个标志位,表明请求的是静态内容还是动态内容。通过stat函数判断文件是否存在。
最后,如果请求的是静态内容,我们需要检验它是否是一个普通文件,并且可读。条件通过,则我们服务器向客服端发送静态内容;相似的,如果请求的是动态内容,我就核实该文件是否是可执行文件,如果是则执行该文件,并提供动态功能。
Tiny的clienterror函数
向客户端返回错误信息。
sprintf(buf,"------------"):将字符串“------------”输送到buf中。
rio_writen(fd,buf,strlen(buf)):将buf中的字符串写入fd描述符中。
Tiny的read_requesthdrs函数
Tiny不需要请求报头中的任何信息,所以我们这个函数就是来跳过这些请求报头的。具体做法就是读这些请求报头,直到空行,然后返回。OK!
Tiny的parse_uri函数
根据uri中是否含有cgi-bin来判断请求的是静态内容还是动态内容。如果没有cgi-bin,则说明请求的是静态内容。那么,我们需把cgiargs置NULL,然后获得文件名,如果我们请求的uri最后为 “/”,则自动添加上home.html。比如说,我们请求的是“/”,则返回的文件名为“./home.html”,而我们请求“/logo.gif”,则返回的文件名为“./logo.gif”。如果uri中含有cgi-bin,则说明请求的是动态内容。那么,我们需要把参数拷贝到cgiargs中,把要执行的文件路径写入filename。举例来说,uri为/cgi-bin/adder?3&5,则cigargs中 存放的是3&5,filename中存放的是“./cgi-bin/adder”,OK!
index(uri,'?') : 找出uri字符串中第一个出现参数‘?’的地址,并将此地址返回。
Tiny的serve_static函数
打开文件名为filename的文件,把它映射到一个虚拟存储器空间,将文件的前filesize字节映射到从地址srcp开始的虚拟存储区域。关闭文件描述符srcfd,把虚拟存储区的数据写入fd描述符,最后释放虚拟存储器区域。
Tiny的serve_dynamic函数
Tiny通过派生一个子进程并在子进程的上下文中运行一个cgi程序(可执行文件),来提供各种类型的动态内容。
setenv("QUERY_STRING",cgiargs,1) :设置QUERY_STRING环境变量。
dup2 (fd,STDOUT_FILENO) :重定向它的标准输出到已连接描述符。此时,任何写到标准输出的东西都直接写到客户端。
execve(filename,emptylist,environ) :加载运行cgi程序。
====================from csapp.c========================