整个项目采用B/S模式(浏览器-服务器模式),通过浏览器发送的method(只要包含GET和POST两种方法),server对此进行响应,最终通过html显示所得到的结果。
因为服务器同时处理多条连接,因此采用了多线程的结构。
HTTP是在TCP之上,它负责在发送端“生成针对目标Web服务器的HTTP请求报文”和在接收端“对对Web服务器请求的内容进行处理”,传输功能是由TCP完成的,因此首先我们要创建建监听tcp socket
1.服务器初始化
这里创建tcp sock过程与其他socket编程一致,分别为:socket–>bind–>listen。即创建tcp socket–>绑定端口号–>监听socket
在绑定端口号时,我们采用sockaddr_in的方式,另外为了实现端口的复用,采用了setsockopt中SO_REUSRADDR方式。
2.进行accept及多线程的建立
我们进行accept接收客户端的connect请求。
accept成功以后,我们使用pthread_create来进行创建线程,把socket托付给线程来进行操作。在线程处理的过程中,有一个问题就是线程等待,我们为了解决这个问题,我们使用线程分离,这样使得线程可以作为孤儿进程的形式托管给1号进程,当执行完毕以后,由1号进程来进行资源的回收。
每次收到一个请求都创建一个线程,之所以处理线程是让线程去处理请求,其中让一个线程处理请求,另一个线程可以去调accept处理下一个请求,两者互不耽误。
另外,不使用多进程是因为创建进程成本高;I/O多路复用也可以。
3.线程处理
在整个线程处理函数中,我们对HTTP的请求进行分析。
a)获取HTTP信息
线程处理函数中最主要的工作就是进行HTTP请求的分析,获取HTTP信息,因为HTTP是一种行文本格式协议,因此我们采取的方式是按行读取。
例如:在读取首行时,我们为了获取首行中有效信息–metnod和url,因此采取如图方式
就可以把HTTP请求的方法(GET或POST,目前只进行处理这两个),资源路径(url)和最后一个字段–HTTP 的版本信息均获得
在得到这些资源之后,我们现在要进行处理, 接下来就是考虑参数。HTTP请求经常会带一些参数,通过这些参数浏览器请求资源。
其中GET方法的资源是在url当中,POST方法的资源是在消息正文当中,这样我们也就能得到资源了。
参数形式举例:
?a=1&b=2 (均以‘?’开头,’?’后面是一对对用‘&’符号分割的键值对)
需要说明的是,我们项目的多线程HTTP服务器有三种情况:
- 非CGI:此时不带参数,返回主页给浏览器
- GET CGI:此时参数在url后,并且按照一定的规律连接,我们需要把参数提出来交给CGI程序去进行运算
- POST CGI:此时参数在消息正文当中,我们需要结合消息报头,提取参数
b)非CGI模式
我们这里先来看下非cgi方式,这种情况下,首先明白此时我们可以得到资源路径,这个资源路径其实就是根目录下的路径,默认我们去寻找根目录下的主页。
所以我们需要给资源加上index.html
然后我们把整个index.html的信息发送给scoket。
c)GET CGI模式
对于GET CGI模式,最重要的就是解析出url中的url_path和query_string。
如果是method==GET,并且没有query_string,就认为是静态页面
如果是method==GET,且有query_string,就可以根据query_string参数内容动态计算生成页面
d)POST CGI模式
对于POST CGI模式,此时参数在消息正文当中,我们需要结合消息报头,提取参数,那么就简单了,我们只用提取出消息报头中的content_length,(此处为了简单,只保留了content_length,其他的header内容直接就丢弃了), 然后获取到这个长度以后,我们就可以知道向socket读取多少长度的内容了,然后读取完以后我们就可以获得到参数,同样是按照“?”和“&”的形式进行组织的,我们取出这个内容,然后进行数据运算操作。
注意
在每次对于错误的处理时,我们都将统一返回404,还要注意的是此处只考虑短连接,而短连接的意思是每次浏览器给服务器端发送请求之前,都是新建立一个socket进行连接,如果响应写完了,就可以关闭new_sock。但因为此处是服务器主动断开连接,也就会进入TIME_WAIT状态,由于服务器可能短时间处理大量的连接,就会导致服务器上出现大量的TIME_WAIT.因此就设置setsockopt REUSERADDR来进行端口复用操作