一、背景
csapp的网络编程粗略的介绍了关于网络编程的一些知识,在最后的一节主要就实现了一个小型的Webserver。这个server名叫Tiny,它是一个小型的可是功能齐全的Webserver。在短短300行左右的代码中,结合了很多思想,比如,进程控制,unix I/O、套接字、HTTP等,令人兴奋的是,它能够为Web浏览器提供静态和动态的内容,也就是说在浏览器中要打开的HTML之类的文件能够直接通过Tiny直接显示在窗体。
我一直想要学习网络编程,这或许就是第一个做成的东西吧,想想都让人兴奋,可是,原书中的代码可不是能够直接使用的。还有详细如何使用它。如何让浏览器指向自己的server。书上没有详细说,我也不知道,我在网上找了好久最终明确了如何使用。书上的代码并不全然。是不能直接执行成功的。这里。我将我的过程记下来,想必然会有人须要的。
二、编译搭建
1.将完整的代码编译
gcc tinyserver.c -o tinyserver
2.将測试程序adder.c编译成可运行程序,adder.c需放在与tinyserver在同一文件夹下的cgi-bin文件夹下(后面再说为什么这样放)gcc adder.c -o adder
3.执行tinyserver程序并指定所用port(1024--49151可用,其它为系统使用。一般不能占用)./tinyserver 2000
4.在浏览器中地址栏输入訪问地址http:localhost:2000/cgi-bin/adder?
30&72
5.执行结果浏览器中显示:
后台server信息显示:
假设想要显示其它的文件,比如图片,文章等做法和上面一样
http:localhost:2000/testpic.jpg
可是假设在測试的过程中会遇到以下的情况,后台显示一直在刷新
我想可能是由于这个server是单线程的原因,当接收到一个请求后,在main中由于是持续刷新的,才会出现这样的情况,可是我不是非常确定,不知道哪位大神能够解释下.........
以上就是简单的使用情况,相信在測试成功的那一刻,是不是成就感非常大有没有啊??
?
三、源代码分析
Tiny是一个迭代server,监听在命令行中确定的port上的连接请求。在通过open_listenedfd函数打开一个监听套接字以后,Tiny运行典型的无限服务循环,重复地接受一个连接(accept)请求。运行事务(doit),最后关闭连接描写叙述符(close)
1.头文件:
/*
TINY - A simple ,iterative HTTP/1.0 Web server
*/
#ifndef __CSAPP_H__
#define __CSAPP_H__
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <setjmp.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <math.h>
#include <semaphore.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//以上的头文件按说都是在”csapp.h”中,可是我试了试不行的,所以就直接自己写了
#define DEF_MODE S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH
#define DEF_UMASK S_IWGRP|S_IWOTH
typedef struct sockaddr SA;
#define RIO_BUFSIZE 8192
typedef struct {
int rio_fd; /* 内部缓存区的描写叙述符 */
int rio_cnt; /* 内部缓存区剩下还未读的字节数 */
char *rio_bufptr; /* 指向内部缓存区中下一个未读字节 */
char rio_buf[RIO_BUFSIZE]; /* 内部缓存区 */
} rio_t;
extern char **environ;
#define MAXLINE 8192 /* 每行最大字符数 */
#define MAXBUF 8192 /* I/O缓存区的最大容量 */
#define LISTENQ 1024 /* 监听的第二个參数 */
/* helper functions */
ssize_t rio_writen(int fd,void *usrbuf,size_t n);
void rio_readinitb(rio_t *rp,int fd); //将程序的内部缓存区与描写叙述符相关联。
ssize_t rio_readlineb(rio_t *rp,void *usrbuf,size_t maxlen); /*从内部缓存区读出一个文本行至buf中。以null字符来结束这个文本行。当然,
每行最大的字符数量不能超过MAXLINE。*/
int open_clientfd(char *hostname, int portno);
int open_listenfd(int portno);
#endif
void doit(int fd);
void read_requesthdrs(rio_t *rp); //读并忽略请求报头
int parse_uri(char *uri, char *filename, char *cgiargs); //解析uri,得文件名称存入filename中,參数存入cgiargs中。
void serve_static(int fd, char *filename, int filesize); //提供静态服务。
void get_filetype(char *filename, char *filetype);
void serve_dynamic(int fd, char *cause, char *cgiargs); //提供动态服务。
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg);
/*
Tiny是一个迭代服务器,监听在命令行中确定的端口上的连接请求。
在通过open_listenedfd函数打开
一个监听套接字以后。Tiny运行典型的无限服务循环,重复地接受一个连接(accept)请求,运行事务(doit),
最后关闭连接描写叙述符(close)
*/
/*
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。
stat(filename,&sbuf) :将文件filename中的各个元数据填写进sbuf中,假设找不到文件返回0。
S_ISREG(sbuf,st_mode) :此文件为普通文件。
S_IRUSR & sbuf.st_mode :有读取权限。
*/
2.Tiny的main函数
int main(int argc, char const *argv[])
{
int listenfd, connfd, port, clientlen;
struct sockaddr_in clientaddr;
if(argc != 2) {
fprintf(stderr, "usage: %s
", 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);
}
} 3.Tiny的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;
rio_readinitb(&rio,fd);
rio_readlineb(&rio,buf,MAXLINE);
sscanf(buf,"%s %s %s",method,uri,version);
if(strcasecmp(method,"GET")) {
clienterror(fd,method,"501","Not Implemented","Tiny does not implement this method");
return;
}
read_requesthdrs(&rio);
is_static = parse_uri(uri,filename,cgiargs);
if(stat(filename,&sbuf) < 0) {
clienterror(fd,filename, "404", "Not found","Tiny coundn't find this file");
return;
}
if(is_static) {
if(!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) {
clienterror(fd,filename, "403", "Forbidden","Tiny coundn't read the file");
return;
}
serve_static(fd,filename,sbuf.st_size);
}
else {
if(!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) {
clienterror(fd,filename, "403", "Forbidden","Tiny coundn't run the CGI program");
return;
}
serve_dynamic(fd,filename,cgiargs);
}
}
/*
从doit函数中可知,我们的Tiny Webserver仅仅支持“GET”方法,其它方法请求的话则会发送一条错误消息。主程序返回
。并等待下一个请求。否则,我们读并忽略请求报头。
(事实上,我们在请求服务时,直接不用写请求报头就可以,写上仅仅是
为了符合HTTP协议标准)。
然后,我们将uri解析为一个文件名称和一个可能为空的CGI參数,而且设置一个标志位,表明请求的是静态内容还是动态
内容。通过stat函数推断文件是否存在。
最后,假设请求的是静态内容,我们须要检验它是否是一个普通文件,而且可读。条件通过,则我们server向客服端发送
静态内容。类似的,假设请求的是动态内容,我就核实该文件是否是可运行文件。假设是则运行该文件。并提供动态功能。
*/
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg)
{
char buf[MAXLINE],body[MAXBUF];
sprintf(body,"<html><title>Tiny Error</title>");
sprintf(body,"%s<body bgcolor=""ffffff"">
",body);
sprintf(body,"%s%s: %s
",body,errnum,shortmsg);
sprintf(body,"%s<p>%s: %s
",body,longmsg,cause);
sprintf(body,"%s<hr><em>The Web server</em>
",body);
sprintf(buf,"HTTP/1.0 %s %s
",errnum,longmsg);
rio_writen(fd,buf,strlen(buf));
sprintf(buf,"Content-type: text/html
");
rio_writen(fd,buf,strlen(buf));
sprintf(buf,"sContent-length: %d
",(int)strlen(body));
rio_writen(fd,buf,strlen(buf));
rio_writen(fd,body,strlen(body));
}
/*
向客户端返回错误信息。
sprintf(buf,"------------"):将字符串“------------”输送到buf中。
rio_writen(fd,buf,strlen(buf)):将buf中的字符串写入fd描写叙述符中。
*/5.Tiny的
void read_requesthdrs(rio_t *rp)
{
char buf[MAXLINE];
rio_readlineb(rp,buf,MAXLINE);
while(strcmp(buf,"
")) {
rio_readlineb(rp,buf,MAXLINE);
printf("%s", buf);
}
return;
}
/*
Tiny不须要请求报头中的不论什么信息。这个函数就是来跳过这些请求报头的,读这些请求报头,直到空行。然后返回。
*/
int parse_uri(char *uri, char *filename,char *cgiargs)
{
char *ptr;
if(!strstr(uri,"cgi-bin")) {
strcpy(cgiargs,"");
strcpy(filename,".");
strcat(filename,uri);
if(uri[strlen(uri)-1] == '/') {
strcat(filename,"home.html");
}
return 1;
}
else {
ptr = index(uri,'?');
if(ptr) {
strcpy(cgiargs,ptr+1);
*ptr = '