Chinaunix首页 | 论坛 | 博客
  • 博客访问: 104826
  • 博文数量: 29
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 47
  • 用 户 组: 普通用户
  • 注册时间: 2012-12-13 16:46
文章分类
文章存档

2014年(20)

2013年(4)

2012年(5)

我的朋友

分类: C/C++

2014-09-16 10:29:08

原文地址:简单web服务器 作者:tastesweet

  昨天看了一下boa的源码,简单的浏览了一遍,boa是非常优秀的嵌入式web服务器,虽然只支持单任务,但它的高效是其他web服务器不能比拟的,正好这段时间在学习socket编程,那么动手写一个简单的web服务器,应该是可行的,虽然自己写的肯定不会太好,但如果写一遍,那么对与web服务器的运行机制与底层原理会更加的清晰,那么开始写一个简单的web服务器吧。
   首先需要分析下http协议,可以在百度百科中查阅详细的资料,这里给出链接: ,
因为只实现简单的web服务,所以功能会简化很多,只对报文中常见的信息头进行解析,看看请求报文格式:
       
GET /index.html HTTP/1.1
Accept:image/gif.image/jpeg,*/*
Accept-Language:zh-cn
Connection:Keep-Alive
Host:localhost
Accept-Encoding:gzip,deflate

GET /index.html HTTP/1.1         包括请求的文件名, http协议版本
Accept:image/gif.image/jpeg,*/*  表示支持的文件类型
除了第一行是必须信息外,其他的都是非必选项目,可以具体的添加一些必须的项目
在同web服务器建立连接后,将可以把以上的报文以字符串的形式写入到socket描述符中,可以理解为文件的写入,而底层的东西已经有系统和tcp/ip协议栈完成了。对与底层的具体实现,显然不是这里的重点,具体可以参照其他书目。
 
这样,我们的web服务器受到了请求报文,就可以根据第一行的信息去读取文件信息,在把加入回复报文头部信息,一起发出去。看看应答报文格式:

HTTP/1.1 200 OK
Server:Apache Tomcat/5.0.12
Content-Type: text/html
Content-Length:28


Hello HTTP!
HTTP/1.1 200 OK : 该行信息中,需要注意的是数字200, 这是web服务器应答状态码,200表示正常,如果文件不存在,则返回404,具体应答状态码具体参照http协议说明。这里特别说明的是,对于不同的执行结构要返回不同的应答状态码。
   在上边的应答报文格式中,可以清楚的看到,在信息头部和文件头部之间是有一个空行相隔的,这是需要特别注意的,浏览器正式同个这个换行符来区分那是头信息、那么文件信息的。 
用一张图片来展示下http报文请求与应答的过程: 
  
 
 
 我们知道,在boa移植过程中,需要在/var/下,创建www目录,而这个目录就是boa默认的网页文件存放的根目录,这里我们是实现web服务器时,通过设定环境变量来制定网页文件存放的根目录,设定环境变量为:HTTP_DIR,在代码中,通过函数char *getenv("HTTP_DIR")来获取环境变量的内容。
  通过上边的叙述,简单的web服务器雏形已经完成,报文的请求与应答都可以完成,那么怎么来完成并发,也就是说可以同时相应多个客户端,我们可以通过多进程与多线程两种方法来实现。下边一一说明。
   采用多进程来完成web服务器的并发时,需要注意一些小的问题,我们知道,在进行fork()时,子进程会复制父进程运行空间内的所有内容,如果在父进程中已经完成了socket的创建、端口的绑定、listen()、accept(),那么在子进程中也会出现和父进程一样的网络socket描述符。先看代码吧。
 

if((pid = fork()) == 0)

{
    // Child close listening socket
    close(Socket_fd);
    ClientHandler(&acptSock);
    // Process done with this client, close it
    close(socket_fd);
    // Child exit
    exit(0);    
}

else

{    // Parent close the accepted connection
    close(connfd);
}

  显然可以看出,在子进程和父进程中都存在socket_fd描述符,如果在子进程中不关闭socket_fd,那么就在同时存在两个socket_fd,这样会存在隐患,设想下,父进程要退出,如果只有父进程拥有socket_fd,那么父进程close(socket_fd)后,子进程的网络连接也就断开了,但这里的实际情况是父进程和子进程都拥有socket_fd,父进程关闭socket_fd后,子进程仍然能够正常的通信,那么这个子进程就不受父进程控制了,成了不听老子话的小子了。所以一定要关闭子进程中的socket_fd。同样,对与文件描述符connfd,这是一个当前连接的描述符,正常情况下,子进程退出既关闭conn_fd,可如果在父子进程同时拥有conn_fd的情况下,子进程退出后,虽然关闭了conn_fd,但父进程中仍然存在conn_fd,实际子进程的连接并没有关闭。上述的情况在使用进程实现并发中需要注意,这些错误不是太容易发现。
  来看看通过线程实现的web服务并发:


conn_fd = accept(lstnSocket,(struct sockaddr *)&pin,&address_size);
if(conn_fd > 0)
{
          pthread_create(&thread_id, NULL,
          (void *)Client_Process,
          (void *)conn_fd);
}

void *Client_Process(void *arg)// 客户端服务程序

{
    int conn_fd = (int)arg;
    ClientHandler(&acptSock);
    close(conn_fd);
    return;
}

 通过线程来实现web服务器的并发思路是非常清晰的,在pthread_create中,只能传入一个参数,如果要传入多个参数,可以通过结构体传入。

   这样,一个支持多连接的简单web服务器就完成了,今天就写到这,明天加入以下功能:客户端登陆日志、错误信息日志,报文出错检测、加入默认主页服务,如果时间容许,加入对cgi的支持。

阅读(1100) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~