搭建一个web服务器

搭建一个web服务器构建一个web服务器最近几天我根据一些参考书和网上的开源程序写了一个简单的HTTP服务器链接,这个HTTP服务器尽量用简单的代码完成一个服务器基本的功能,比如分析请求行,分析请求头,并且根据不同的请求方法给出响应。网上的开源程序很多,但是有的定义了很多自定义的结构体,有的作为c语言结构的程序,参

大家好,欢迎来到IT知识分享网。

构建一个web服务器

最近几天我根据一些参考书和网上的开源程序写了一个简单的HTTP服务器 链接,这个HTTP服务器尽量用简单的代码完成一个服务器基本的功能,比如分析请求行,分析请求头,并且根据不同的请求方法给出响应。网上的开源程序很多,但是有的定义了很多自定义的结构体,有的作为c语言结构的程序,参数要在各个文件中互相传递,有的实现很复杂,增加很多功能。我实现一个简单的服务器,不追求支持太多的功能,但是希望用OOP的思想把尽量多的参数封装在类里,调用成员来完成功能,同时也运用了一些c++11的功能,让整个程序可读性更高,而且易使用。整个程序借鉴了很多其他人程序中的思想,但是我也对他们进行了修改,满足需求。

一个基本的WEB服务器

作为一个基本的WEB服务器,它需要接收客户端的数据,并且解析客户端数据是否符合要求,如果符合要求是否能给出客户端需要的数据,如果不能应该如何告之客户端。并且用尽量少的开销完成。另一个需要注意的是,服务器一定面向多个客户端,可以接受多个客户端同时访问,给出每个客户端需要的内容。一个基本的HTTP请求如下
搭建一个web服务器
一个HTTP的响应如下
搭建一个web服务器
不考虑多个客户端同时向服务器请求,我们从客户端接收到的数据,第一个要做的是解析请求头,找到方法,uri,并且确认协议号。协议号之后就是请求头,请求头以空行结束,也就是我们只要找到这个空行就可以完成请求头的解析,并且继续解析请求体。但是很可能客户端一次不能发送完全部的数据,我们需要先解析一部分,有新的数据我们再解析后面的内容。这就可以用有限状态机解决,参考《Linux 高性能服务器编程》,我们有一个主状态机来记录解析的请求头还是请求行,从状态机记录是否得到完整的一行。如此,我们每次收到数据,如果得到完整的一行,把内容交给解析函数,如果还没有得到完整的一行,那么继续等待下次客户端的数据。
我们把每个客户端的所有信息封装到一个对象,这里面记录了这个客户端的所有信息,比如有限状态机的状态,请求的文件地址,请求头的内容等。
在这个封装连接信息的类里,我使用一个map建立了请求头和处理函数的映射,也就是只要在map的key中能查询到请求头,就可以快速执行处理,而且方便扩展,如果支持更多请求头,只需要增加处理函数并且加到map里。

Reactor模式和Proactor模式

我们以上考虑的都是单个客户端对服务器访问,如果有多个客户端,我们考虑多线程模式,一个经典的设计方法是Reactor,在这个模式下,我们使用IO复用,比如epoll,把sockfd注册到事件表,主线程发现sockfd可读,那么主线程接收新的连接,把连接分配给读线程,读线程读出内容,并且处理业务,并发送要的数据给写线程,写线程发送。
另一种经典的设计方法是Proactor,在这种模式下,主线程接收新的连接,并且将连接注册到事件表,同时等待读完成,在这种模式下,我们不需要自己读数据,只需要等待读完成,实现了异步IO。子线程负责业务。
在toy HTTP server中,我们用Reactor模式,但是读和写交给主线程,即主线程负责,接受新连接,把新的连接放到事件表。并且读出数据,并且发送给客户端数据。对于子线程来说,他们不知道读写操作,只关注业务。
在实现线程池中,我使用了c++11的thread类,可以很方便的创建线程,另外可以将类的非静态成员函数作为线程的handler,但是要显式的传入this指针。

定时器

定时器使用最小堆,也就是把下一个要触发的定时器放在顶部,每次触发ALARM,我们检查顶部的定时器是否到时,如果到就执行相应的函数。定时器是我认为不够完善的一个部分。如之前所说,每个连接都封装成一个类,类中拥有一个function类型成员timer_handler,指定这个连接的定时器的回调函数。这个成员可以通过bind函数绑定任意一个类成员函数。如果建立一个timer,我们就把这个连接的地址传入timer,定时以后,执行这个连接里的timer_handler绑定的函数,但是如果一个连接有多个定时器并且需要绑定不同的函数就不行,因为对于同一个实例timer_handler必须只能绑定的函数。同时,定时器类需要传入一个结构体,这个结构体包含了定时时间,连接对象指针一些信息,也就是说我们在连接的实例里,如果要建立一个定时器,需要建立一个定时器的结构体,同时把自己的指针传入,然后把这个结构体的指针传入到定时器的类。有两个问题,一个是如果连接的实例被释放(一般情况下不会)定时器绑定的结构体也会被释放,造成指针指向了一个释放的地址。第二个问题是,在连接实例里建立了定时器结构体,定时器类会调用定时器结构体绑定的实例的成员,这样循环绑定,感觉会造成很多不稳定的可能。参考其他程序的定时器,发现定时器类在完成定时后,会释放定时器结构体的内存,我修改为定时器结构体的内存由连接的实例释放,因为这个结构体是由连接的实例申请内存建立。

其他困难

在完成程序后,我使用webbench进行压力测试,但是发现不管多少客户端,全部访问都会失败,让我感觉很怪,使用HTTP调试发送都可以得到正确响应,但是放到webbench就出问题。我只能先理解以下webbench的程序,发现webench使用多进程来建立多个客户端,每个进程在规定时间内不停的访问服务器,并且看能不能得到响应,如果得到响应就进行下一次访问。我觉得问题不出在多客户端上,把客户端调整为1也会出问题。首先我把webbench所有关于多进程的代码删除,留下单进程观察,还是出现同样的问题,关键问题在,webbench不断读服务器发送的数据,直到读出长度为0代表成功,或者长度-1表示失败,然后关闭连接进行下一次连接。使用我的程序,webbench每次建立连接,第一次都能读数据,但是第二次就会失败。最后错误原因是在写程序时,使用setsockopt的SO_LINGER, 每次关闭socket,服务器会发送一个复位报文,马上关闭连接,这样不需要进行tcp断开连接的过程,也就是异常关闭,节省了断开连接的数据,新的连接可以使用这个资源,这样的异常关闭会让客户端认为错误,带来每次访问都失败,之后不使用setsockopt,恢复正常。

需要改进的地方

用更稳定的方法构建timer。
支持更多方法。
处理请求体。
支持更高的访问量。

reference

[1] 《Linux高性能服务器编程》
[2] 《图解HTTP》
[3] http://zyearn.com/blog/2015/05/16/how-to-write-a-server/
[4] https://blog.csdn.net/superbfly/article/details/72782264?utm_medium=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromMachineLearnPai2~default-1.baidujs&dist_request_id=1332048.21087.16195157602338031&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromMachineLearnPai2~default-1.baidujs

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/32576.html

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信