一个简易的reactor+线程池模型的服务器

Main函数
g_manager全局管理:
    1.创建内存池2K
    2.创建线程池,最小50个线程,最大1024,任务队列长度1024
    3.创建连接池,预先分配1024个连接,给每个连接初始化(包括每个连接单独拥有的内存池,读事件,写事件)
    4.创建epoll描述符,预先分配4096个epoll_event的空间
 
把listenfd放入到epoll的关注列表里,回调函数是add_conn,也就是说epoll线程每次倾听到新的写连接,main函数里就会执行add_conn函数,此函数会把accept此函数的任务加入到线程池中,让线程池的任何一个工作线程来执行,
 
accpet完以后,相当于多一条连接,我们把此连接加入到连接池,让epoll也关注此连接,每当此套接字有东西要写,main调用回调函数process_conn,此函数把work_request的任务加入到线程池中,由线程池完成任务
 
主循环里只需要不停地监听这些套接字,得到任务之后,把任务放到线程池里让计算线程计算即可,这就是reactor模式
 
线程池的工作线程需要读取request里的请求,得到参数,读取相应的文件,进行逻辑处理,然后把相应的结果通过send(client_fd)返回到客户端。
 
线程池
线程池主要包括一个结构体和三个函数,三个函数包括:创建线程池,向线程池添加一个任务,销毁线程池
结构体:
    每个线程的线程Id
    管理线程的id
    任务队列(任务队列满用条件变量,任务队列空用条件变量),这个条件变量可以通过pthead_cont_broadcast发布给所有线程
    存活的线程数
    忙碌的线程数
    即将销毁的线程数
 
创建线程池:
    1.从内存池中分配资源,包括给工作线程数组申请空间,给任务队列申请空间
    2.启动工作线程,工作线程执行一个死循环,
        任务列表里任务就取任务执行,因为此时消耗了一个任务,我们可以通知有新的任务可以进来。执行任务时,忙碌的线程数加一,任务结束后,忙碌的线程数减一
        如果没有任务且线程池仍在运行,那么本线程就进入条件变量wait状态,等待队列有数据并且被唤醒,此时如果线程池里线程个数大于最小值,则本线程可以结束(任务不多)
        如果线程池要结束,则每个线程都要关闭
   3.启动管理线程,当线程池不需要关闭时,管理线程执行一个死循环
        1.当任务数大于存活的线程数时创建新线程
        2.忙碌的线程*2<存活的线程时销毁多余的空闲线程
 
向线程池添加任务:
    如果任务队列已满,则条件阻塞等待pthread_cond_wait(&(pool->queue_not_full), &(pool->struct_lock));
    直到某个工作线程完成工作后,通过pthread_cond_broadcast广播,告诉队列目前可以插入任务,才会从阻塞中返回
    添加任务到任务队列里,并且广播告诉所有线程任务队列不为空(phread_cond_signal)
 
内存池
这是参考nginx里面的内存池
内存池里分别有一个next和一个large指针,next指向下一个小块内存,large指向下一个大块内存,大块内存由malloc自由分配,小块内存大小都是相同的
 
如上图,nginx的内存池实际是一个由ngx_pool_data_t和ngx_pool_s构成的链表,其中:
ngx_pool_data_t中:
last:是一个unsigned char 类型的指针,保存的是/当前内存池分配到末位地址,即下一次分配从此处开始。
end:内存池结束位置;
next:内存池里面有很多块内存,这些内存块就是通过该指针连成链表的,next指向下一块内存。
failed:内存池分配失败次数。
 
ngx_pool_s
d:内存池的数据块;
max:内存池数据块的最大值;
current:指向当前内存池;
chain:该指针挂接一个ngx_chain_t结构;
large:大块内存链表,即分配空间超过max的情况使用;
cleanup:释放内存池的callback
log:日志信息
 
二 内存池的基本操作
    1.创建内存池
        初始状态:last指向ngx_pool_data_t的结构体之后数据起始位置。end指向分配的整个size大小的内存的末尾
        分配的大小size不能超过4096,如果超过了也只能按照这个大小分配
    2.销毁内存池
        按照大块内存和小块内存,分别遍历free销毁
    3.重置内存池
        大块内存释放,小块内存的指针尾部last直接恢复至现在内存池的大小尾端位置
    4.从内存池申请内存
        如果申请的内存大于内存池的max值,则直接申请大内存,大内存单独保存,每申请一个位于最前端,就是内存池指向large的第一个
        如果小于则小内存池中分配内存。在分配内存前要做内存对齐,就是下一个元素的起始位置要是min(UKEY_POOL_ALIGNMENT,sizeof(下一个元素))的倍数,因此移动last,如果此内存池能分配则分配,否则调用下一个内存池。如果所有的内存池都不能分配,则新建一个内存池,同样在存放size之前要进行内存对齐操作,然后把size存进去。这里增加一个failed次数统计机制,把前面的所有内存池分配失败次数都加1,如果某个内存池分配失败次数大于4,则说明此内存池容量不多了,下次就不用找他来分配了。然后把本内存池加入next队列。
 
连接池
连接池:
    包含左右的连接,每个连接拥有一个内存池,它关注的读事件或写事件(包含它的fd,以及对应的事件发生时的回调函数)
 
每个连接的作用:
    设置连接,目的是把你的fd,关注的读写事件以及对应的回调函数,注册到epoll中,并且设置到本连接里,以便让epoll某个fd有可读或可写时,在main函数调用

猜你喜欢

转载自www.cnblogs.com/eelzhblog/p/11061882.html