libevent学习篇之一:libevent快速入门

LibEvent快速入门

https://blog.csdn.net/XiaoWhy/article/details/88173413

https://www.jianshu.com/p/8ea60a8d3abb

简介

基本的socket变成是阻塞/同步的,每个操作除非已经完成,出错,或者超时才会返回,这样对于每一个请求,要使用一个线程或者单独的进程去处理,系统资源没有办法支撑大量的请求。posix定义了可以使用异步的select系统调用,但是因为它采用了轮询的方式来判断某个fd是否变成active,效率不高。于是各系统就分别提出了基于异步的系统调用,例如Linux的epoll,由于在内核层面做了支持,所以可以用O(1)的效率查找到active的fd。基本上,libevent就是对这些高效IO的封装,提供统一的API,简化开发。

原理简介

libevent默认情况下是单线程的,可以配置成多线程,每个线程有且只有一个event_base,对应一个struct event_base结构体以及附于其上的事件管理器,用来调度托管给它的一系列event,可以和操作系统的进程管理类比。当一个事件发生后,event_base会在合适的时间,不一定是立即去调用绑定在这个事件上的函数,直到这个函数执行完,再去调度其他的事件。

 
  1. //创建一个event_base

  2. struct event_base *base = event_base_new();

  3. assert(base != NULL);

event_base内部有一个循环,循环阻塞在epoll等系统调用上,直到有一个/一些时间发生,然后去处理这些事件。当然,这些事件要被绑定在这个event_base上,每个事件对应一个struct event,可以是监听一个fd或者信号量之类的,struct event使用event_new来创建和绑定,使用event_add来将event绑定到event_base中。

 
  1. // 创建并绑定一个event

  2. struct event* listen_event;

  3.  
  4. //参数:event_base,监听的对象,需要监听的事件,事件发生后的回调函数,传给回调函数的参数

  5. listen_event = event_new(base, listener, EV_READ | EV_PERSIST, callback_func, (void*)base);

  6. //参数:event,超时时间,NULL表示无超时设置

  7. event_add(listen_event, NULL);

注:libevent支持的事件及属性包括(使用bitfield实现)

  1. EV_TIMEOUT:超时;
  2. EV_READ:只要网络缓冲中还有数据,回调函数就会被触发;
  3. EV_WRITE:只要塞给网络缓冲的数据被写完,回调函数就会被触发;
  4. EV_SIGNAL:POSIX信号量;
  5. EV_PERSIST:不指定这个属性,回调函数被触发后事件会被删除;
  6. EV_ET:Edge-Trigger边缘触发(这个还不懂是什么意思)

然后启动event_base的循环,开始处理事件。循环地启动使用event_base_dispatch,循环将一直持续,找到不再有需要关注的事件,或者是遇到event_loopbreak()/event_loopexit()函数。

 
  1. //启动循环,开始处理事件

  2. event_base_dispatch(base);

接下来再来关注事件发生时的回调函数callback_func,callback_func的原型如下所示

typedef void(* event_callback_fn)(evutil_socket_t sockfd, short event_type, void *arg)

传给callback_func的是一个监听的fd,监听的事件类型,以及event_new中最后一个参数。在上述程序中,是将event_base传给了callback_func,实际中更常用的是构造一个结构体,把需要传给回调函数的参数都放进来,然后传给event_new,event_new再传给回调函数。

所以总结一下,对于一个服务器而言,流程大致如下:

  1. 获取待监听的内容的fd;
  2. 创建一个event_base;
  3. 创建一个event,指定待监听的fd,待监听事件的类型,以及事件放生时的回调函数及传给回调函数的参数;
  4. 将event添加到event_base的事件管理器中;
  5. 开启event_base的事件处理循环;
  6. (异步)当事件发生的时候,调用前面设置的回调函数。

简易版QuickStart

下面的代码实现了一个简单的echo server,server启动后,client端启动并连接,在cmd中输入文字,server端收到后,将文字再返回给client。
server端代码:

 
  1. /**

  2. You need libevent2 to compile this piece of code

  3. Please see: http://libevent.org/

  4. Or you can simply run this command to install on Mac: brew install libevent

  5. Cmd to compile this piece of code: g++ LibeventQuickStartServer.c -o LibeventQuickStartServer /usr/local/lib/libevent.a

  6. **/

  7. #include<stdio.h>

  8. #include<string.h>

  9. #include<errno.h>

  10.  
  11. #include<unistd.h>

  12. #include<event.h>

  13.  
  14. void accept_cb(int fd, short events, void* arg);

  15. void socket_read_cb(int fd, short events, void* arg);

  16.  
  17. int tcp_server_init(int port, int listen_num);

  18.  
  19. int main(int argc, char const *argv[])

  20. {

  21. /* code */

  22. int listener = tcp_server_init(9999, 10);

  23. if (listener == -1)

  24. {

  25. perror("tcp_server_init error");

  26. return -1;

  27. }

  28.  
  29. struct event_base* base = event_base_new();

  30.  
  31. // 监听客户端请求链接事件

  32. struct event* ev_listen = event_new(base, listener, EV_READ | EV_PERSIST, accept_cb, base);

  33.  
  34. event_add(ev_listen, NULL);

  35.  
  36. event_base_dispatch(base);

  37.  
  38. return 0;

  39. }

  40.  
  41. void accept_cb(int fd, short events, void* arg)

  42. {

  43. evutil_socket_t sockfd;

  44.  
  45. struct sockaddr_in client;

  46. socklen_t len = sizeof(client);

  47.  
  48. sockfd = ::accept(fd, (struct sockaddr*)&client, &len);

  49. evutil_make_socket_nonblocking(sockfd);

  50.  
  51. printf("accept a client %d\n", sockfd);

  52.  
  53. struct event_base* base = (event_base*)arg;

  54.  
  55. //动态创建一个event结构体,并将其作为回调参数传递给

  56. struct event* ev = event_new(NULL, -1, 0, NULL, NULL);

  57. event_assign(ev, base, sockfd, EV_READ | EV_PERSIST, socket_read_cb, (void*)ev);

  58.  
  59. event_add(ev, NULL);

  60. }

  61.  
  62.  
  63. void socket_read_cb(int fd, short events, void* arg)

  64. {

  65. char msg[4096];

  66. struct event* ev = (struct event*)arg;

  67. int len = read(fd, msg, sizeof(msg) - 1);

  68.  
  69. if(len <= 0)

  70. {

  71. printf("some error happen when read\n");

  72. event_free(ev);

  73. close(fd);

  74. return;

  75. }

  76.  
  77. msg[len] = '\0';

  78. printf("recv the client msg : %s\n", msg);

  79.  
  80. char reply_msg[4096] = "I have received the msg: ";

  81. strcat(reply_msg + strlen(reply_msg), msg);

  82.  
  83. write(fd, reply_msg, strlen(reply_msg));

  84. }

  85.  
  86. typedef struct sockaddr SA;

  87. int tcp_server_init(int port, int listen_num)

  88. {

  89. int errno_save;

  90. evutil_socket_t listener;

  91.  
  92. listener = ::socket(AF_INET, SOCK_STREAM, 0);

  93. if( listener == -1 )

  94. return -1;

  95.  
  96. //允许多次绑定同一个地址。要用在socket和bind之间

  97. evutil_make_listen_socket_reuseable(listener);

  98.  
  99. struct sockaddr_in sin;

  100. sin.sin_family = AF_INET;

  101. sin.sin_addr.s_addr = 0;

  102. sin.sin_port = htons(port);

  103.  
  104. if( ::bind(listener, (SA*)&sin, sizeof(sin)) < 0 )

  105. goto error;

  106.  
  107. if( ::listen(listener, listen_num) < 0)

  108. goto error;

  109.  
  110.  
  111. //跨平台统一接口,将套接字设置为非阻塞状态

  112. evutil_make_socket_nonblocking(listener);

  113.  
  114. return listener;

  115.  
  116. error:

  117. errno_save = errno;

  118. evutil_closesocket(listener);

  119. errno = errno_save;

  120.  
  121. return -1;

  122. }

  123.  

client端代码:

 
  1.  
  2. /**

  3. You need libevent2 to compile this piece of code

  4. Please see: http://libevent.org/

  5. Or you can simply run this command to install on Mac: brew install libevent

  6. Cmd to compile this piece of code: g++ LibeventQuickStartClient.c -o LibeventQuickStartClient /usr/local/lib/libevent.a

  7. **/

  8. #include<sys/types.h>

  9. #include<sys/socket.h>

  10. #include<netinet/in.h>

  11. #include<arpa/inet.h>

  12. #include<errno.h>

  13. #include<unistd.h>

  14.  
  15. #include<stdio.h>

  16. #include<string.h>

  17. #include<stdlib.h>

  18.  
  19. #include<event.h>

  20. #include<event2/util.h>

  21.  
  22.  
  23.  
  24.  
  25. int tcp_connect_server(const char* server_ip, int port);

  26.  
  27.  
  28. void cmd_msg_cb(int fd, short events, void* arg);

  29. void socket_read_cb(int fd, short events, void *arg);

  30.  
  31. int main(int argc, char** argv)

  32. {

  33. if( argc < 3 )

  34. {

  35. printf("please input 2 parameter\n");

  36. return -1;

  37. }

  38.  
  39.  
  40. //两个参数依次是服务器端的IP地址、端口号

  41. int sockfd = tcp_connect_server(argv[1], atoi(argv[2]));

  42. if( sockfd == -1)

  43. {

  44. perror("tcp_connect error ");

  45. return -1;

  46. }

  47.  
  48. printf("connect to server successful\n");

  49.  
  50. struct event_base* base = event_base_new();

  51.  
  52. struct event *ev_sockfd = event_new(base, sockfd,

  53. EV_READ | EV_PERSIST,

  54. socket_read_cb, NULL);

  55. event_add(ev_sockfd, NULL);

  56.  
  57. //监听终端输入事件

  58. struct event* ev_cmd = event_new(base, STDIN_FILENO,

  59. EV_READ | EV_PERSIST, cmd_msg_cb,

  60. (void*)&sockfd);

  61.  
  62.  
  63. event_add(ev_cmd, NULL);

  64.  
  65. event_base_dispatch(base);

  66.  
  67. printf("finished \n");

  68. return 0;

  69. }

  70.  
  71.  
  72.  
  73.  
  74.  
  75.  
  76. void cmd_msg_cb(int fd, short events, void* arg)

  77. {

  78. char msg[1024];

  79.  
  80. int ret = read(fd, msg, sizeof(msg));

  81. if( ret <= 0 )

  82. {

  83. perror("read fail ");

  84. exit(1);

  85. }

  86.  
  87. int sockfd = *((int*)arg);

  88.  
  89. //把终端的消息发送给服务器端

  90. //为了简单起见,不考虑写一半数据的情况

  91. write(sockfd, msg, ret);

  92. }

  93.  
  94.  
  95. void socket_read_cb(int fd, short events, void *arg)

  96. {

  97. char msg[1024];

  98.  
  99. //为了简单起见,不考虑读一半数据的情况

  100. int len = read(fd, msg, sizeof(msg)-1);

  101. if( len <= 0 )

  102. {

  103. perror("read fail ");

  104. exit(1);

  105. }

  106.  
  107. msg[len] = '\0';

  108.  
  109. printf("recv %s from server\n", msg);

  110. }

  111.  
  112.  
  113.  
  114. typedef struct sockaddr SA;

  115. int tcp_connect_server(const char* server_ip, int port)

  116. {

  117. int sockfd, status, save_errno;

  118. struct sockaddr_in server_addr;

  119.  
  120. memset(&server_addr, 0, sizeof(server_addr) );

  121.  
  122. server_addr.sin_family = AF_INET;

  123. server_addr.sin_port = htons(port);

  124. status = inet_aton(server_ip, &server_addr.sin_addr);

  125.  
  126. if( status == 0 ) //the server_ip is not valid value

  127. {

  128. errno = EINVAL;

  129. return -1;

  130. }

  131.  
  132. sockfd = ::socket(PF_INET, SOCK_STREAM, 0);

  133. if( sockfd == -1 )

  134. return sockfd;

  135.  
  136.  
  137. status = ::connect(sockfd, (SA*)&server_addr, sizeof(server_addr) );

  138.  
  139. if( status == -1 )

  140. {

  141. save_errno = errno;

  142. ::close(sockfd);

  143. errno = save_errno; //the close may be error

  144. return -1;

  145. }

  146.  
  147. evutil_make_socket_nonblocking(sockfd);

  148.  
  149. return sockfd;

  150. }

  151.  

程序运行截图

客户端

 

Paste_Image.png

服务端

Paste_Image.png

使用BufferEvent

在上面的代码中,client的cmd中有信息输入时,client直接将数据写入到fd中,server中收到信息后,也是直接将信息写入到fd中,因为fd是非阻塞的,所以不能保证正确。那么需要一个自己管理的缓存来管理自己的数据。那么步骤将稍微有些变化,如下所示:

  1. 设置scokfd为nonblocking;
  2. 使用bufferevent_socket_new创建一个struct bufferevent* bev,关联上面的sockfd,并托管给event_base;
  3. 使用bufferevent_setcb(bev, read_cb, write_cb, error_cb, (void*)arg);
  4. 使用buffevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST)来启动read/write事件

代码如下所示:

使用bufferevent的server端代码

 
  1. #include<stdio.h>

  2. #include<string.h>

  3. #include<errno.h>

  4.  
  5. #include<event.h>

  6. #include<event2/bufferevent.h>

  7.  
  8.  
  9.  
  10. void accept_cb(int fd, short events, void* arg);

  11. void socket_read_cb(bufferevent* bev, void* arg);

  12. void event_cb(struct bufferevent *bev, short event, void *arg);

  13. int tcp_server_init(int port, int listen_num);

  14.  
  15. int main(int argc, char** argv)

  16. {

  17.  
  18. int listener = tcp_server_init(9999, 10);

  19. if( listener == -1 )

  20. {

  21. perror(" tcp_server_init error ");

  22. return -1;

  23. }

  24.  
  25. struct event_base* base = event_base_new();

  26.  
  27. //添加监听客户端请求连接事件

  28. struct event* ev_listen = event_new(base, listener, EV_READ | EV_PERSIST,

  29. accept_cb, base);

  30. event_add(ev_listen, NULL);

  31.  
  32.  
  33. event_base_dispatch(base);

  34. event_base_free(base);

  35.  
  36.  
  37. return 0;

  38. }

  39.  
  40.  
  41.  
  42. void accept_cb(int fd, short events, void* arg)

  43. {

  44. evutil_socket_t sockfd;

  45.  
  46. struct sockaddr_in client;

  47. socklen_t len = sizeof(client);

  48.  
  49. sockfd = ::accept(fd, (struct sockaddr*)&client, &len );

  50. evutil_make_socket_nonblocking(sockfd);

  51.  
  52. printf("accept a client %d\n", sockfd);

  53.  
  54. struct event_base* base = (event_base*)arg;

  55.  
  56. bufferevent* bev = bufferevent_socket_new(base, sockfd, BEV_OPT_CLOSE_ON_FREE);

  57. bufferevent_setcb(bev, socket_read_cb, NULL, event_cb, arg);

  58.  
  59. bufferevent_enable(bev, EV_READ | EV_PERSIST);

  60. }

  61.  
  62.  
  63.  
  64. void socket_read_cb(bufferevent* bev, void* arg)

  65. {

  66. char msg[4096];

  67.  
  68. size_t len = bufferevent_read(bev, msg, sizeof(msg));

  69.  
  70. msg[len] = '\0';

  71. printf("recv the client msg: %s", msg);

  72.  
  73.  
  74. char reply_msg[4096] = "I have recvieced the msg: ";

  75.  
  76. strcat(reply_msg + strlen(reply_msg), msg);

  77. bufferevent_write(bev, reply_msg, strlen(reply_msg));

  78. }

  79.  
  80.  
  81.  
  82. void event_cb(struct bufferevent *bev, short event, void *arg)

  83. {

  84.  
  85. if (event & BEV_EVENT_EOF)

  86. printf("connection closed\n");

  87. else if (event & BEV_EVENT_ERROR)

  88. printf("some other error\n");

  89.  
  90. //这将自动close套接字和free读写缓冲区

  91. bufferevent_free(bev);

  92. }

  93.  
  94.  
  95. typedef struct sockaddr SA;

  96. int tcp_server_init(int port, int listen_num)

  97. {

  98. int errno_save;

  99. evutil_socket_t listener;

  100.  
  101. listener = ::socket(AF_INET, SOCK_STREAM, 0);

  102. if( listener == -1 )

  103. return -1;

  104.  
  105. //允许多次绑定同一个地址。要用在socket和bind之间

  106. evutil_make_listen_socket_reuseable(listener);

  107.  
  108. struct sockaddr_in sin;

  109. sin.sin_family = AF_INET;

  110. sin.sin_addr.s_addr = 0;

  111. sin.sin_port = htons(port);

  112.  
  113. if( ::bind(listener, (SA*)&sin, sizeof(sin)) < 0 )

  114. goto error;

  115.  
  116. if( ::listen(listener, listen_num) < 0)

  117. goto error;

  118.  
  119.  
  120. //跨平台统一接口,将套接字设置为非阻塞状态

  121. evutil_make_socket_nonblocking(listener);

  122.  
  123. return listener;

  124.  
  125. error:

  126. errno_save = errno;

  127. evutil_closesocket(listener);

  128. errno = errno_save;

  129.  
  130. return -1;

  131. }

  132.  

使用bufferevent的client端代码

 
  1. #include<sys/types.h>

  2. #include<sys/socket.h>

  3. #include<netinet/in.h>

  4. #include<arpa/inet.h>

  5. #include<errno.h>

  6. #include<unistd.h>

  7.  
  8. #include<stdio.h>

  9. #include<string.h>

  10. #include<stdlib.h>

  11.  
  12. #include<event.h>

  13. #include<event2/bufferevent.h>

  14. #include<event2/buffer.h>

  15. #include<event2/util.h>

  16.  
  17.  
  18.  
  19.  
  20. int tcp_connect_server(const char* server_ip, int port);

  21.  
  22.  
  23. void cmd_msg_cb(int fd, short events, void* arg);

  24. void server_msg_cb(struct bufferevent* bev, void* arg);

  25. void event_cb(struct bufferevent *bev, short event, void *arg);

  26.  
  27. int main(int argc, char** argv)

  28. {

  29. if( argc < 3 )

  30. {

  31. printf("please input 2 parameter\n");

  32. return -1;

  33. }

  34.  
  35.  
  36. //两个参数依次是服务器端的IP地址、端口号

  37. int sockfd = tcp_connect_server(argv[1], atoi(argv[2]));

  38. if( sockfd == -1)

  39. {

  40. perror("tcp_connect error ");

  41. return -1;

  42. }

  43.  
  44. printf("connect to server successful\n");

  45.  
  46. struct event_base* base = event_base_new();

  47.  
  48. struct bufferevent* bev = bufferevent_socket_new(base, sockfd,

  49. BEV_OPT_CLOSE_ON_FREE);

  50.  
  51. //监听终端输入事件

  52. struct event* ev_cmd = event_new(base, STDIN_FILENO,

  53. EV_READ | EV_PERSIST, cmd_msg_cb,

  54. (void*)bev);

  55. event_add(ev_cmd, NULL);

  56.  
  57. //当socket关闭时会用到回调参数

  58. bufferevent_setcb(bev, server_msg_cb, NULL, event_cb, (void*)ev_cmd);

  59. bufferevent_enable(bev, EV_READ | EV_PERSIST);

  60.  
  61.  
  62. event_base_dispatch(base);

  63.  
  64. printf("finished \n");

  65. return 0;

  66. }

  67.  
  68.  
  69.  
  70.  
  71.  
  72.  
  73. void cmd_msg_cb(int fd, short events, void* arg)

  74. {

  75. char msg[1024];

  76.  
  77. int ret = read(fd, msg, sizeof(msg));

  78. if( ret < 0 )

  79. {

  80. perror("read fail ");

  81. exit(1);

  82. }

  83.  
  84. struct bufferevent* bev = (struct bufferevent*)arg;

  85.  
  86. //把终端的消息发送给服务器端

  87. bufferevent_write(bev, msg, ret);

  88. }

  89.  
  90.  
  91. void server_msg_cb(struct bufferevent* bev, void* arg)

  92. {

  93. char msg[1024];

  94.  
  95. size_t len = bufferevent_read(bev, msg, sizeof(msg));

  96. msg[len] = '\0';

  97.  
  98. printf("recv %s from server\n", msg);

  99. }

  100.  
  101.  
  102. void event_cb(struct bufferevent *bev, short event, void *arg)

  103. {

  104.  
  105. if (event & BEV_EVENT_EOF)

  106. printf("connection closed\n");

  107. else if (event & BEV_EVENT_ERROR)

  108. printf("some other error\n");

  109.  
  110. //这将自动close套接字和free读写缓冲区

  111. bufferevent_free(bev);

  112.  
  113. struct event *ev = (struct event*)arg;

  114. //因为socket已经没有,所以这个event也没有存在的必要了

  115. event_free(ev);

  116. }

  117.  
  118.  
  119. typedef struct sockaddr SA;

  120. int tcp_connect_server(const char* server_ip, int port)

  121. {

  122. int sockfd, status, save_errno;

  123. struct sockaddr_in server_addr;

  124.  
  125. memset(&server_addr, 0, sizeof(server_addr) );

  126.  
  127. server_addr.sin_family = AF_INET;

  128. server_addr.sin_port = htons(port);

  129. status = inet_aton(server_ip, &server_addr.sin_addr);

  130.  
  131. if( status == 0 ) //the server_ip is not valid value

  132. {

  133. errno = EINVAL;

  134. return -1;

  135. }

  136.  
  137. sockfd = ::socket(PF_INET, SOCK_STREAM, 0);

  138. if( sockfd == -1 )

  139. return sockfd;

  140.  
  141.  
  142. status = ::connect(sockfd, (SA*)&server_addr, sizeof(server_addr) );

  143.  
  144. if( status == -1 )

  145. {

  146. save_errno = errno;

  147. ::close(sockfd);

  148. errno = save_errno; //the close may be error

  149. return -1;

  150. }

  151.  
  152. evutil_make_socket_nonblocking(sockfd);

  153.  
  154. return sockfd;

  155. }

欢迎留言交流学习
参考链接:
https://www.felix021.com/blog/read.php?2068
http://blog.csdn.net/luotuo44/article/details/39670221

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值