优化版本的HTTP服务器项目

项目介绍

开发语言/环境:C语言,linux Centos6.5
项目分析:
1、基于 HTTP/1.1 版本的协议,支持长链接
2、采用 Reactor的时间处理模式和半同步/半反应堆的并发模式
3、使用了半同步/反应堆线程池,来提高创建销毁的时间
4、使用了I/O 复用中的 epoll模型
5、使用了时间堆来管理定时任务,使用 webbench来测试它在一分钟的连接数


项目的相关背景知识

背景:http协议被广泛使用,从移动端,pc端浏览器,http协议无疑是打开互联网应用窗口的重要协议,http在网络应用 层中的地位不可撼动,是能准确区分前后台的重要协议。
目标:对http协议的理论学习,从零开始完成web服务器开发,坐拥下三层协议,从技术到应用,让网络难点无处遁形
描述:采用C/S模型,编写支持中小型应用的http,并结合mysql,理解常见互联网应用行为,做完该项目,你可以从技术 上完全理解从你上网开始,到关闭浏览器的所有操作中的技术细节
技术特点:网络编程(TCP/IP协议, socket流式套接字,http协议) 多线程技术 cgi技术 shell脚本 mysql C api技术 使用多路转接技术进行升级
实现具体细节:
这里写图片描述

项目实现的大致流程

画流程图

HTTP项目实现的基本思路

1、http协议是基于TCP通信的协议,因此,实现web服务器的第一步至少要能实现两个主机不同进程之间的TCP通信。
2、接下来的部分就是比较主要的处理逻辑了,当服务器收到请求后,首先应该分析请求方法(因为web服务器是要支持cgi的,但请求方法不同处理cgi也不同,这里我们只处理GET和POST方法)。
3、当方法确定后,应该拿到请求的URL,这一步是为了我们后边能处理GET和POST方法的cgi(GET和POST的参数位置不同,GET的参数在URL中,POST的参数在请求正文中)
4、判断资源是否存在,如果存在,判断这个资源是一个目录、普通文件还是一个可执行程序。之前几步我们已经提取到URL以及参数。GET方法:如果没有参数,就直接将请求的资源返回(即进入非cgi模式运行);否则,进入cgi模式内部运行;只要是POST方法就需要支持cgi:直接进入cgi函数内部运行。
cgi模式:
这里写图片描述

上述这张图描述了运行cgi时的过程,首先服务器要从浏览器上读取参数,然后需要fork出一个子进程进行cgi部分的处理,父进程通过环境变量的方式将参数转交给子进程,子进程运行完成后,将结果交给父进程,父进程再将数据输出给浏览器。在这个过程中可以将父进程看作一个所谓的中间量,只进行了参数的转交,因此可以将子进程的输入输出文件描述符进行重定向,即子进程直接与浏览器“联系”。
父进程:
(1)创建两个管道,关闭相应的文件描述符
(2)POST方法:继续读取数据,直到读完POST的参数部分;GET方法直接从子进程读取结果
(3)将数据和方法全部交给子进程后,等待子进程返回结果
子进程:
(1)关闭适当的文件描述符
(2)对标准输入输出来进行重定向
(3)通过环境变量传递参数
(4)进行exec程序替换
非cgi模式:
进入非cgi模式时一定是GET方法且没有参数,此时进入echo_www()函数内部即可,该函数会将所请求的资源以html的格式返回给浏览器。

三、错误处理

3、 状态码
HTTP状态吗是用以表示服务器HTTP响应状态的3位数字代码。通过状态码就可以知道服务器是否正确的处理了请求。
响应状态码为200,表示成功响应,文本描述为OK。响应状态码有很多种。
1XX:接收的请求正在处理
2XX:请求正常处理完毕
3XX:需要进行附加操作以完成请求 ,重定向
4XX:服务器无法处理请求
5XX:服务器请求出错
eg:
(1)200 ok:客户端发来的http请求,被正确处理了
(2)204 No Content:表明请求结果被正确处理了,但是响应信息中没有响应正文。
(3)206 Partial Content:该状态码表示客户端对服务器进行了范围请求,而且服务器成功的执行了这部分GET请求,响应报文中包含由 Content-Range指定的实体内容范围:类似于视频加载
(4)301 Moved Permanently:永久性重定向 该状态码表示请求的资源已经被分配了新的URI,以后应使用新的URI
(5)302 Found 临时性重定向:目标资源被分配了新的URL,希望用户本次使用新的URI进行访问
(6)307 Temporary Redirect 临时重定向(与302有着相同含义,但307会遵照浏览器标准,不会从POST变成GET。)
302与307的区别:这两个其实差不多,只不过这两个在标准上通常会有一些不同,在重定向之后,发起二次请求时,会把GET方法转成POST方法,但是302/307怎么做跟浏览器有很大关系
(7)400 Bad Request 该状态码表明请求报文中存在语法错误,需要修改请求内容重新发送。
(8)403 Forbidden:该状态码表明浏览器所请求的资源被服务器拒绝了。
(9)404 Not Found:服务器上没有请求的资源。
(10) 500 Internal Server Error:表明服务器端在执行的时候发生了错误。
(11)Server Unavailable : 该状态码表明服务器目前处于超负载或正在进行停机维护状态。

HTTP项目具体实现细节

1、GET方法和POST方法
GET方法和POST方法的区别:
知道GET方法和POST方法,GET方法和POST理解的比较多一些,因为比较
常用。PUT方法,HEAD方法,DELETE方法,POTIONS等方法是否提供是不一定的,因为要考虑到服务器的安全性问题
(1)GET和POST在传参形式上有所不同,GET方法通过url传参,post方法通过请求正文传参
(2)以带参的GET方法和POST方式传参时,必须以cji方式进行处理
GET方法只有在传参时要以cgi方式运行,而POST方法全部都以cgi方式运行
(3)GET方法传参时,会把信息回显到浏览器上,POST方法传参时,会将参数放在正文里
(4) 在url上面传参,参数长度受限制;在正文上面传参,参数不受限制;
GET方法传参,将参数放在query_string的数组里;POST方法传参,用的是while循环
cookie和session的区别(cookie和session是根据无状态提出来的)
cookie,把用户的信息保存在用户本地,session把用户的信息保存在用户的服务器端,保存的是session ID
项目流程:
首先创建一个监听套接字,获得新链接,然后创建线程来处理处理套接字,handler_request函数来处理链接,
首先获得第一行,然后进行行分析,获得请求的方法,判断方法。GET/POST正常,并且cgi=1,其他的话就将头部清楚状态码设置为404
GET方法带参为cji模式;
用stat判断资源是否存在,若不存在,清除头部,若存在,且是目录,就将首页拼接上;存在且不是目录,判断是否具有可执行权限,如果有,就将cgi设置为1
如果不以cgi方式运行,就执行echo_www
math_cgi:被http执行,
cgi其实就是用http执行一个程序
创建套接字的方法:用startup函数创建一个监听套接字,其中startup函数中包含了创建套接字的基本步骤,下去需要好好体会
创建线程:pthread_create(),线程处理函数包含了对行的处理请求
对HTTP请求的处理:按行进行处理,遇到\n,\r\n,\r都要进行换行处理,里面使用了一个窥探函数,获得第一行之后,需要获得请求的方法和url
判断请求的方法为GET方法和POST方法:GET方法和POST方法只要传参了就要以cgi方法处理,POST方法,必须要以cji方式运行,GET是常规请求没有参数,不需要cgi运行
如何判断GET方法带参数:url中如果有?,说明带参数,?左边是资源,右边是参数
处理首页:一般首页名字叫做index.html,首页内容在w3cschool中找一个,判断最后一个字符是否是/,如果是,就拼上首页 strcat
判断资源是否存在:
int stat(const char *path, struct stat buf);//stat,可以获取文件属性信息,path表示请求的资源,buf表示资源的属性
struct stat {
dev_t st_dev; /
ID of device containing file /
ino_t st_ino; /
inode number /
mode_t st_mode; /
protection / //权限
nlink_t st_nlink; /
number of hard links / //硬链接
uid_t st_uid; /
user ID of owner /
gid_t st_gid; /
group ID of owner /
dev_t st_rdev; /
device ID (if special file) /
off_t st_size; /
total size, in bytes / //文件大小
blksize_t st_blksize; /
blocksize for file system I/O /
blkcnt_t st_blocks; /
number of 512B blocks allocated /
time_t st_atime; /
time of last access /
time_t st_mtime; /
time of last modification /
time_t st_ctime; /
time of last status change */
};
如果请求的资源是一个目录,就给每一个目录下面也创建一个首页index.html,将首页拼上
如果请求的资源是一个二进制程序,就将此二进制程序在服务器上跑完,然后将结果返回客户端
如果对方的服务器请求的是一个二进制可执行程序,就将此二进制程序在服务器上跑完,跑的这种方式就叫做cgi
检测是否是目录或者文件,stat中已经给出判断方法
The following POSIX macros are defined to check the file type using the st_mode field:

       S_ISREG(m)  is it a regular file?

       S_ISDIR(m)  directory?                        是否是目录

       S_ISCHR(m)  character device?

       S_ISBLK(m)  block device?

       S_ISFIFO(m) FIFO (named pipe)?

       S_ISLNK(m)  symbolic link?  (Not in POSIX.1-1996.)

       S_ISSOCK(m) socket?  (Not in POSIX.1-1996.)

//查看可执行权限
_IFIFO 0010000 FIFO
S_ISUID 0004000 set-user-ID bit
S_ISGID 0002000 set-group-ID bit (see below)
S_ISVTX 0001000 sticky bit (see below)
S_IRWXU 00700 mask for file owner permissions
S_IRUSR 00400 owner has read permission
S_IWUSR 00200 owner has write permission
S_IXUSR 00100 owner has execute permission
S_IRWXG 00070 mask for group permission
S_IRGRP 00040 group has read permission
S_IWGRP 00020 group has write permission
S_IXGRP 00010 group has execute permission
S_IRWXO 00007 mask for permissions for others (not in group)
S_IROTH 00004 others have read permission
S_IWOTH 00002 others have write permission
S_IXOTH 00001 others have execute permission
如果三者任意一个拥有权限,就将cji设置为1
如果是cgi:以cgi方式进行处理
处理非cgi:
如果不是cgi(资源找到了,不是目录,没有可执行权限,不是cgi):就将资源响应回去,用echo_www进行响应
响应时构建:不能直接返回,而是首先构建状态行,响应报头,空行,正文
以上都构建好之后使用sendfile函数高效发送
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count); //将文件一个文件拷贝到另一个文件,此函数速度快,直接从内核拷贝,效率高
图片不能具有可执行权限,否则就要按cgi方式运行
处理cgi
exe_cgi()
GET:直接清掉报头
POST:要一直读,读到空行,空行部分后面才是正文,正文后面时参数;如果正文多读或者少读都会遇到黏包问题
怎么读:先将报头中所有的字段全部清除掉,从报头中提取Content-Length字段,然后下次连读n个字符
linux下怎么执行一个指定路径下 的可执行程序:进程的程序替换,exec系列函数
path标识了要执行的程序,目前是由一个线程完成的,这样是不行的,执行可执行程序时要进行程>
序替换,如果单进成的话,会替换web服务器的代码,所以需要调用fork创建子进程,在进行程序替换
父进程如何获取子进程:进程间通信
环境变量、重定向、创建父子进程,进程间通信、进程程序替换,
//环境变量会被子进程继承,环境变量不会因程序替换被替换掉
数据库相关问题
连接数据库: 切换root用户 service mysqld start
如何判断数据库是否启动:netstat -nltp
mysql -uroot -p //mysql相当于客户端
service mysqld start //mysqld想当于服务器
sshd //相当于服务器
ssh //相当于客户端
用cgi连接数据库:
comm.c comm.h //连接数据库/断开数据库
insert_cgi.c //向数据库中插入数据
创建的数据库名为class_23,
通过浏览器提交数据到insert_cgi,inset_cgi将数据插入到后台的库当中

http项目分为三个大部分:
http请求:对请求过程中的请求行进行分析处理,确定url,确定要访问的资源,并确定资源是否存在,可以访问的情况下,将资源返回回去
返回去的资源并不简单的将网页返回回去,而是要构建一个http的响应报头,响应报头包括状态行,响应报头,空行,正文,只有请求完处理完才能给出响应
项目缺点:
(1)单进程多线程服务器,每次只能处理一个客户的请求,当客户量增多时,服务器的处理能力是有限度的;(多线程的优缺点http都有)
(2)功能缺失,仅支持GET方法和POST方法,Web服务器上面资源太少
(3)访问量增多会导致服务器压力变大,导致IO效率降低,对外服务降低
遇到的一些问题
1、本地环回测试ok,Linux下的浏览器测试也可以,但不能接外部的浏览器访问(没有设置桥接模式)嗯~要是在外部浏览器测试的话千万别忘记关闭防火墙
2、服务器应答时,没有将html格式的页面发送,而是将底层的实现代码展示在浏览器,并且在调试时将本来要打印的调试信息会打印到网页上(在回应空行时将send期望发送的数值写的太大,本来只需要发送两个字节的内容)
解决:先检查代码,思路正确,在容易出现问题的地方加入调试信息,最后将问题定位在echo_www()函数内
3、不能显示图片(这个问题是没有将所有发送的情况考虑完全,只考虑到目录、可执行程序,但没有考虑到如果请求的是一个路径明确的普通文件)
解决:测试请求一个路径明确的test.html文件,加入调试信息 ,将问题定位在:如果请求的资源存在,应该如何处理。对于普通文件,找到后并回显给浏览器;如果是目录,应答的是默认页面;如果是可执行程序,执行后返回结果
4、能显示图片后,但显示的不完整(原因:echo_www中,期望读取一行信息的line值太小,不能存下一张图片)
5、运行cgi模式时,每次提交数据并进行submit后都会自动出现提醒下载的页面
6、请求没有处理完,就开始相应;忽略掉空行

原因:在响应报头中,将Content-Type中的”text”写成”test”。而浏览器对于不能识别或解析的实体,都会提醒用户下载。
与HTTP项目有关的一些小细节
cookie与session的区别
https://blog.csdn.net/wyn126/article/details/82700817
Webbench测Web服务器
https://blog.csdn.net/wyn126/article/details/82559337
HTTP的长连接与短连接
https://blog.csdn.net/wyn126/article/details/81868550
IO多路转接之epoll
https://blog.csdn.net/wyn126/article/details/80557005
TCP可靠性传输
https://blog.csdn.net/wyn126/article/details/80472411
三次握手四次挥手
https://blog.csdn.net/wyn126/article/details/80395291

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值