【高效server實踐】--memcached網絡框架剝離實踐

memcached通過epoll(使用libevent)實現異步服務,主要由"主線程"和多個"worker線程"構成,主線程負責監聽網絡鏈接,並且accept連接。當監聽到連接accept成功後,把連接句柄FD傳給其中的一個空閒work線程處理。空閒的worker線程接收到主線程傳過來的連接句柄DF後,將其加入自己的epoll監聽隊列並處理該連接的讀寫事件。
   博主從memcahced剝離了基於線程池的收發包框架代碼,linux下直接make編譯可運行:https://github.com/donaldhuang/memcached_network
       用libevent做事件監聽使用起來非常簡單:
  event_flags=EV_READ | EV_PERSIST;//監聽可讀事件
  base=(event_base*)event_init();//初始化

   event_set(event, sfd, event_flags, base_event_handler,argvs);

//創建監聽sfd句柄的可讀事件,處理函數是base_event_handler,argvs是傳入處理函數的參數

   event_base_set(base, event);//爲創建的事件event指定事件基地
   event_add(event, time_tv);//把事件加入到事件基地進行監聽,time_tv爲epoll沒有可讀事件的超時時間
   event_base_loop(main_base,flag);//進入監聽事件的循環,flag分爲阻塞和非阻塞
   1)主線程根據用戶的設置創建n個worker線程,每個線程有一個待處理的連接句柄隊列conn_queue,且每個線程都使用libevent監聽一個管道並設置了處理函數thread_libevent_process
    
threads=(LIBEVENT_THREAD*)calloc(nthreads,sizeof(LIBEVENT_THREAD));     //LIBEVENT_THREAD,加入libevent元素的thread結構 “數組”  
    if(!threads)
    {   
        perror("can't allocate thread des");
        exit(1);
    }       

    for(i=0;i<nthreads;i++)                                                 //設置thread和thread中的libevent所需屬性
    {   
        int fds[2];
        if(pipe(fds))                                                       //thread和主線程的通信pipe
        {   
            perror("can't create notify pipe");
            exit(1);
        }       

        threads[i].notify_receive_fd=fds[0];
        threads[i].notify_send_fd=fds[1];

        setup_event_thread(&threads[i]);                                    //設置thread和thread中的libevent所需屬性
        printf("init thread:%d\n",i);
    }       

    for(i=0;i<nthreads;i++)
    {   
        create_worker(worker_libevent,&threads[i]);                         //啓動thread
    }  

   2)主線程首先使用libevent監聽一個主端口。當有連接事件到達時,accept獲得一個連接句柄FD,把該FD封裝到一個CQ_ITEM,然後選擇一個空閒的Worker線程,將CQ_ITEM壓入Worker線程的待處理的連接句柄隊列中,並往Worker線程家庭的管道中寫入一個"c"字符。
 CQ_ITEM *item=cqi_new();    
     int tid=(last_thread+1)%settings.num_threads;           //輪詢選出workerThread(數組)
     LIBEVENT_THREAD *thread=workerThreads->threads+tid;
    last_thread=tid;
    item->sfd=sfd;                                          //封裝必要的信息到item結構,後面會利用item封裝爲conn
     item->init_state=init_state;
    item->event_flags=event_flags;
    item->read_buffer_size=read_buffer_size;
    item->transport=transport;
 write(thread->notify_send_fd,"c",1);//主線程和workerThread的通信方式,寫入到notify_send_fd告訴輔助線程item準備好了,可以處理
     last_thread++;

   3) Worker線程監聽到了管道可讀事件,觸發thread_libevent_process處理函數。從待處理的連接句柄隊列中取出一個CQ_ITEM,然後用libevent監聽CQ_ITEM裏面的連接句柄的可讀事件,並設置事件處理函數event_handler
     
  LIBEVENT_THREAD *me=(LIBEVENT_THREAD*)arg;
       CQ_ITEM *item;
       char buf[1];
       if(read(fd,buf,1)!=1)
          fprintf(stderr,"can't read from libevent pipe\n");
     item=cq_pop(me->new_conn_queue);
    if(NULL!=item)
     {   
          conn *c= conn_new (item->sfd,item->init_state,item->event_flags,
                item->read_buffer_size,item->transport,me->base);

          if(NULL==c)
         {   
            if( IS_UDP(item->transport))
             {   
                  fprintf(stderr,"can't listen for events on UDP\n");
                 exit(1);
              }   
                else
               {   
                      fprintf(stderr,"can't listen for events on fd %d\n",item->sfd);
                 close(item->sfd);
              }   
         }   
        else
       {   
             c->thread=me;
        }
         cqi_free(item);
     }
conn *conn_new(const int sfd,enum conn_states init_state,const int event_flags,
               const int read_buffer_size,enum network_transport transport,
struct event_base *base)
{
c->sfd=sfd;
event_set(&c->event,sfd,event_flags,event_handler,(void*)c);
   event_base_set(base,&c->event);
        c->ev_flags=event_flags;

        if(event_add(&c->event,0)==-1)
        {   
        if( conn_add_to_freelist(c))
            conn_free(c);
        perror("event_add");
        return NULL;
        }   
     return c;
} 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章