Memcached源码分析 - 基于Libevent的网络模型实现(1)

                   

目录

一、Memcached简介

二、Memcached分析

1. 网络模型流程分析

2. 网络模型数据结构

3. main启动入口

4. 工作线程worker thread

5. 主线程main thread


一、Memcached简介


memcached是一款非常普及的服务器端缓存软件,memcached主要是基于Libevent库进行开发的。

如果你还不了解libevent相关知识,请先看我的libevent这篇文章《Linux c 开发 - libevent 》

memcached也是使用autotools的进行代码编译管理的,如果你还不了解autotools,你可以先看下文章:《Linux c 开发 - Autotools使用详细解读 》

memcached你可以去官网上获取源代码。官网地址

笔者这边分析的memcached版本是1.4.21版本,你也可以通过我的《环境安装系列 - Linux下 Memcache的安装和简单管理》去下载

 

二、Memcached分析


1. 网络模型流程分析


Memcached主要是基于Libevent的事件库来实现网络线程模型的。我们先需要下载memcached的源码包,上面我们已经给出了源码包下载地址。

Memcached的网络线程模型主要涉及两个主要文件:memcached.cthread.c文件。

我们这边主要分析tcp的模型。memcached也支持udp。

流程

  • memcached首先在主线程中会创建main_base,memcached的主线程的主要工作就是监听和接收listen和accpet新进入的连接。
  • 当memcached启动的时候会初始化N个worker thread工作线程,每个工作线程都会有自己的LIBEVENT_THREAD数据结构来存储线程的信息(线程基本信息、线程队列、pipe信息)。worker thread工作线程和main thread主线程之间主要通过pipe来进行通信。
  • 当用户有连接进来的时候,main thread主线程会通过求余的方式选择一个worker thread工作线程。
  • main thread会将当前用户的连接信息放入一个CQ_ITEM,并且将CQ_ITEM放入这个线程的conn_queue处理队列,然后主线程会通过写入pipe的方式来通知worker thread工作线程。
  • 当工作线程得到主线程main thread的通知后,就会去自己的conn_queue队列中取得一条连接信息进行处理,创建libevent的socket读写事件。
  • 工作线程会监听用户的socket,如果用户有消息传递过来,则会进行消息解析和处理,返回相应的结果。

流程图

2. 网络模型数据结构


CQ_ITEM:主要用于存储用户socket连接的基本信息。

主线程会将用户的socket连接信息封装成CQ_ITEM,并放入工作线程的处理队列中。工作线程得到主线程的pipe通知后,就会将队列中的ITEM取出来,创建libevent的socket读事件。


  
  
  1. /* An item in the connection queue. */
  2. typedef struct conn_queue_item CQ_ITEM;
  3. struct conn_queue_item {
  4.     int               sfd;   //socket的fd
  5.     enum conn_states  init_state; //事件类型
  6.     int               event_flags; //libevent的flags
  7.     int               read_buffer_size; //读取的buffer的size
  8.     enum network_transport     transport;
  9.     CQ_ITEM          *next; //下一个item的地址
  10. };

CQ:每个线程的处理队列结构。


  
  
  1. /* A connection queue. */
  2. typedef struct conn_queue CQ;
  3. struct conn_queue {
  4.     CQ_ITEM *head;
  5.     CQ_ITEM *tail;
  6.     pthread_mutex_t lock;
  7. };

LIBEVENT_THREAD:每个工作线程的数据结构。

每一个工作线程都有有这么一个自己的数据结构,主要存储线程信息、处理队列、pipe信息等。


  
  
  1. typedef struct {
  2.      //线程ID
  3.     pthread_t thread_id;        /* unique ID of this thread */
  4.     //线程的 event_base,每个线程都有自己的event_base
  5.     struct event_base *base;    /* libevent handle this thread uses */
  6.     //异步event事件
  7.     struct event notify_event;  /* listen event for notify pipe */
  8.     //管道接收端
  9.     int notify_receive_fd;      /* receiving end of notify pipe */
  10.     //管道发送端
  11.     int notify_send_fd;         /* sending end of notify pipe */
  12.     //线程状态
  13.     struct thread_stats stats;  /* Stats generated by this thread */
  14.     //新连接队列结构
  15.     struct conn_queue *new_conn_queue; /* queue of new connections to handle */
  16.     cache_t *suffix_cache;      /* suffix cache */
  17.     uint8_t item_lock_type;     /* use fine-grained or global item lock */
  18. } LIBEVENT_THREAD;

3. main启动入口


我们需要找到memcached.c中的main()方法。下面的代码中只列出了我们需要的重要部分。


  
  
  1. int main (int argc, char **argv) {
  2.     //...省去一部分代码
  3.     /* initialize main thread libevent instance */
  4.     //初始化一个event_base
  5.     main_base = event_init();
  6.     /* initialize other stuff */
  7.     stats_init();
  8.     assoc_init(settings.hashpower_init);
  9.     conn_init();
  10.     slabs_init(settings.maxbytes, settings.factor, preallocate);
  11.     /*
  12.      * ignore SIGPIPE signals; we can use errno == EPIPE if we
  13.      * need that information
  14.      */
  15.     if (sigignore(SIGPIPE) == -1) {
  16.         perror( "failed to ignore SIGPIPE; sigaction");
  17.         exit(EX_OSERR);
  18.     }
  19.     /* start up worker threads if MT mode */
  20.     //这边非常重要,这个方法主要用来创建工作线程,默认会创建4个工作线程
  21.     thread_init(settings.num_threads, main_base);
  22.     if (start_assoc_maintenance_thread() == -1) {
  23.         exit(EXIT_FAILURE);
  24.     }
  25.     if (settings.slab_reassign &&
  26.         start_slab_maintenance_thread() == -1) {
  27.         exit(EXIT_FAILURE);
  28.     }
  29.     /* Run regardless of initializing it later */
  30.     init_lru_crawler();
  31.     /* initialise clock event */
  32.     clock_handler( 0, 0, 0);
  33.     /* create unix mode sockets after dropping privileges */
  34.     if (settings.socketpath != NULL) {
  35.         errno = 0;
  36.         if (server_socket_unix(settings.socketpath,settings.access)) {
  37.             vperror( "failed to listen on UNIX socket: %s", settings.socketpath);
  38.             exit(EX_OSERR);
  39.         }
  40.     }
  41.     /* create the listening socket, bind it, and init */
  42.     if (settings.socketpath == NULL) {
  43.         const char *portnumber_filename = getenv( "MEMCACHED_PORT_FILENAME");
  44.         char temp_portnumber_filename[PATH_MAX];
  45.         FILE *portnumber_file = NULL;
  46.         if (portnumber_filename != NULL) {
  47.             snprintf(temp_portnumber_filename,
  48.                      sizeof(temp_portnumber_filename),
  49.                      "%s.lck", portnumber_filename);
  50.             portnumber_file = fopen(temp_portnumber_filename, "a");
  51.             if (portnumber_file == NULL) {
  52.                 fprintf( stderr, "Failed to open \"%s\": %s\n",
  53.                         temp_portnumber_filename, strerror(errno));
  54.             }
  55.         }
  56.         errno = 0;
  57.         //这边的server_sockets方法主要是socket的bind、listen、accept等操作
  58.         //主线程主要用于接收客户端的socket连接,并且将连接交给工作线程接管。
  59.         if (settings.port && server_sockets(settings.port, tcp_transport,
  60.                                            portnumber_file)) {
  61.             vperror( "failed to listen on TCP port %d", settings.port);
  62.             exit(EX_OSERR);
  63.         }
  64.     }
  65.     /* enter the event loop */
  66.     //这边开始进行主线程的事件循环
  67.     if (event_base_loop(main_base, 0) != 0) {
  68.         retval = EXIT_FAILURE;
  69.     }
  70.   //...省去一部分代码
  71. }

主线程中主要是通过thread_init方法去创建N个工作线程:

thread_init(settings.num_threads, main_base); 
  
  

通过server_sockets方法去创建socket server:


  
  
  1.         errno = 0;
  2.         if (settings.port && server_sockets(settings.port, tcp_transport,
  3.                                            portnumber_file)) {
  4.             vperror( "failed to listen on TCP port %d", settings.port);
  5.             exit(EX_OSERR);
  6.         }

4. 工作线程worker thread


我们在thread.c文件中找到thread_init这个方法:


  
  
  1. void thread_init(int nthreads, struct event_base *main_base) {
  2.     //...省了一部分代码
  3.     //这边通过循环的方式创建nthreads个线程
  4.     //nthreads应该是可以设置的
  5.     for (i = 0; i < nthreads; i++) {
  6.         int fds[ 2];
  7.         //这边会创建pipe,主要用于主线程和工作线程之间的通信
  8.         if (pipe(fds)) {
  9.             perror( "Can't create notify pipe");
  10.             exit( 1);
  11.         }
  12.         //threads是工作线程的基本结构:LIBEVENT_THREAD
  13.         //将pipe接收端和写入端都放到工作线程的结构体中
  14.         threads[i].notify_receive_fd = fds[ 0]; //接收端
  15.         threads[i].notify_send_fd = fds[ 1]; //写入端
  16.         //这个方法非常重要,主要是创建每个线程自己的libevent的event_base
  17.         setup_thread(&threads[i]);
  18.         /* Reserve three fds for the libevent base, and two for the pipe */
  19.         stats.reserved_fds += 5;
  20.     }
  21.     /* Create threads after we've done all the libevent setup. */
  22.     //这里是循环创建线程
  23.     //线程创建的回调函数是worker_libevent
  24.     for (i = 0; i < nthreads; i++) {
  25.         create_worker(worker_libevent, &threads[i]);
  26.     }
  27.     /* Wait for all the threads to set themselves up before returning. */
  28.     pthread_mutex_lock(&init_lock);
  29.     wait_for_thread_registration(nthreads);
  30.     pthread_mutex_unlock(&init_lock);
  31. }

setup_thread方法:


  
  
  1. /*
  2. * Set up a thread's information.
  3. */
  4. static void setup_thread(LIBEVENT_THREAD *me) {
  5.     //创建一个event_base
  6.     //根据libevent的使用文档,我们可以知道一般情况下每个独立的线程都应该有自己独立的event_base
  7.     me->base = event_init();
  8.     if (! me->base) {
  9.         fprintf( stderr, "Can't allocate event base\n");
  10.         exit( 1);
  11.     }
  12.     /* Listen for notifications from other threads */
  13.     //这边非常重要,这边主要创建pipe的读事件EV_READ的监听
  14.     //当pipe中有写入事件的时候,libevent就会回调thread_libevent_process方法
  15.     event_set(&me->notify_event, me->notify_receive_fd,
  16.               EV_READ | EV_PERSIST, thread_libevent_process, me);
  17.     event_base_set(me->base, &me->notify_event);
  18.     //添加事件操作
  19.     if (event_add(&me->notify_event, 0) == -1) {
  20.         fprintf( stderr, "Can't monitor libevent notify pipe\n");
  21.         exit( 1);
  22.     }
  23.     //初始化一个工作队列
  24.     me->new_conn_queue = malloc( sizeof(struct conn_queue));
  25.     if (me->new_conn_queue == NULL) {
  26.         perror( "Failed to allocate memory for connection queue");
  27.         exit(EXIT_FAILURE);
  28.     }
  29.     cq_init(me->new_conn_queue);
  30.     //初始化线程锁
  31.     if (pthread_mutex_init(&me->stats.mutex, NULL) != 0) {
  32.         perror( "Failed to initialize mutex");
  33.         exit(EXIT_FAILURE);
  34.     }
  35.     me->suffix_cache = cache_create( "suffix", SUFFIX_SIZE, sizeof( char*),
  36.                                     NULL, NULL);
  37.     if (me->suffix_cache == NULL) {
  38.         fprintf( stderr, "Failed to create suffix cache\n");
  39.         exit(EXIT_FAILURE);
  40.     }
  41. }

create_worker方法:


  
  
  1. /*
  2. * Creates a worker thread.
  3. */
  4. //这个方法是真正的创建工作线程
  5. static void create_worker(void *(*func)(void *), void *arg) {
  6.     pthread_t       thread;
  7.     pthread_attr_t  attr;
  8.     int             ret;
  9.     pthread_attr_init(&attr);
  10.     //这边真正的创建线程
  11.     if ((ret = pthread_create(&thread, &attr, func, arg)) != 0) {
  12.         fprintf( stderr, "Can't create thread: %s\n",
  13.                 strerror(ret));
  14.         exit( 1);
  15.     }
  16. }

worker_libevent方法:


  
  
  1. /*
  2. * Worker thread: main event loop
  3. */
  4. static void *worker_libevent(void *arg) {
  5.     LIBEVENT_THREAD *me = arg;
  6.     /* Any per-thread setup can happen here; thread_init() will block until
  7.      * all threads have finished initializing.
  8.      */
  9.     /* set an indexable thread-specific memory item for the lock type.
  10.      * this could be unnecessary if we pass the conn *c struct through
  11.      * all item_lock calls...
  12.      */
  13.     me->item_lock_type = ITEM_LOCK_GRANULAR;
  14.     pthread_setspecific(item_lock_type_key, &me->item_lock_type);
  15.     register_thread_initialized();
  16.     //这个方法主要是开启事件的循环
  17.     //每个线程中都会有自己独立的event_base和事件的循环机制
  18.     //memcache的每个工作线程都会独立处理自己接管的连接
  19.     event_base_loop(me->base, 0);
  20.     return NULL;
  21. }

thread_libevent_process方法:


  
  
  1. static void thread_libevent_process(int fd, short which, void *arg) {
  2.     LIBEVENT_THREAD *me = arg;
  3.     CQ_ITEM *item;
  4.     char buf[ 1];
  5.     //回调函数中回去读取pipe中的信息
  6.     //主线程中如果有新的连接,会向其中一个线程的pipe中写入1
  7.     //这边读取pipe中的数据,如果为1,则说明从pipe中获取的数据是正确的
  8.      if (read(fd, buf, 1) != 1)
  9.         if (settings.verbose > 0)
  10.             fprintf( stderr, "Can't read from libevent pipe\n");
  11.     switch (buf[ 0]) {
  12.     case 'c':
  13.     //从工作线程的队列中获取一个CQ_ITEM连接信息
  14.     item = cq_pop(me->new_conn_queue);
  15.     //如果item不为空,则需要进行连接的接管
  16.     if ( NULL != item) {
  17.       //conn_new这个方法非常重要,主要是创建socket的读写等监听事件。
  18.       //init_state 为初始化的类型,主要在drive_machine中通过这个状态类判断处理类型
  19.         conn *c = conn_new(item->sfd, item->init_state, item->event_flags,
  20.                            item->read_buffer_size, item->transport, me->base);
  21.         if (c == NULL) {
  22.             if (IS_UDP(item->transport)) {
  23.                 fprintf( stderr, "Can't listen for events on UDP socket\n");
  24.                 exit( 1);
  25.             } else {
  26.                 if (settings.verbose > 0) {
  27.                     fprintf( stderr, "Can't listen for events on fd %d\n",
  28.                         item->sfd);
  29.                 }
  30.                 close(item->sfd);
  31.             }
  32.         } else {
  33.             c->thread = me;
  34.         }
  35.         cqi_free(item);
  36.     }
  37.         break;
  38.     /* we were told to flip the lock type and report in */
  39.     case 'l':
  40.     me->item_lock_type = ITEM_LOCK_GRANULAR;
  41.     register_thread_initialized();
  42.         break;
  43.     case 'g':
  44.     me->item_lock_type = ITEM_LOCK_GLOBAL;
  45.     register_thread_initialized();
  46.         break;
  47.     }
  48. }

conn_new方法(主要看两行):


  
  
  1.     //我们发现这个方法中又在创建event了,这边实际上是监听socket的读写等事件
  2.     //主线程主要是监听用户的socket连接事件;工作线程主要监听socket的读写事件
  3.     //当用户socket的连接有数据传递过来的时候,就会调用event_handler这个回调函数
  4.     event_set(&c->event, sfd, event_flags, event_handler, ( void *)c);
  5.     event_base_set(base, &c->event);
  6.     c->ev_flags = event_flags;
  7.     //将事件添加到libevent的loop循环中
  8.     if (event_add(&c->event, 0) == -1) {
  9.         perror( "event_add");
  10.         return NULL;
  11.     }

event_handler方法:


  
  
  1. void event_handler(const int fd, const short which, void *arg) {
  2.     conn *c;
  3.     //组装conn结构
  4.     c = (conn *)arg;
  5.     assert(c != NULL);
  6.     c->which = which;
  7.     /* sanity */
  8.     if (fd != c->sfd) {
  9.         if (settings.verbose > 0)
  10.             fprintf( stderr, "Catastrophic: event fd doesn't match conn fd!\n");
  11.         conn_close(c);
  12.         return;
  13.     }
  14.     //最终转交给了drive_machine这个方法
  15.     //memcache的大部分的网络事件都是由drive_machine这个方法来处理的
  16.     //drive_machine这个方法主要通过c->state这个事件的类型来处理不同类型的事件   
  17.     drive_machine(c);
  18.     /* wait for next event */
  19.     return;
  20. }

然后继续看最重要的,也是核心的处理事件的方法drive_machine(状态机),监听socket连接、监听socket的读写、断开连接等操作都是在drive_machine这个方法中实现的。而这些操作都是通过c->state这个状态来判断不同的操作类型。


  
  
  1. static void drive_machine(conn *c) {
  2.      //....................
  3.     assert(c != NULL);
  4.     while (!stop) {
  5.         //这边通过state来处理不同类型的事件
  6.         switch(c->state) {
  7.         //这边主要处理tcp连接,只有在主线程的下,才会执行listening监听操作。
  8.          case conn_listening:
  9.             addrlen = sizeof(addr);
  10. #ifdef HAVE_ACCEPT4
  11.             if (use_accept4) {
  12.                 sfd = accept4(c->sfd, (struct sockaddr *)&addr, &addrlen, SOCK_NONBLOCK);
  13.             } else {
  14.                 sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen);
  15.             }
  16. #else
  17.             sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen);
  18. #endif
  19.             //......................
  20.             if (settings.maxconns_fast &&
  21.                 stats.curr_conns + stats.reserved_fds >= settings.maxconns - 1) {
  22.                 str = "ERROR Too many open connections\r\n";
  23.                 res = write(sfd, str, strlen(str));
  24.                 close(sfd);
  25.                 STATS_LOCK();
  26.                 stats.rejected_conns++;
  27.                 STATS_UNLOCK();
  28.             } else {
  29.                 dispatch_conn_new(sfd, conn_new_cmd, EV_READ | EV_PERSIST,
  30.                                      DATA_BUFFER_SIZE, tcp_transport);
  31.             }
  32.             stop = true;
  33.             break;
  34.         //连接等待
  35.         case conn_waiting:
  36.             //.........
  37.             break;
  38.         //读取事件
  39.         //例如有用户提交数据过来的时候,工作线程监听到事件后,最终会调用这块代码
  40.          case conn_read:
  41.             res = IS_UDP(c->transport) ? try_read_udp(c) : try_read_network(c);
  42.             switch (res) {
  43.             case READ_NO_DATA_RECEIVED:
  44.                 conn_set_state(c, conn_waiting);
  45.                 break;
  46.             case READ_DATA_RECEIVED:
  47.                 conn_set_state(c, conn_parse_cmd);
  48.                 break;
  49.             case READ_ERROR:
  50.                 conn_set_state(c, conn_closing);
  51.                 break;
  52.             case READ_MEMORY_ERROR: /* Failed to allocate more memory */
  53.                 /* State already set by try_read_network */
  54.                 break;
  55.             }
  56.             break;
  57.         case conn_parse_cmd :
  58.             if (try_read_command(c) == 0) {
  59.                 /* wee need more data! */
  60.                 conn_set_state(c, conn_waiting);
  61.             }
  62.             break;
  63.         case conn_new_cmd:
  64.             /* Only process nreqs at a time to avoid starving other
  65.                connections */
  66.             --nreqs;
  67.             if (nreqs >= 0) {
  68.                 reset_cmd_handler(c);
  69.             } else {
  70.                 pthread_mutex_lock(&c->thread->stats.mutex);
  71.                 c->thread->stats.conn_yields++;
  72.                 pthread_mutex_unlock(&c->thread->stats.mutex);
  73.                 if (c->rbytes > 0) {
  74.                     /* We have already read in data into the input buffer,
  75.                        so libevent will most likely not signal read events
  76.                        on the socket (unless more data is available. As a
  77.                        hack we should just put in a request to write data,
  78.                        because that should be possible ;-)
  79.                     */
  80.                     if (!update_event(c, EV_WRITE | EV_PERSIST)) {
  81.                         if (settings.verbose > 0)
  82.                             fprintf( stderr, "Couldn't update event\n");
  83.                         conn_set_state(c, conn_closing);
  84.                         break;
  85.                     }
  86.                 }
  87.                 stop = true;
  88.             }
  89.             break;
  90.         case conn_nread:
  91.            //....................
  92.             break;
  93.         case conn_swallow:
  94.           //....................
  95.             break;
  96.         case conn_write:
  97.            //.................
  98.             break;
  99.         
  100.         //连接关闭
  101.         case conn_closing:
  102.             if (IS_UDP(c->transport))
  103.                 conn_cleanup(c);
  104.             else
  105.                 conn_close(c);
  106.             stop = true;
  107.             break;
  108.         case conn_closed:
  109.             /* This only happens if dormando is an idiot. */
  110.             abort();
  111.             break;
  112.         case conn_max_state:
  113.             assert( false);
  114.             break;
  115.         }
  116.     }
  117.     return;
  118. }

5. 主线程main thread


主线程的socket server主要通过server_sockets这个方法创建。而server_sockets中主要调用了server_socket这个方法,我们可以看下server_socket这个方法:


  
  
  1. /**
  2. * Create a socket and bind it to a specific port number
  3. * @param interface the interface to bind to
  4. * @param port the port number to bind to
  5. * @param transport the transport protocol (TCP / UDP)
  6. * @param portnumber_file A filepointer to write the port numbers to
  7. *        when they are successfully added to the list of ports we
  8. *        listen on.
  9. */
  10. static int server_socket(const char *interface,
  11.                          int port,
  12.                          enum network_transport transport,
  13.                          FILE *portnumber_file) {
  14.             //创建一个新的事件
  15.             //我们发现上面的工作线程也是调用这个方法,但是区别是这个方法指定了state的类型为:conn_listening
  16.              //注意这边有一个conn_listening,这个参数主要是指定调用drive_machine这个方法中的conn_listen代码块。
  17.             if (!(listen_conn_add = conn_new(sfd, conn_listening,
  18.                                              EV_READ | EV_PERSIST, 1,
  19.                                              transport, main_base))) {
  20.                 fprintf( stderr, "failed to create listening connection\n");
  21.                 exit(EXIT_FAILURE);
  22.             }
  23.             listen_conn_add->next = listen_conn;
  24.             listen_conn = listen_conn_add;
  25. }

conn_new方法:


  
  
  1.     //我们发现这个方法中又在创建event了,这边实际上是监听socket的读写等事件
  2.     //主线程主要是监听用户的socket连接事件;工作线程主要监听socket的读写事件
  3.     //当用户socket的连接有数据传递过来的时候,就会调用event_handler这个回调函数
  4.     event_set(&c->event, sfd, event_flags, event_handler, ( void *)c);
  5.     event_base_set(base, &c->event);
  6.     c->ev_flags = event_flags;
  7.     //将事件添加到libevent的loop循环中
  8.     if (event_add(&c->event, 0) == -1) {
  9.         perror( "event_add");
  10.         return NULL;
  11.     }

然后我们跟踪进入event_handler这个方法,并且进入drive_machine这个方法,我们上面说过server_socket这个方法中传递的state参数为默认写死的conn_listening这个状态,所以我们详细看drive_machine中关于conn_listening这块逻辑的代码。


  
  
  1. case conn_listening:
  2.             addrlen = sizeof(addr);
  3. #ifdef HAVE_ACCEPT4
  4.             //我们可以看到下面的代码是accept,接受客户端的socket连接的代码
  5.              if (use_accept4) {
  6.                 sfd = accept4(c->sfd, (struct sockaddr *)&addr, &addrlen, SOCK_NONBLOCK);
  7.             } else {
  8.                 sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen);
  9.             }
  10. #else
  11.             sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen);
  12. #endif
  13.             if (sfd == -1) {
  14.                 if (use_accept4 && errno == ENOSYS) {
  15.                     use_accept4 = 0;
  16.                     continue;
  17.                 }
  18.                 perror(use_accept4 ? "accept4()" : "accept()");
  19.                 if (errno == EAGAIN || errno == EWOULDBLOCK) {
  20.                     /* these are transient, so don't log anything */
  21.                     stop = true;
  22.                 } else if (errno == EMFILE) {
  23.                     if (settings.verbose > 0)
  24.                         fprintf( stderr, "Too many open connections\n");
  25.                     accept_new_conns( false);
  26.                     stop = true;
  27.                 } else {
  28.                     perror( "accept()");
  29.                     stop = true;
  30.                 }
  31.                 break;
  32.             }
  33.             if (!use_accept4) {
  34.                 if (fcntl(sfd, F_SETFL, fcntl(sfd, F_GETFL) | O_NONBLOCK) < 0) {
  35.                     perror( "setting O_NONBLOCK");
  36.                     close(sfd);
  37.                     break;
  38.                 }
  39.             }
  40.             if (settings.maxconns_fast &&
  41.                 stats.curr_conns + stats.reserved_fds >= settings.maxconns - 1) {
  42.                 str = "ERROR Too many open connections\r\n";
  43.                 res = write(sfd, str, strlen(str));
  44.                 close(sfd);
  45.                 STATS_LOCK();
  46.                 stats.rejected_conns++;
  47.                 STATS_UNLOCK();
  48.             } else {
  49.                 //如果客户端用socket连接上来,则会调用这个分发逻辑的函数
  50.                 //这个函数会将连接信息分发到某一个工作线程中,然后工作线程接管具体的读写操作
  51.                 dispatch_conn_new(sfd, conn_new_cmd, EV_READ | EV_PERSIST,
  52.                                      DATA_BUFFER_SIZE, tcp_transport);
  53.             }
  54.             stop = true;
  55.             break;

dispatch_conn_new方法:


  
  
  1. /*
  2. * Dispatches a new connection to another thread. This is only ever called
  3. * from the main thread, either during initialization (for UDP) or because
  4. * of an incoming connection.
  5. */
  6. void dispatch_conn_new(int sfd, enum conn_states init_state, int event_flags,
  7.                        int read_buffer_size, enum network_transport transport) {
  8.     //每个连接连上来的时候,都会申请一块CQ_ITEM的内存块,用于存储连接的基本信息
  9.  CQ_ITEM *item = cqi_new();
  10.     char buf[ 1];
  11.     //如果item创建失败,则关闭连接
  12.     if (item == NULL) {
  13.         close(sfd);
  14.         /* given that malloc failed this may also fail, but let's try */
  15.         fprintf( stderr, "Failed to allocate memory for connection object\n");
  16.         return ;
  17.     }
  18.     //这个方法非常重要。主要是通过求余数的方法来得到当前的连接需要哪个线程来接管
  19.     //而且last_thread会记录每次最后一次使用的工作线程,每次记录之后就可以让工作线程进入一个轮询,保证了每个工作线程处理的连接数的平衡
  20.      int tid = (last_thread + 1) % settings.num_threads;
  21.     //获取线程的基本结构
  22.     LIBEVENT_THREAD *thread = threads + tid;
  23.     last_thread = tid;
  24.     item->sfd = sfd;
  25.     item->init_state = init_state;
  26.     item->event_flags = event_flags;
  27.     item->read_buffer_size = read_buffer_size;
  28.     item->transport = transport;
  29.     //向工作线程的队列中放入CQ_ITEM
  30.     cq_push(thread->new_conn_queue, item);
  31.     MEMCACHED_CONN_DISPATCH(sfd, thread->thread_id);
  32.     buf[ 0] = 'c';
  33.     //向工作线程的pipe中写入1
  34.     //工作线程监听到pipe中有写入数据,工作线程接收到通知后,就会向thread->new_conn_queue队列中pop出一个item,然后进行连接的接管操作
  35.     if (write(thread->notify_send_fd, buf, 1) != 1) {
  36.         perror( "Writing to thread notify pipe");
  37.     }
  38. }

 

 

 

               
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值