LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 992|回复: 6

简单的web服务器.

[复制链接]
发表于 2010-5-14 20:21:13 | 显示全部楼层 |阅读模式
从自己的博客转过来的,不知道这样会不会被删帖的?
原文 http://godorz.cn/2010/05/a-simple-web-server/

一点知识

HTTP

客户端(浏览器)与Web服务器之间的交互主要包含客户端的请求和服务器的应答.请求和应答的格式在HTTP协议中有 相应的定义.其中,GET应该是使用最广的方法,即客户端请求某个文件,服务器做出响应.为了知道GET请求具体的格式,我们可以用telnet测试:

arthur@arthur-desktop:~$ telnet godorz.cn 80

然后敲入 GET /in.html HTTP/1.0 ,两个回车,得到响应如下:

这里只发送了一个请求,却接收到了多行返回.细节如下:

1.HTTP请求: GET

telnet创建了一个socket并调用connect来连接到Web服务器,服务器接受请求,并创建一个基于socket的从客户端终端到服务器的数据通道.

一个HTTP请求包含了3个字符串,第一个字符串是命令,第二个是参数,第三个是所使用的协议版本号..在该例中,GET为命令,以/in.html作是参数,HTTP/1.0为版本号.

2.HTTP应答: OK

服务器读取请求并返回响应,应答分为头部和内容两个部分.其中,头部以状态行起始,此例中为 HTTP/1.1 200 OK, 状态行第一个字符串为协议版本(HTTP/1.1),第二个串为返回码(200,如果文件不存在,则返回码为404),文本解释为OK..头部的其他部分为附加信息,包括服务器名,应答时间,数据类型以及连接类型等.最后,应答的其余部分为文件内容..

文件操作

文件属性

一旦得到pathname,那么我们就可以使用stat函数返回与此文件有关的信息结构.翻一下APUE,可以知道stat所含内容主要有:

    * mode_t st_mode; /* 文件类型和许可权限 */
    * uid_t st_uid; /* 用户所有者的ID */
    * gid_t st_gid; /* 所属组的ID */
    * off_t st_size; /* 所占字节数 */
    * time_t st_mtime; /* 文件最后修改时间 */

其中,需要注意的是st_mtime是time_t类型,我们可以用ctime将其转为字符串.

还有一个需要注意的是st_mode,它是一个16位的二进制数,文件类型和许可权限被编码在st_mode中..如下所示:

解码比较复杂,简单的说,就是用一系列的掩码来把st_mode的值转为ls -l要显示的字符串.原理是,文件类型在st_mode第一个字节的前四位,所以我们可以通过掩码来将其余部分置0,从而得到类型的值..至于许可权限, 它在st_mode的最低9位,同样可以用掩码得到.一个很简单的例子为:

if(S_ISDIR(mode))  str[0] = ‘d’;    /* 是否为目录?  */

其中,S_ISDIR为宏命令,终端man stat可以查到.

最后,怎样将用户/组 ID转换成字符串呢? 答案是,用getpwuid得到完整的用户列表,用getgrgid得到组列表,然后查询用户名/组名.
Web服务器

前面的知识储备已经足够编写一个最简单的Web服务器了,它只支持GET方法,接受请求行,跳过其余参数,然后处理请求和发送应答..

程序的构造如图,在一系列初始化后,主进程掉入死循环,阻塞等待客户端连接请求,一旦有connect,主进程将fork一个子进程处理请求,而主进程自身继续等待客户端连接请求..

上面的都没什么难的,unp第5章几乎就把实现给出来了..需要注意的是,为了杀死僵尸进程,我们可以使用waitpid等待结束的子进程(捕捉信号SIGCHLD)..如果accept()函数阻塞等待客户机调用connect()建立连接时,进程恰好捕捉到信号,那么accept在返回”-1″ 的同时将变量errno的值设置为EINTR.这和accept()函数执行失败是有区别的(仅返回-1,errno值不变).

还有一个需要注意的是,为了列出一个目录下的所有文件或者子目录,我们常常这么写:

while((direntp = readdir(dirPtr)) != NULL)
dosomething(direntp->d_name, …);

这里的direntp->d_name仅仅是当前目录下的文件名/子目录名,它并不是绝对地址,所以为了stat此文件,我们需要将它转为绝对地址,即  sprintf(realFilename, “%s/%s”, dirname, direntp->d_name);..

最后,当客户端请求一个文件时,最简单的做法当然是将文件所有内容一次性的往客户端连接描述字里写,但这又引出了一个问题,假如文件教大,那么不管是服务器还是客户端,都会出现内存消耗过多的局面,而且下载速度过慢..一个比较好的解决方法是,使用多线程下载..

下面写写我理解的多线程下载.

客户端发送GET请求时,可以使用”Range: bytes=r1 – r2″参数,服务器将只传送指定文件中从第r1个字节到r2个字节之间的内容.因此,在实现一个下载函数时,我们可以将文件均分为m段,然后 pthread_create多个线程,分别请求各段,在各线程都完成下载后,在本地将其拼接成一个完整的文件..多线程的下载还能带来一个好处,当网络连接出现问题时,我们可以将各线程的下载进度保存到文件中,作为断点信息,线程重启后从断点处重新开始下载.这样,我们就可以实现断点传送的特性了..

最后,一个简单web服务器运行如下,没有实现多线程下载………..

已详细注释的源代码在这..欢迎交流.

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
发表于 2010-5-14 23:30:06 | 显示全部楼层
对于大文件的问题,一般是通过创建一个固定大小的缓冲区,连续的用read或者fread读满,然后写入到socket,直到读到eof。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2010-5-14 23:58:44 | 显示全部楼层
Post by realtang;2090148
对于大文件的问题,一般是通过创建一个固定大小的缓冲区,连续的用read或者fread读满,然后写入到socket,直到读到eof。
thank you,回去学习下,呵呵.
回复 支持 反对

使用道具 举报

发表于 2010-5-16 18:24:41 | 显示全部楼层
不错! 谢谢!
回复 支持 反对

使用道具 举报

发表于 2010-5-18 02:00:15 | 显示全部楼层
对大文件传输,Linux提供了sendfile(),操作系统会自己进行文件读取和缓冲区分配,web server和ftp server适用。man 2 sendfile
回复 支持 反对

使用道具 举报

 楼主| 发表于 2010-5-18 18:37:56 | 显示全部楼层
Post by 没本;2090759
对大文件传输,Linux提供了sendfile(),操作系统会自己进行文件读取和缓冲区分配,web server和ftp server适用。man 2 sendfile
严重感谢>.
回复 支持 反对

使用道具 举报

发表于 2010-5-25 17:52:36 | 显示全部楼层
  1.     /* if dirname points to a file instead od a directory, then download it */
  2.     if (S_ISREG(info.st_mode))
  3.     {
  4.         readFd = open(dirname, O_RDONLY);
  5.         fileLen = lseek(readFd, 0, SEEK_END);
  6.         readBuffer = (char *) malloc(fileLen + 1);
  7.         bzero(readBuffer, fileLen + 1);
  8.         lseek(readFd, 0, SEEK_SET);
  9.         ret = read(readFd, readBuffer, fileLen);
  10.         close(readFd);
  11.         fprintf(lsFd, "HTTP/1.1 200 OK\r\nServer: Web Resource Viewer by Arthur1989\r\nConnection: keep-alive\r\nContent-type: application/*\r\nContent-Length:%d\r\n\r\n", fileLen);
  12.         fwrite(readBuffer, fileLen, 1, lsFd);
  13.         free(readBuffer);
  14.     }
复制代码

这里面你分配了一个文件大小的buffer, 如果文件足够大, 内存就不够用了。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表