libevent原始碼分析:hello-world例子

weixin_33941350發表於2016-12-16

hello-world是libevent自帶的一個例子,這個例子的作用是啟動後監聽一個埠,對於所有通過這個埠連線上伺服器的程式傳送一段字元:hello-world,然後關閉連線。

  1 /*
  2 * gcc -g -o hello-world hello-world.c -levent_core
  3 */
  4 #include <sys/socket.h>
  5 #include <netinet/in.h>
  6 #include <arpa/inet.h>
  7 #include <string.h>
  8 #include <errno.h>
  9 #include <stdio.h>
 10 #include <signal.h>
 11 
 12 #include <event2/bufferevent.h>
 13 #include <event2/buffer.h>
 14 #include <event2/listener.h>
 15 #include <event2/util.h>
 16 #include <event2/event.h>
 17 
 18 static const char MESSAGE[] = "Hello, World!\n";
 19 
 20 static const int PORT = 9995;
 21 
 22 static void listener_cb(struct evconnlistener *, evutil_socket_t, struct sockaddr *, int socklen, void *);
 23 static void conn_writecb(struct bufferevent *, void *);
 24 static void conn_eventcb(struct bufferevent *, short, void *);
 25 static void signal_cb(evutil_socket_t, short, void *);
 26 
 27 int main(void)
 28 {
 29     struct event_base *base;
 30     struct evconnlistener *listener;
 31     struct event *signal_event;
 32     
 33     struct sockaddr_in sin;
 34 
 35     base = event_base_new();
 36     if (!base)
 37     {
 38         fprintf(stderr, "Could not initialize libevent\n");
 39         return 1;
 40     }
 41 
 42     memset(&sin, 0, sizeof(sin));
 43     sin.sin_family = AF_INET;
 44     sin.sin_port = htons(PORT);
 45 
 46     listener = evconnlistener_new_bind(base, listener_cb, (void *)base, 
 47             LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1, (struct sockaddr *)&sin, sizeof(sin));
 48 
 49     if (!listener)
 50     {
 51         fprintf(stderr, "Could not create a listener!\n");
 52         return 1;
 53     }
 54     printf("Listening on %d\n", PORT);
 55 
 56     signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);
 57 
 58     if (!signal_event || event_add(signal_event, NULL) < 0)
 59     {
 60         fprintf(stderr, "Could not create/add a signal event!\n");
 61         return 1;
 62     }
 63 
 64     event_base_dispatch(base);
 65 
 66     evconnlistener_free(listener);
 67     event_free(signal_event);
 68     event_base_free(base);
 69 
 70     printf("Done!\n");
 71     return 0;
 72 }
 73 
 74 static void listener_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *sa, int socklen, void *user_data)
 75 {
 76     struct event_base *base = user_data;
 77     struct bufferevent *bev;
 78     struct sockaddr_in *sa_in = (struct sockaddr_in*)sa;
 79 
 80     bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
 81     if (!bev)
 82     {
 83         fprintf(stderr, "Error construction bufferevent!");
 84         event_base_loopbreak(base);
 85         return;
 86     }
 87     printf("Recv a new connection, ip[%s], port[%d]\n", inet_ntoa(sa_in->sin_addr), ntohs(sa_in->sin_port));
 88     bufferevent_setcb(bev, NULL, conn_writecb, conn_eventcb, NULL);
 89     bufferevent_enable(bev, EV_WRITE);
 90     bufferevent_disable(bev, EV_READ);
 91 
 92     bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
 93 }
 94 
 95 
 96 static void conn_writecb(struct bufferevent *bev, void *user_data)
 97 {
 98     struct evbuffer *output = bufferevent_get_output(bev);
 99     if (evbuffer_get_length(output) == 0)
100     {
101         printf("flushed answer\n");
102         bufferevent_free(bev);
103     }
104 }
105 
106 static void conn_eventcb(struct bufferevent *bev, short events, void *user_data)
107 {
108     if (events & BEV_EVENT_EOF)
109     {
110         printf("Connection closed.\n");
111     }
112     else if (events & BEV_EVENT_ERROR)
113     {
114         printf("Got an error on the connection: %s\n", strerror(errno));
115     }
116 
117     bufferevent_free(bev);
118 }
119 
120 static void signal_cb(evutil_socket_t sig, short events, void *user_data)
121 {
122     struct event_base *base = user_data;
123     struct timeval delay = { 2, 0 };
124     printf("Caught an interrupt signal; exiting cleanly in two seconds\n");
125     event_base_loopexit(base, &delay);
126 }
View Code

下面就通過分析這個例子來看一下libevent對於IO事件是如何處理的:

1、呼叫event_base_new獲得event_base物件。

2、呼叫evconnlistener_new_bind,返回一個struct evconnlistener物件指標,evconnlistener_new_bind函式內部實現如下:

1)呼叫evutil_socket_函式獲取一個socket。

2)呼叫evconnlistener_new函式獲取一個struct evconnlistener物件指標,並返回。

在evconnlistener_new函式內部,首先呼叫malloc函式分配一個struct evconnlistener_event物件,然後利用傳入的形參fd呼叫listen函式,然後初始化base的各個引數,呼叫event_assign初始化成員listener。

其實這些函式歸根結底還是會呼叫最基本的libevent函式,只是這些函式對基本的函式做了一些封裝提供更高階、更方便的使用方式。

struct evconnlistener和struct evconnlistener_event的定義如下:

 1 struct evconnlistener {
2 const struct evconnlistener_ops *ops; 3 void *lock; 4 evconnlistener_cb cb; 5 evconnlistener_errorcb errorcb; 6 void *user_data; 7 unsigned flags; 8 short refcnt; 9 int accept4_flags; 10 unsigned enabled : 1; 11 }; 12 13 struct evconnlistener_event { 14 struct evconnlistener base; 15 struct event listener; 16 };

 關於這裡,我有些不明白的是listener是如何被呼叫event_add函式的?答案在這裡

3、呼叫evsignal_new函式獲取一個訊號事件物件,其實這個函式也是對event_new函式的封裝。

1 #define evsignal_new(b, x, cb, arg)                \
2     event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))

 4、呼叫event_base_dispatch函式進入事件迴圈。

5、呼叫evconnlistener_free釋放struct evconnlistener物件。

然後看一下回撥函式,在建立evconnlistener物件時傳入了一個回撥函式:listener_cb,該函式會在監聽物件有新連線到來時被呼叫,在它的內部,首先建立一個struct bufferevent物件,然後設定該物件的回撥函式,然後就是呼叫bufferevent_write函式將要傳送的資料寫入到該物件對應的buffer中,就不用管了。

由此可見使用libevent庫來開發網路伺服器是多麼的方便,高效。

 

至此一個基於libevent的簡單伺服器就完成了,只能對連線上的客戶端傳送“hello world”然後關閉連線。

相關文章