代码开源:
https://github.com/PetterZhukov/webserver_HTTP
如下展示的是我在进行代码复现和整理的时候进行的修改和模块化的过程和思考
具体的操作可以查看我在github上的commit
Commits · PetterZhukov/webserver_HTTP · GitHub
目录
1. 计算地址的时候减少依赖——使用iovec数组的iov_base
2. 计算长度的时候减少依赖——使用iovec数组的iov_len
9.4 unordered_map与tuple的使用(生成响应报文的时候的代码复用)
9.5 模块抽象:将Write和Read提取出http_conn
9.1 将线程池中的请求队列分离
最开始的线程池和请求队列是一体的,二者的变量并列放在一起,相比起来,我觉得将线程池当初做一个抽象,将其变为上下层级关系,比较易于理解,可读性也比较好,于是对其做了修改。
9.2 将分散写的两个部分明确定义
分散写writev一开始是为了将内存映射准备的,因为对内存映射来说,使用一个大的缓冲区将正文以外的部分和正文拼合在缓冲区中再发送无疑是很不现实的,一个是内存映射的大小无法确定,另一个是再次拷贝再发送很占内存,因此使用分散写writev是必须的。
但是这样也形成一种割裂的情况,因为对于那些错误信息来说,它实际上就不需要调用writev了,老师的原码是加上了一个add_content来将其与状态行、响应头一起,都放在写缓冲区中,然后分散写依赖的iv_count直接定义为1,将其作为普通的write来使用。
我感觉这样成功与不成功二者不够统一,同时也需要使用额外的函数,于是我将定义明确为:writev写的第一部分是状态行与响应头,存放在写缓冲区中,第二部分是响应正文,不论是内存映射还是错误信息。这样iv_count的值都是2,add_content也不需要增加了。
9.3 继9.2 在Write中减少依赖的代码
1. 计算地址的时候减少依赖——使用iovec数组的iov_base
源码来计算地址的时候会用到address_mmap(即内存映射的地址),因为9.2中我希望地址有多种情况,既可以是内存映射地址,也可以指向错误信息,因此这里需要修改,同时,在有iovec数组作为存储信息,还使用之前的地址来辅助计算,这样会提高模块之间的耦合(这方面在9.5也会提到)
因此我希望修改只需要修改iovec数组中的iov_base指针,实际上这样也很容易做到,只需要分析writev的返回值即可,在写成功的情况下其会返回写的字符的长度,通过其返回值ret对iovec进行修改即可。
2. 计算长度的时候减少依赖——使用iovec数组的iov_len
同理,因为报文可能分多次发送,因此需要有两个值:byte_to_send和byte_have_send,一个存储需要发送的报文长度,一个存储已经发送的报文长度。
经过我的修改,我将这两个值的作用也转移到了iovec数组中,其本来就有一个成员iov_len表示本元素指向的位置的长度,我只需要调整iov_len即可,就不需要额外的值了,同样降低了耦合性。
9.4 unordered_map与tuple的使用(生成响应报文的时候的代码复用)
在上一节我提到了使用unordered_map与tuple,相比于使用switch,将其用哈希表存储起来,将错误值作为key从中提取具体信息,对代码复用和可读性都有提升(tuple居然有内置hash函数,本来打算手写一个的)
9.5 模块抽象:将Write和Read提取出http_conn
在阅读完源码后我就有这个冲动,第1.3.两个更改主要都是基于这个修改,因为假如他们都在一个模块,那耦合性其实也是不那么重要了,但是当我把Read和Write提取出来放入epoll_class(也就是主线程的抽象)中时,我需要添加set和get作为接口,这时候使用的变量数量就显得非常明显。
至于为什么将其拆分有两个方面的原因,一个就是根据Proactor模式,I/O应该由主线程使用,只是一个简单的调用对其展现的不是很明显,第二个就是http_conn类太大太冗杂了,我觉得将其进行拆分可以很大地提高可读性。
如下是详细描述
将conn中的write和read抽出来,放入到epoll的封装中
原因是我将conn抽象成子线程对应的操作,而write和read对应的是主线程的读写操作,因此应该取出。
同时,write和read设计网络通信,其应该由epoll完成,而conn只需专心实现对写好内存的业务处理即可
不过再想到最开始的架构,conn其实可以抽象成对内存的操作的集合,而conn在子线程内运行的部分被视作子线程的业务逻辑,而在主线程运行的部分(write和read)被视作主线程的操作,这其实也是非常自洽的
不过虽然将read和write拆分出去,增加了一部分耦合性(要多开放一些私有变量的读取和修改接口),但是同时它明确了write和read会对conn的内存进行修改的具体部分,使得理解和调试变得更为容易,同时如上文所言,也比较符合我对架构的理解,因此我坚持这样修改。