昨天看了一下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) |