typedefstruct { //线程ID pthread_t thread_id; /* unique ID of this thread */ //线程的 event_base,每个线程都有自己的event_base structevent_base *base; /* libevent handle this thread uses */ //异步event事件 structevent notify_event; /* listen event for notify pipe */ //管道接收端 int notify_receive_fd; /* receiving end of notify pipe */ //管道发送端 int notify_send_fd; /* sending end of notify pipe */ //线程状态 structthread_stats stats; /* Stats generated by this thread */ //新连接队列结构 structconn_queue *new_conn_queue; /* queue of new connections to handle */ cache_t *suffix_cache; /* suffix cache */ uint8_t item_lock_type; /* use fine-grained or global item lock */ } LIBEVENT_THREAD;
/* An item in the connection queue. */ typedefstructconn_queue_item CQ_ITEM; structconn_queue_item { int sfd; //socket的fd enumconn_states init_state; //事件类型 int event_flags; //libevent的flags int read_buffer_size; //读取的buffer的size enumnetwork_transport transport; CQ_ITEM *next; //下一个item的地址 };
intmain(int argc, char **argv){ //...省去一部分代码 /* initialize main thread libevent instance */ //初始化一个event_base main_base = event_init(); /* initialize other stuff */ stats_init(); assoc_init(settings.hashpower_init); conn_init(); slabs_init(settings.maxbytes, settings.factor, preallocate); /* * ignore SIGPIPE signals; we can use errno == EPIPE if we * need that information */ if (sigignore(SIGPIPE) == -1) { perror("failed to ignore SIGPIPE; sigaction"); exit(EX_OSERR); } /* start up worker threads if MT mode */ //这边非常重要,这个方法主要用来创建工作线程,默认会创建4个工作线程 thread_init(settings.num_threads, main_base); if (start_assoc_maintenance_thread() == -1) { exit(EXIT_FAILURE); } if (settings.slab_reassign && start_slab_maintenance_thread() == -1) { exit(EXIT_FAILURE); } /* Run regardless of initializing it later */ init_lru_crawler(); /* initialise clock event */ clock_handler(0, 0, 0); /* create unix mode sockets after dropping privileges */ if (settings.socketpath != NULL) { errno = 0; if (server_socket_unix(settings.socketpath,settings.access)) { vperror("failed to listen on UNIX socket: %s", settings.socketpath); exit(EX_OSERR); } } /* create the listening socket, bind it, and init */ if (settings.socketpath == NULL) { constchar *portnumber_filename = getenv("MEMCACHED_PORT_FILENAME"); char temp_portnumber_filename[PATH_MAX]; FILE *portnumber_file = NULL; if (portnumber_filename != NULL) { snprintf(temp_portnumber_filename, sizeof(temp_portnumber_filename), "%s.lck", portnumber_filename); portnumber_file = fopen(temp_portnumber_filename, "a"); if (portnumber_file == NULL) { fprintf(stderr, "Failed to open \"%s\": %s\n", temp_portnumber_filename, strerror(errno)); } } errno = 0; //这边的server_sockets方法主要是socket的bind、listen、accept等操作 //主线程主要用于接收客户端的socket连接,并且将连接交给工作线程接管。 if (settings.port && server_sockets(settings.port, tcp_transport, portnumber_file)) { vperror("failed to listen on TCP port %d", settings.port); exit(EX_OSERR); } } /* enter the event loop */ //这边开始进行主线程的事件循环 if (event_base_loop(main_base, 0) != 0) { retval = EXIT_FAILURE; } //...省去一部分代码 }
主线程中主要是通过thread_init方法去创建N个工作线程:
1
thread_init(settings.num_threads, main_base);
通过server_sockets方法去创建socket server:
1 2 3 4 5 6
errno = 0; if (settings.port && server_sockets(settings.port, tcp_transport, portnumber_file)) { vperror("failed to listen on TCP port %d", settings.port); exit(EX_OSERR); }
voidthread_init(int nthreads, struct event_base *main_base){ //...省了一部分代码 //这边通过循环的方式创建nthreads个线程 //nthreads应该是可以设置的 for (i = 0; i < nthreads; i++) { int fds[2]; //这边会创建pipe,主要用于主线程和工作线程之间的通信 if (pipe(fds)) { perror("Can't create notify pipe"); exit(1); } //threads是工作线程的基本结构:LIBEVENT_THREAD //将pipe接收端和写入端都放到工作线程的结构体中 threads[i].notify_receive_fd = fds[0]; //接收端 threads[i].notify_send_fd = fds[1]; //写入端 //这个方法非常重要,主要是创建每个线程自己的libevent的event_base setup_thread(&threads[i]); /* Reserve three fds for the libevent base, and two for the pipe */ stats.reserved_fds += 5; } /* Create threads after we've done all the libevent setup. */ //这里是循环创建线程 //线程创建的回调函数是worker_libevent for (i = 0; i < nthreads; i++) { create_worker(worker_libevent, &threads[i]); } /* Wait for all the threads to set themselves up before returning. */ pthread_mutex_lock(&init_lock); wait_for_thread_registration(nthreads); pthread_mutex_unlock(&init_lock); }
/* Any per-thread setup can happen here; thread_init() will block until all threads have finished initializing. */
/* set an indexable thread-specific memory item for the lock type. this could be unnecessary if we pass the conn *c struct through all item_lock calls... */ me->item_lock_type = ITEM_LOCK_GRANULAR; pthread_setspecific(item_lock_type_key, &me->item_lock_type);
staticvoiddrive_machine(conn *c){ //.................... assert(c != NULL); while (!stop) { //这边通过state来处理不同类型的事件 switch(c->state) { //这边主要处理tcp连接,只有在主线程的下,才会执行listening监听操作。 case conn_listening: addrlen = sizeof(addr); #ifdef HAVE_ACCEPT4 if (use_accept4) { sfd = accept4(c->sfd, (struct sockaddr *)&addr, &addrlen, SOCK_NONBLOCK); } else { sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen); } #else sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen); #endif //...................... if (settings.maxconns_fast && stats.curr_conns + stats.reserved_fds >= settings.maxconns - 1) { str = "ERROR Too many open connections\r\n"; res = write(sfd, str, strlen(str)); close(sfd); STATS_LOCK(); stats.rejected_conns++; STATS_UNLOCK(); } else { dispatch_conn_new(sfd, conn_new_cmd, EV_READ | EV_PERSIST, DATA_BUFFER_SIZE, tcp_transport); } stop = true; break; //连接等待 case conn_waiting: //......... break; //读取事件 //例如有用户提交数据过来的时候,工作线程监听到事件后,最终会调用这块代码 case conn_read: res = IS_UDP(c->transport) ? try_read_udp(c) : try_read_network(c); switch (res) { case READ_NO_DATA_RECEIVED: conn_set_state(c, conn_waiting); break; case READ_DATA_RECEIVED: conn_set_state(c, conn_parse_cmd); break; case READ_ERROR: conn_set_state(c, conn_closing); break; case READ_MEMORY_ERROR: /* Failed to allocate more memory */ /* State already set by try_read_network */ break; } break; case conn_parse_cmd : if (try_read_command(c) == 0) { /* wee need more data! */ conn_set_state(c, conn_waiting); } break; case conn_new_cmd: /* Only process nreqs at a time to avoid starving other connections */ --nreqs; if (nreqs >= 0) { reset_cmd_handler(c); } else { pthread_mutex_lock(&c->thread->stats.mutex); c->thread->stats.conn_yields++; pthread_mutex_unlock(&c->thread->stats.mutex); if (c->rbytes > 0) { /* We have already read in data into the input buffer, so libevent will most likely not signal read events on the socket (unless more data is available. As a hack we should just put in a request to write data, because that should be possible ;-) */ if (!update_event(c, EV_WRITE | EV_PERSIST)) { if (settings.verbose > 0) fprintf(stderr, "Couldn't update event\n"); conn_set_state(c, conn_closing); break; } } stop = true; } break; case conn_nread: //.................... break; case conn_swallow: //.................... break; case conn_write: //................. break; //连接关闭 case conn_closing: if (IS_UDP(c->transport)) conn_cleanup(c); else conn_close(c); stop = true; break; case conn_closed: /* This only happens if dormando is an idiot. */ abort(); break; case conn_max_state: assert(false); break; } } return; }
/** Create a socket and bind it to a specific port number @param interface the interface to bind to @param port the port number to bind to @param transport the transport protocol (TCP / UDP) @param portnumber_file A filepointer to write the port numbers to when they are successfully added to the list of ports we listen on. */ staticintserver_socket(constchar *interface, int port, enum network_transport transport, FILE *portnumber_file){ //创建一个新的事件 //我们发现上面的工作线程也是调用这个方法,但是区别是这个方法指定了state的类型为:conn_listening //注意这边有一个conn_listening,这个参数主要是指定调用drive_machine这个方法中的conn_listen代码块。 if (!(listen_conn_add = conn_new(sfd, conn_listening, EV_READ | EV_PERSIST, 1, transport, main_base))) { fprintf(stderr, "failed to create listening connection\n"); exit(EXIT_FAILURE); } listen_conn_add->next = listen_conn; listen_conn = listen_conn_add; }
/* * Dispatches a new connection to another thread. This is only ever called * from the main thread, either during initialization (for UDP) or because * of an incoming connection. */ voiddispatch_conn_new(int sfd, enum conn_states init_state, int event_flags, int read_buffer_size, enum network_transport transport){ //每个连接连上来的时候,都会申请一块CQ_ITEM的内存块,用于存储连接的基本信息 CQ_ITEM *item = cqi_new(); char buf[1]; //如果item创建失败,则关闭连接 if (item == NULL) { close(sfd); /* given that malloc failed this may also fail, but let's try */ fprintf(stderr, "Failed to allocate memory for connection object\n"); return ; } //这个方法非常重要。主要是通过求余数的方法来得到当前的连接需要哪个线程来接管 //而且last_thread会记录每次最后一次使用的工作线程,每次记录之后就可以让工作线程进入一个轮询,保证了每个工作线程处理的连接数的平衡 int tid = (last_thread + 1) % settings.num_threads; //获取线程的基本结构 LIBEVENT_THREAD *thread = threads + tid; last_thread = tid; item->sfd = sfd; item->init_state = init_state; item->event_flags = event_flags; item->read_buffer_size = read_buffer_size; item->transport = transport; //向工作线程的队列中放入CQ_ITEM cq_push(thread->new_conn_queue, item); MEMCACHED_CONN_DISPATCH(sfd, thread->thread_id); buf[0] = 'c'; //向工作线程的pipe中写入1 //工作线程监听到pipe中有写入数据,工作线程接收到通知后,就会向thread->new_conn_queue队列中pop出一个item,然后进行连接的接管操作 if (write(thread->notify_send_fd, buf, 1) != 1) { perror("Writing to thread notify pipe"); } }