本项目是使用C++编写的基于事件驱动模型的Web Server. 目前支持静态GET请求以及CGI请求. 以及部分HTTP/1.1特性.
- 基于事件驱动模型
- Reactor风格
- 基于状态机的HTTP协议解析器
- 线程池实现web服务器
- 进程池实现cgi服务器
- 数据库连接池连接自己实现的数据库 TODO
Round Robin
生产者线程
lock (lock_task_list);
add (task_list, task);
unlock (lock_task_list);
消费者线程
lock (lock_task_list);
getfirst_task (task_list);
unlock (lock_task_list);
run();
使用mmap获取文件文件内容,提高IO效率0拷贝。 1、使用mmap需要注意的一个关键点是,mmap映射区域大小必须是物理页大小(page_size)的整倍数(32位系统中通常是4k字节)。原因是,内存的最小粒度是页,而进程虚拟地址空间和内存的映射也是以页为单位。为了匹配内存的操作,mmap从磁盘到虚拟地址空间的映射也必须是页。
2、内核可以跟踪被内存映射的底层对象(文件)的大小,进程可以合法的访问在当前文件大小以内又在内存映射区以内的那些字节。也就是说,如果文件的大小一直在扩张,只要在映射区域范围内的数据,进程都可以合法得到,这和映射建立时文件的大小无关。
3、映射建立之后,即使文件关闭,映射依然存在。因为映射的是磁盘的地址,不是文件本身,和文件句柄无关。同时可用于进程间通信的有效地址空间不完全受限于被映射文件的大小,因为是按页映射。
int fd = open( m_real_file, O_RDONLY );
/*起始地址(默认NULL) 指定内存段长度 内存段的访问权限 控制内存段内容被修改后程序的行为 被映射的文件描述符 从何处开始映射*/
m_file_address = ( char* )mmap( NULL, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0 );
/**
* 这个有被问到过,我当时没听清问题,理解成了关闭之后是否会解除映射关系,不会
* 此时数据已经被映射进内存了,关闭套接字不会有影响,但至于为什么之后还能落盘成功
* 我猜是因为内核处理脏页了
*
* 但是此处,我只是读而已,关闭了没有影响。
*/
close( fd );
简言之,惊群现象就是多进程(多线程)在同时阻塞等待同一个事件的时候(休眠状态),如果等待的这个事件发生,那么他就会唤醒等待的所有进程(或者线程),但是最终却只可能有一个进程(线程)获得这个时间的“控制权”,对该事件进行处理,而其他进程(线程)获取“控制权”失败,只能重新进入休眠状态,这种现象和性能浪费就叫做惊群。
为了更好的理解何为惊群,举一个很简单的例子,当你往一群鸽子中间扔一粒谷子,所有的各自都被惊动前来抢夺这粒食物,但是最终注定只可能有一个鸽子满意的抢到食物,没有抢到的鸽子只好回去继续睡觉,等待下一粒谷子的到来。这里鸽子表示进程(线程),那粒谷子就是等待处理的事件。
其实在linux2.6版本以后,linux内核已经解决了accept()函数的“惊群”现象,大概的处理方式就是,当内核接收到一个客户连接后,只会唤醒等待队列上的第一个进程(线程),所以如果服务器采用accept阻塞调用方式,在最新的linux系统中已经没有“惊群效应”了。
主线程负责连接客户端,接受任务,放置进任务队列中去,唤醒线程池工作线程,去处理事件。
这里关于模型的选择,proactor 采用的异步io,linux提供的异步IO, aio 目前主要针对文件IO,对网络套接字的支持并不够友好。 虽然网上有人用 reactor 模拟实现的异步过程,但这样的模型似乎有点复杂,遂选择reactor。
应用于心跳机制,检测对端是否关闭。 鉴于客户端比较多的场景下,使用时间轮定时容器,添加删除时间复杂度均为 O(1).
- timewait 的避免
- sigpipe信号的屏蔽
将所有事件集中起来统一处理,时间事件,IO事件,信号事件,将信号写进管道再由epoll监听。