每個worker線程用pipe創建一個管道,並註冊libevent事件,當管道的讀端可以讀時,就調用thread_libevent_process()函數。
thread_libevent_process()做的事情等下再說。
- void thread_init(int nthreads, struct event_base *main_base) {
- threads = calloc(nthreads, sizeof(LIBEVENT_THREAD));
- //初始化dispatcher線程
- dispatcher_thread.base = main_base;
- dispatcher_thread.thread_id = pthread_self();
- //初始化nthreads個worker線程
- for (i = 0; i < nthreads; i++) {
- int fds[2];
- if (pipe(fds)) {
- perror("Can't create notify pipe");
- exit(1);
- }
- //每個線程打開一個管道
- threads[i].notify_receive_fd = fds[0];
- threads[i].notify_send_fd = fds[1];
- //註冊libevent事件。當該線程的管道的讀端可以讀時,調用thread_libevent_process()
- setup_thread(&threads[i]);
- }
- /* Create threads after we've done all the libevent setup. */
- /* 創建worker線程,並讓每個線程進入event_base_loop()循環。
- 前面的threads數組只是創建了nthreads個LIBEVENT_THREAD對象,
- 並初始化了libevent以及其他信息。並沒有真正開始一個新的進程。
- 在create_worker裏才真正調用了pthread_create,
- 並且每個進程都進入了event_base_loop()
- */
- for (i = 0; i < nthreads; i++) {
- create_worker(worker_libevent, &threads[i]);
- }
- }
- static void setup_thread(LIBEVENT_THREAD *me) {
- me->base = event_init();
- /* Listen for notifications from other threads */
- //當讀端可以讀時,調用thread_libevent_process()函數
- event_set(&me->notify_event, me->notify_receive_fd,
- EV_READ | EV_PERSIST, thread_libevent_process, me);
- event_base_set(me->base, &me->notify_event);
- if (event_add(&me->notify_event, 0) == -1) {
- fprintf(stderr, "Can't monitor libevent notify pipe\n");
- exit(1);
- }
- /*初始化每個線程的new_conn_queue成員。
- new_conn_queue成員是一個conn_queue指針,相當於一個隊列,
- 記錄分配到該線程的,等待new一個conn對象的那些item的信息。
- 每次調用thread_libevent_process()時,
- 就從該隊列中取出一個item,然後建立一個conn*/
- me->new_conn_queue = malloc(sizeof(struct conn_queue));
- cq_init(me->new_conn_queue);
- }
- static void create_worker(void *(*func)(void *), void *arg) {
- pthread_t thread;
- pthread_attr_t attr;
- int ret;
- pthread_attr_init(&attr);
- if ((ret = pthread_create(&thread, &attr, func, arg)) != 0) {
- fprintf(stderr, "Can't create thread: %s\n",
- strerror(ret));
- exit(1);
- }
- }
- static void *worker_libevent(void *arg) {
- LIBEVENT_THREAD *me = arg;
- event_base_loop(me->base, 0);
- return NULL;
- }
2. main函數中,然後調用server_sockets,在指定的端口和ip地址上分別建立tcp和udp的監聽。
然後
1) 如果是TCP,就調用conn_new(sfd, conn_listening, EV_READ|EV+PERSIST, 1, tcp_transport, main_base),該函數在main_base上建立libevent事件,當sfd可讀時,調用event_handler(). 而event_handler()又調用drive_machine().
2) 如果是UDP,就調用dispatch_conn_new(sfd, conn_read, EV_READ | EV_PERSIST, UDP_READ_BUFFER_SIZE, udp_transport),該函數用round-robin的方法找到一個worker線程,然後new一個CQ_ITEM對象,把該item對象放到worker線程的new_conn_queue隊列中。然後,通知該worker線程。通知的方法是,往該線程的寫端寫一個字節,這樣,該線程的讀端就可讀了。由於之前已經講過,每個worker線程註冊了libevent事件,當讀端可讀時,就調用thread_libevent_process()。
server_sockets()調用server_socket(), server_socket()先在指定端口和ip上建立socket,得到文件描述符sfd,然後bind,然後
- static int server_socket(const char *interface,
- int port,
- enum network_transport transport,
- FILE *portnumber_file) {
- if ((sfd = new_socket(next)) == -1) {
- ...
- }
- if (bind(sfd, next->ai_addr, next->ai_addrlen) == -1) {
- ...
- }
- if (!IS_UDP(transport) && listen(sfd, settings.backlog) == -1) {
- ...
- }
- if (IS_UDP(transport)) {
- int c;
- for (c = 0; c < settings.num_threads_per_udp; c++) {
- /* this is guaranteed to hit all threads because we round-robin */
- dispatch_conn_new(sfd, conn_read, EV_READ | EV_PERSIST,
- UDP_READ_BUFFER_SIZE, transport);
- }
- } else {
- if (!(listen_conn_add = conn_new(sfd, conn_listening,
- EV_READ | EV_PERSIST, 1,
- transport, main_base))) {
- ...
- }
- }
- }
3. 繼續2.1
如果是TCP,把conn的狀態設爲conn_listening, 設置libevent事件。當一個新的tcp connection到達時,進入drive_machine。
- static void drive_machine(conn *c) {
- switch(c->state) {
- case conn_listening:
- addrlen = sizeof(addr);
- sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen));
- dispatch_conn_new(sfd, conn_new_cmd, EV_READ | EV_PERSIST,
- DATA_BUFFER_SIZE, tcp_transport);
- ...
- }
4. 繼續2.2
注意server_socket中,當協議是UDP時,調用dispatch_new_conn,但是是在一個for循環中。就是說,對於一個udp的描述符,指派了settings.num_threads_per_udp個線程來監控該udp描述符。
這叫做“驚羣”。當一個udp連接到達時,所有的libevent都能檢測到該事件,但是隻有一個線程調用recvfrom能返回數據。其他線程都返回失敗。
udp用驚羣而tcp不用的原因可能是:對於tcp,主線程(即dispatch_thread)會一直監控是否有新的tcp connection到達。如果到達,就會指派一個worker thread處理它。而對於udp,如果不用驚羣,那麼只有一個worker 線程監控udp 請求。因此,當該線程在處理一個udp請求時,其他的udp請求就不能得到及時處理。
另一個原因是:
“TCP是定然不能這樣設計的,所以只能accept之後分發到特定線程,因爲是字節流.
而UDP是包,隨便哪個線程處理包1,再換個線程處理包2也是沒有問題的.”