一、背景

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

可是假设在測试的过程中会遇到以下的情况,后台显示一直在刷新

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvenp1Y3NsaWFuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

我想可能是由于这个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\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);
}
}

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向客服端发送
静态内容。类似的,假设请求的是动态内容,我就核实该文件是否是可运行文件。假设是则运行该文件。并提供动态功能。 */

4.Tiny的clienterrorh函数

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"">\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 Web server</em>\r\n",body); sprintf(buf,"HTTP/1.0 %s %s\r\n",errnum,longmsg);
rio_writen(fd,buf,strlen(buf));
sprintf(buf,"Content-type: text/html\r\n");
rio_writen(fd,buf,strlen(buf));
sprintf(buf,"sContent-length: %d\r\n\r\n",(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,"\r\n")) {
rio_readlineb(rp,buf,MAXLINE);
printf("%s", buf);
}
return;
}
/*
Tiny不须要请求报头中的不论什么信息。这个函数就是来跳过这些请求报头的,读这些请求报头,直到空行。然后返回。
*/

6.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 = '\0';
}
else {
strcpy(cgiargs,"");
}
strcpy(filename,".");
strcat(filename,uri);
return 0;
}
}
/*
依据uri中是否含有cgi-bin来推断请求的是静态内容还是动态内容。 假设没有cgi-bin。则说明请求的是静态内容。 那么
。我们需把cgiargs置NULL,然后获得文件名称,假设我们请求的uri最后为 “/”。则自己主动加入上home.html。比方说,我
请求的是“/”,则返回的文件名称为“./home.html”,而我们请求“/logo.gif”,则返回的文件名称为“./logo.gif”。 假设
uri中含有cgi-bin。则说明请求的是动态内容。 那么,我们须要把參数复制到cgiargs中,把要运行的文件路径写入
ilename。举例来说,uri为/cgi-bin/adder? 12&45,则cigargs中存放的是12&45,filename中存放的是
“./cgi-bin/adder” index(uri,'? ') : 找出uri字符串中第一个出现參数‘?’的地址。并将此地址返回。 */

7.Tiny的serve_static函数

void serve_static(int fd, char *filename, int filesize)
{
int srcfd;
char *srcp,filetype[MAXLINE],buf[MAXBUF]; get_filetype(filename,filetype);
sprintf(buf,"HTTP/1.0 200 OK\r\n");
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)); srcfd = open(filename,O_RDONLY,0);
srcp = mmap(0,filesize, PROT_READ, MAP_PRIVATE,srcfd,0);
close(srcfd);
rio_writen(fd,srcp,filesize);
munmap(srcp,filesize);
}
/*
打开文件名称为filename的文件,把它映射到一个虚拟存储器空间,将文件的前filesize字节映射到从地址srcp開始的
虚拟存储区域。关闭文件描写叙述符srcfd,把虚拟存储区的数据写入fd描写叙述符。最后释放虚拟存储器区域。 */
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/jpg");
else
strcpy(filetype,"text/plain");
}

8.Tiny的server_dynamic函数

void serve_dynamic(int fd, char *filename, char *cgiargs)
{
char buf[MAXLINE],*emptylist[] = {NULL}; 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) {
setenv("QUERY_STRING",cgiargs,1);
dup2(fd,STDOUT_FILENO);
execve(filename,emptylist,environ);
}
wait(NULL);
}
/*
Tiny通过派生一个子进程并在子进程的上下文中执行一个cgi程序(可执行文件)。来提供各种类型的动态内容。 setenv("QUERY_STRING",cgiargs,1) :设置QUERY_STRING环境变量。 dup2 (fd。STDOUT_FILENO) :重定向它的标准输出到已连接描写叙述符。此时。不论什么写到标准输出的东西都直接写到client。 execve(filename,emptylist,environ) :载入执行cgi程序。
*/

9.一些其它的函数

ssize_t rio_writen(int fd, void *usrbuf, size_t n)
{
size_t nleft = n;
ssize_t nwritten;
char *bufp = usrbuf;
while (nleft > 0) {
if ((nwritten = write(fd, bufp, nleft)) <= 0) {
if (errno == EINTR)
nwritten = 0;
else
return -1;
}
nleft -= nwritten;
bufp += nwritten;
}
return n;
}
static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n)
{
int cnt;
while (rp->rio_cnt <= 0) { /* 假设缓存区空,则又一次填充 */
rp->rio_cnt = read(rp->rio_fd, rp->rio_buf, sizeof(rp->rio_buf));
if (rp->rio_cnt < 0) {
if (errno != EINTR)
return -1;
}
else if (rp->rio_cnt == 0) /* EOF */
return 0;
else
rp->rio_bufptr = rp->rio_buf; /* 又一次设置缓存区指针 */
}
/* 从内部缓存区拷贝 min(n, rp->rio_cnt) 个字节到usrbuf*/
cnt = n;
if (rp->rio_cnt < n)
cnt = rp->rio_cnt;
memcpy(usrbuf, rp->rio_bufptr, cnt);
rp->rio_bufptr += cnt;
rp->rio_cnt -= cnt;
return cnt;
}
void rio_readinitb(rio_t *rp, int fd)
{
rp->rio_fd = fd;
rp->rio_cnt = 0;
rp->rio_bufptr = rp->rio_buf;
}
ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen)
{
int n, rc;
char c, *bufp = usrbuf;
for (n = 1; n < maxlen; n++) {
if ((rc = rio_read(rp, &c, 1)) == 1) {
*bufp++ = c;
if (c == '\n')
break;
}else if (rc == 0) {
if (n == 1)
return 0; /* EOF, no data read */
else
break; /* EOF, some data was read */
} else
return -1; /* error */
}
*bufp = 0;
return n;
}
int open_clientfd(char *hostname, int port)
{
int clientfd;
struct hostent *hp;
struct sockaddr_in serveraddr;
if ((clientfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return -1;
if ((hp = gethostbyname(hostname)) == NULL)
return -2;
bzero((char *) &serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
bcopy((char *)hp->h_addr_list[0],
(char *)&serveraddr.sin_addr.s_addr, hp->h_length);
serveraddr.sin_port = htons(port);
if (connect(clientfd, (SA *) &serveraddr, sizeof(serveraddr)) < 0)
return -1;
return clientfd;
}
int open_listenfd(int port)
{
int listenfd, optval=1;
struct sockaddr_in serveraddr; /* 创建一个套接字描写叙述符 */
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return -1; /* Eliminates "Address already in use" error from bind. */
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,
(const void *)&optval , sizeof(int)) < 0)
return -1;
/* Listenfd will be an endpoint for all requests to port
on any IP address for this host */
bzero((char *) &serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons((unsigned short)port);
if (bind(listenfd, (SA *)&serveraddr, sizeof(serveraddr)) < 0)
return -1;
/* Make it a listening socket ready to accept connection requests */
if (listen(listenfd, LISTENQ) < 0)
return -1;
return listenfd;
}

Tiny server:小型Web服务器的更多相关文章

  1. C语言构建小型Web服务器

    #include <stdio.h> #include <sys/socket.h> #include <stdlib.h> #include <string ...

  2. Atitit.Gui控件and面板----web server区----- web服务器监控面板and控制台条目

    Atitit.Gui控件and面板----web server区----- web服务器监控面板and控制台条目 1. Resin4.0.22 1 2. 查看http连接数::Summary>& ...

  3. MINI_httpd移植,构建小型WEB服务器

    一.简介 目的:构建小型WEB站,具备SSL. mini_httpd is a small HTTP server. Its performance is not great, but for low ...

  4. HttpListener 实现小型web服务器

    HttpListener 实现web服务器 用于小型服务器,简单.方便.不需要部署. 总共代码量不超过50行. static void Main(string[] args) { //创建HTTP监听 ...

  5. 小型web服务器thttpd的学习总结(上)

    1.软件的主要架构 软件的文件布局比较清晰,主要分为6个模块,主模块是thttpd.c文件,这个文件中包含了web server的主要逻辑,并调用了其他模块的函数.其他的5个模块都是单一的功能模块,之 ...

  6. 小型web服务器thttpd的学习总结(下)

    1.主函数模块分析 对于主函数而言,概括来说主要做了三点内容,也就是初始化系统,进行系统大循环,退出系统.下面主要简单阐述下在这三个部分,又做了哪些工作呢. 初始化系统 拿出程序的名字(argv[0] ...

  7. 什么是 Web 服务器(server)

    首先我们来了解什么是服务器(server) Web服务器一般指网站服务器,是指驻留于因特网上某种类型计算机的程序,可以向浏览器等Web客户端提供文档,[1]也可以放置网站文件,让全世界浏览:可以放置数 ...

  8. 前端学HTTP之WEB服务器

    前面的话 Web服务器每天会分发出数以亿计的Web页面,它是万维网的骨干.本文主要介绍WEB服务器的相关内容 总括 Web服务器会对HTTP请求进行处理并提供响应.术语“Web服务器”可以用来表示We ...

  9. Web服务器集群搭建关键步骤纪要

    前言:本文记述了搭建一个小型web服务器集群的过程,由于篇幅所限,系统.软件的安装和基本配置我这里就省略了,只记叙关键配置和脚本内容.假如各位朋友想了解各软件详细配置建议查阅官方文档. 一 需求分析: ...

随机推荐

  1. 嵌入jetty到Java代码

    在做Demo实例时,使用的jetty版本号为8.x. 为了避免麻烦,将全部的包都导入到MyEclipse的lib文件夹下. 实例1:自己定义handler的服务器 package com.jetty. ...

  2. poll调用深入解析

    poll调用深入解析http://blog.csdn.net/zmxiangde_88/article/details/8099049 poll调用和select调用实现的功能一样,都是网络IO利用的 ...

  3. cocos2d-x环境的搭建之xcode-本人亲历成功搭建!

    cocos2d-x环境的搭建之xcode-本人亲历成功搭建! 写给大家的前言,在学习cocos2d-x的时候自己走了很多的弯路,也遇到了很多很多问题,不管是简单的还是困难的现在都慢慢的一步一步克服了, ...

  4. Charles_N:HTTP请求响应监听工具

    Charles:HTTP请求响应监听工具使用说明.doc   1.    介绍 Charles是一个HTTP代理服务器,HTTP监视器,反转代理服务器.它允许一个开发者查看所有连接互联网的HTTP通信 ...

  5. com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: SELECT command denied to user 'xxxx'@''

    这两天项目一直在报这个错误消息: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: SELECT command denied to ...

  6. Oracle管道函数(Pipelined Table Function)介绍

    一 概述: 1.管道函数即是能够返回行集合(能够使嵌套表nested table 或数组 varray)的函数,我们能够像查询物理表一样查询它或者将其  赋值给集合变量. 2.管道函数为并行运行,在普 ...

  7. State Design Pattern 状态设计模式

    设置好内部状态,然后依据不同的函数作为行为模式,进行状态转换. 有点像Finite Automata算法,两者的思想是一样的. 会Finite Automata,那么这个设计模式就非常easy了. # ...

  8. 关于WEB三层架构的思考

    1.MVC设计思想 MVC程序设计思想是眼下比較流行的WEB开发的模式,当中,M(model)是模型.即JavaBean,用来封装和保存数据:V(view)是视图,即JSP.用来显示内容:C(cont ...

  9. Android ble 蓝牙4.0 总结

    本文介绍Android ble 蓝牙4.0,也就是说API level >= 18,且支持蓝牙4.0的手机才可以使用,如果手机系统版本API level < 18,也是用不了蓝牙4.0的哦 ...

  10. linux+Qt程序如何打包发布

    源地址:http://zhidao.baidu.com/link?url=UTWEoXS21B4p1L5LJmYgGBMAr0dTdXfzmaGbWeltnwQLA3Uc9_K9RcDQFFIArbx ...