• libevent


    libevent

    库概念和特点

            开源。精简。跨平台(Windows、Linux、maxos、unix)。专注于网络通信(不一定非用在网络当中,比如下面的读写管道)。

            libevent特性:基于"事件",面向“文件描述符”的异步(回调)通信模型。

            异步:函数"注册"时间和函数真正被执行的时间不同,函数真正是被内核调用(等待某一条件满足)

    事件库:就干一件事,监听文件描述符的库机制。

            先创建一个事件底座,然后创建你想监听的时间,插到底座上,libevent底座就会帮忙监听事件,当事件发生时,就会自动调用事件的回调函数。】

    libevent使用框架

    常规事件

    常规事件函数
    1. //创建event_base(事件底座)
    2. struct event_base* base = event_base_new();
    3. //创建事件event
    4. 常规事件event:event_new();
    5. 例如:
    6. struct event* signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);
    7. buffer event:bufferevent_socket_new();
    8. //将事件添加到base上
    9. int event_add(struct event* ev, const struct timeval* tv);
    10. //循环监听事件满足
    11. int event_base_dispatch(struct event_base* base);
    12. event_base_dispatch(base); //调用
    13. //释放event_base
    14. event_base_free(base);
    其他函数

     libevent函数使用的一个简单的例子:
    1. /*
    2. This example program provides a trivial server program that listens for TCP
    3. connections on port 9995. When they arrive, it writes a short message to
    4. each client connection, and closes each connection once it is flushed.
    5. Where possible, it exits cleanly in response to a SIGINT (ctrl-c).
    6. */
    7. #include
    8. #include
    9. #include
    10. #include
    11. #ifndef _WIN32
    12. #include
    13. # ifdef _XOPEN_SOURCE_EXTENDED
    14. # include
    15. # endif
    16. #include
    17. #endif
    18. #include
    19. #include
    20. #include
    21. #include
    22. #include
    23. static const char MESSAGE[] = "Hello, World!\n";
    24. static const int PORT = 9995;
    25. static void listener_cb(struct evconnlistener *, evutil_socket_t,
    26. struct sockaddr *, int socklen, void *);
    27. static void conn_writecb(struct bufferevent *, void *);
    28. static void conn_eventcb(struct bufferevent *, short, void *);
    29. static void signal_cb(evutil_socket_t, short, void *);
    30. //main函数中是使用框架,剩下外面的函数就是事件的回调
    31. int main(int argc, char **argv)
    32. {//创建事件底座指针
    33. struct event_base *base;
    34. struct evconnlistener *listener;
    35. //创建事件指针
    36. struct event *signal_event;
    37. //创建套接字的地址结构
    38. struct sockaddr_in sin;
    39. #ifdef _WIN32
    40. WSADATA wsa_data;
    41. WSAStartup(0x0201, &wsa_data);
    42. #endif
    43. //真正创建事件底座
    44. base = event_base_new();
    45. if (!base) {
    46. fprintf(stderr, "Could not initialize libevent!\n");
    47. return 1;
    48. }
    49. memset(&sin, 0, sizeof(sin));
    50. //初始化套接字的地址结构
    51. sin.sin_family = AF_INET;
    52. sin.sin_port = htons(PORT);
    53. //一个函数干了套接字通信的 socket、listen、bind、accept几个函数的事情
    54. listener = evconnlistener_new_bind(base, listener_cb, (void *)base,
    55. LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE, -1,
    56. (struct sockaddr*)&sin,
    57. sizeof(sin));
    58. if (!listener) {
    59. fprintf(stderr, "Could not create a listener!\n");
    60. return 1;
    61. }
    62. //注册一个信号事件
    63. signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);
    64. if (!signal_event || event_add(signal_event, NULL)<0) {
    65. fprintf(stderr, "Could not create/add a signal event!\n");
    66. return 1;
    67. }
    68. //循环监听,相当于 while循环加里面的epoll_wait;下面的循环有很大概率没有机会调用
    69. event_base_dispatch(base);
    70. //释放之前创建的事件
    71. evconnlistener_free(listener);
    72. event_free(signal_event);
    73. event_base_free(base);
    74. printf("done\n");
    75. return 0;
    76. }
    77. //下面是千篇一律,定义了三个回调,分别独立对应三个事件。看一个就行
    78. static void listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
    79. struct sockaddr *sa, int socklen, void *user_data)
    80. {
    81. struct event_base *base = user_data;
    82. struct bufferevent *bev;
    83. bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
    84. if (!bev) {
    85. fprintf(stderr, "Error constructing bufferevent!");
    86. event_base_loopbreak(base);
    87. return;
    88. }
    89. bufferevent_setcb(bev, NULL, conn_writecb, conn_eventcb, NULL);
    90. bufferevent_enable(bev, EV_WRITE);
    91. bufferevent_disable(bev, EV_READ);
    92. bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
    93. }
    94. static void conn_writecb(struct bufferevent *bev, void *user_data)
    95. {
    96. struct evbuffer *output = bufferevent_get_output(bev);
    97. if (evbuffer_get_length(output) == 0) {
    98. printf("flushed answer\n");
    99. bufferevent_free(bev);
    100. }
    101. }
    102. static void conn_eventcb(struct bufferevent *bev, short events, void *user_data)
    103. {
    104. if (events & BEV_EVENT_EOF) {
    105. printf("Connection closed.\n");
    106. } else if (events & BEV_EVENT_ERROR) {
    107. printf("Got an error on the connection: %s\n",
    108. strerror(errno));/*XXX win32*/
    109. }
    110. /* None of the other events can happen here, since we haven't enabled
    111. * timeouts */
    112. bufferevent_free(bev);
    113. }
    114. static void signal_cb(evutil_socket_t sig, short events, void *user_data)
    115. {
    116. struct event_base *base = user_data;
    117. struct timeval delay = { 2, 0 };
    118. printf("Caught an interrupt signal; exiting cleanly in two seconds.\n");
    119. event_base_loopexit(base, &delay);
    120. }
    常规事件函数使用详解
    *事件创建 event_new:
    1. struct event* event_new(struct event_base* base,evutil_socket_t fd,short what,event_callback_fd cb,void* arg);
    2. //
    3. 参数:
    4. base:基事件, 也就是event_base_new()的返回值
    5. fd:绑定到event上的文件描述符,监听的对象
    6. what:文件描述符对应的事件(r/w/e),监听类型是什么
    7. what的取值:
    8. EV_READ:读一次后,退出循环
    9. EV_WRITE:写一次,退出循环
    10. EV_PERSIST:持续触发,可以理解为while(read())或while(write())
    11. cb:一旦满足监听条件,回调的函数
    12. arg:回调函数的参数
    13. //返回值:成功返回创建的事件对象event
    事件回调函数:

    参数跟上面一致(实际用时候不用传参)

    1. typedef void (*event_callback_fn)(evutil_socket_t fd,short what,void* arg);
    2. f
    事件添加(到event_base上)
    1. int event_add(struct event* ev,const strcut timeval* tv);
    2. //参数:
    3. ev是要添加的事件对象,就是event_new的返回值
    4. tv一般传NULL,表示一直等到事件被触发,回调函数才会被调用。如果传非0,会等待事件被触发,如果事件一直不触发,时间到,回调函数依然会被调用
    5. //返回值:成功返回0;失败返回-1
    事件删除(对应add,从event_base上摘下事件(不常用)):
    1. int event_del(struct event* ev);
    2. //ev是要摘下的事件对象,就是event_new的返回值
    事件销毁:
    1. int event_free(strcut event* ev);
    2. //ev是要销毁的事件对象,就是event_new的返回值
    基于libevent实现的FIFO的读写
    读FIFO进程:
    1. void perr_exit(const char* str) {
    2. perror(str);
    3. exit(1);
    4. }
    5. //读回调
    6. void read_cb(evutil_socket_t fd, short what, void* arg) {
    7. char buf[BUFSIZ] = {0};
    8. read(fd, buf, sizeof(buf));
    9. printf("Read from writer:%s\n", buf);
    10. printf("what=%s\n", what & EV_READ ? "Yes" : "No");
    11. sleep(1);
    12. return;
    13. }
    14. int main(int argc, char* argv[]) {
    15. int ret = 0;
    16. int fd = 0;
    17. unlink("myfifo");
    18. mkfifo("myfifo", 0644);
    19. fd = open("myfifo", O_RDONLY | O_NONBLOCK);//借助epoll反应堆,默认都是非阻塞
    20. if (fd == -1)
    21. perr_exit("open error");
    22. //读管道时候,管道中不一定有数据,此时就需要有事件概念介入了
    23. //创建事件基座
    24. struct event_base* base = event_base_new();
    25. struct event* ev = NULL;
    26. //创建事件
    27. ev = event_new(base, fd, EV_READ | EV_PERSIST, read_cb, NULL);
    28. //添加到事件基座
    29. event_add(ev, NULL);
    30. //开启事件监听
    31. event_base_dispatch(base);
    32. //释放事件
    33. event_base_free(base);
    34. close(fd);
    35. return 0;
    36. }
    写FIFO进程:
    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. // 对操作处理函数
    10. void write_cb(evutil_socket_t fd, short what, void *arg)
    11. {
    12. // write管道
    13. char buf[1024] = {0};
    14. static int num = 0;
    15. sprintf(buf, "hello,world-%d\n", num++);
    16. write(fd, buf, strlen(buf)+1);
    17. sleep(1);
    18. }
    19. // 写管道
    20. int main(int argc, const char* argv[])
    21. {
    22. // open file
    23. //int fd = open("myfifo", O_WRONLY | O_NONBLOCK);
    24. int fd = open("myfifo", O_WRONLY);
    25. if(fd == -1)
    26. {
    27. perror("open error");
    28. exit(1);
    29. }
    30. // 写管道
    31. struct event_base* base = NULL;
    32. base = event_base_new();
    33. // 创建事件
    34. struct event* ev = NULL;
    35. // 检测的写缓冲区是否有空间写
    36. //ev = event_new(base, fd, EV_WRITE , write_cb, NULL);
    37. ev = event_new(base, fd, EV_WRITE | EV_PERSIST, write_cb, NULL);
    38. // 添加事件
    39. event_add(ev, NULL);
    40. // 事件循环
    41. event_base_dispatch(base);
    42. // 释放资源
    43. event_free(ev);
    44. event_base_free(base);
    45. close(fd);
    46. return 0;
    47. }
     重点关注对写事件event_new函数入参,写监听时长的定义,写一次退出循环和一直触发的区别:

    libevent模型,事件的运行状态:未决和非未决

    未决:有资格被处理,但还没有被处理 (只剩数据没到达了)

    非未决:没有资格被处理 (除了数据没到达,还有其他监听条件没满足,比如事件没add)

    小注意:事件被处理完,如果设置了persist,add会被再调用一次
    在这里插入图片描述

    socket通信事件 bufferevent

    bufferevent概念

    bufferevent的事件对象fd有两个缓冲区。

    读:读缓冲区有数据,读回调函数被调用,使用bufferevent_read()读数据

    写:使用bufferevent_write,向写缓冲中写数据,该缓冲区中有数据自动写出,写完后,回调函数被调用(鸡肋)

    在这里插入图片描述

    原理:bufferent利用队列实现两个缓冲区(数据只能读一次,读走就没, FIFO)

    bufferevent事件对象创建和销毁
    创建socket事件对象 bufferevent_socket_new:
    1. struct bufferevent* bufferevent_socket_new(struct event_base* base,
    2. evutil_socket_t fd,
    3. enum bfferevent_options options)
    4. //入参
    5. base:基事件,event_base_new函数的返回值
    6. fd:封装到bufferevent内的fd(绑定在一起),即lfd
    7. enum表示枚举类型,一般取BEV_OPT_CLOSE_ON_FREE(关闭时释放底层套接字)
    8. //返回
    9. 成功返回bufferevent事件对象

    对比event,有区别。这个不是在 new的时候设置的回调,而是借助后面的 bufferevent_setcb函数专门设置回调。

    销毁事件对象 bufferevent_socket_free:
    1. void bufferevent_socket_free(struct bufferevent* ev)
    2. //入参
    3. bufev:bufferevent_socket_new()函数的返回值
    4. readcb:读缓冲对应的回调,自己封装,在其内部读数据(注意是用bufferevent_read()读,而不是read())
    5. writecb:鸡肋,传NULL即可
    6. eventcb:可传NULL
    7. cbarg:回调函数的参数
    给bufferevent事件设置回调 bufferevent_setcb
    1. void bufferevent_setcb(struct bufferevent* bufev,
    2. bufferevent_data_cb readcb,
    3. bufferevent_data_cb writecb,
    4. bufferevent_event_cb eventcb,
    5. void* cbarg);
    6. //入参:
    7. bufev:bufferevent_socket_new()函数的返回值
    8. readcb:读缓冲对应的回调,自己封装,在其内部读数据(注意是用bufferevent_read()读,而不是read())
    9. writecb:鸡肋,传NULL即可
    10. eventcb:备用回调,比如异常回调,也可传NULL
    11. cbarg:三个回调函数的(相同)参数
     readcb对应的回调函数:

    1. void read_cb(struct bufferevent* bev,void* arg){
    2. ...
    3. bufferevent_read();
    4. }

    为什么 read_cb里面不封装read,因为没有fd给read传。fd通过new函数封装好的bufferevent传。

    size_t bufferevent_read(struct bufferevent* bufev,void* data,size_t size);
    
    writecb对应的回调函数:

    1. int bufferevent_write(struct bufferevent* bufev,const void* data,size_t size)
    eventcb对应的回调函数:
    1. typedef void (*bufferevent_event_cb)(struct bufferevent* bev,short events,void* ctx)

    buffer缓冲区开启和关闭

    默认新建的bufferevent写缓冲是enable的,而读缓冲是disable的

    通过下面函数操作缓冲区读写使能:

    1. void bufferevent_enable(struct bufferevent* bufev,short events); //启用缓冲区
    2. void bufferevnet_disable(struct bufferevent* bufev,short events); //禁用
    3. /*例如:开启读缓冲*/
    4. void bufferevent_enable(bufev,EV_READ);
    5. //events的值可传入三个宏:
    6. EV_READ
    7. EV_WRITE
    8. EV_READ|EV_WRITE

    也可获取缓冲区的禁用状态:

    1. short bufferevent_get_enable(struct bufferevent* bufev)
    客户端和服务器连接和监听函数详解
    客户端建立连接 bufferevent_socket_connect:
    1. int bufferevent_socket_connect(struct bufferevent* bev,struct sockaddr* address,int addrlen);
    2. //入参
    3. bev:bufferevent事件对象(封装了fd)
    4. address,len:等同于connect()的参2和参3
    *服务器创建监听器对象 evconnlistener_new_bind (专门用于监听新链接):

    这一个函数可以完成socket(),bind(),listen(),accept()四个函数的作用

    1. struct evconnlistener* evconnlistener_new_bind(struct event_base* base,
    2. evconnlistener_cb cb,
    3. void* ptr,
    4. unsigned flags,
    5. int backlog,
    6. const struct sockaddr* sa,
    7. int socklen);
    8. //入参
    9. cb:监听回调函数(建立连接后用户要做的操作)
    10. ptr:回调函数的参数
    11. flags:可识别的标志,通常传:
    12. LEV_OPT_CLOSE_ON_FREE(释放bufferevent时关闭底层传输端口, 这将关闭底层套接字, 释放底层bufferevent等)
    13. LEV_OPT_REUSEABLE(可以设置端口复用)
    14. backlog:相当于listen的参2,最大连接数, 传-1表示使用默认的最大值
    15. sa:服务器自己的地址结构
    16. socklen:sa的大小

    回调函数 evconnlistener_cb cb:

    监听成功后,说明客户端链接成功,原来的accept传出的客户端地址结构,现在在这里传出。

    1. typedef void (*evconnlistener_cb)(struct evconnlistener* listener,
    2. evutil_socker_t sock,
    3. struct sockaddr* addr,
    4. int len,
    5. void* ptr);
    6. //入参:
    7. listener:evconnlistener_new_bind 的返回值
    8. sock:用于通信的文件描述符,即cfd
    9. addr:传出,客户端的地址结构
    10. len:客户端地址结构的长度
    11. ptr:外部ptr传进来的值
    libevent实现TCP服务器流程

    • 创建基事件event_base
    • 使用evconlistener_new_bind()创建监听服务器,用来专门监听客户端链接事件,有新链接上来,就调用其回调函数(完成socket(),bind(),listen(),accept()四个函数的作用
    • 设置evconlistener_new_bind()回调函数listner_cb(),该回调函数被调用,说明有一个新的客户端连接上来,会得到一个新的fd,用于跟客户端通信。
    • 创建bufferevent事件对象:bufferevent_socket_new(),将fd封装到这个事件对象中
    • 使用bufferevent_setcb()函数给bufferevent的read,write,event设置回调函数
    • 设置读写缓冲enable
    • 启动循环event_base_dispath(),监听通信事件()
    • 当监听的事件满足时,read_cb会被调用,在其内部bufferevent_read(),读
    • 释放连接
    libevent实现TCP服务器源码分析
    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. // 读缓冲区回调
    11. void read_cb(struct bufferevent *bev, void *arg)
    12. {
    13. char buf[1024] = {0};
    14. bufferevent_read(bev, buf, sizeof(buf));
    15. printf("client say: %s\n", buf);
    16. char *p = "我是服务器, 已经成功收到你发送的数据!";
    17. // 发数据给客户端
    18. bufferevent_write(bev, p, strlen(p)+1);//执行完后回调write_cb
    19. sleep(1);
    20. }
    21. // 写缓冲区回调
    22. void write_cb(struct bufferevent *bev, void *arg)
    23. {
    24. printf("I'm服务器, 成功写数据给客户端,写缓冲区回调函数被回调...\n");
    25. }
    26. // 事件
    27. void event_cb(struct bufferevent *bev, short events, void *arg)
    28. {
    29. if (events & BEV_EVENT_EOF)
    30. {
    31. printf("connection closed\n");
    32. }
    33. else if(events & BEV_EVENT_ERROR)
    34. {
    35. printf("some other error\n");
    36. }
    37. bufferevent_free(bev);
    38. printf("buffevent 资源已经被释放...\n");
    39. }
    40. void cb_listener(
    41. struct evconnlistener *listener,
    42. evutil_socket_t fd,
    43. struct sockaddr *addr,
    44. int len, void *ptr)
    45. {
    46. printf("connect new client\n");
    47. struct event_base* base = (struct event_base*)ptr;
    48. //创建出用于通信的socket事件对象bufferevent,并给bufferevent缓冲区设置回调和读使能
    49. struct bufferevent *bev;
    50. //2参fd被封装在新的事件对象 bufferevent中
    51. bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
    52. bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);
    53. bufferevent_enable(bev, EV_READ);
    54. }
    55. int main(int argc, const char* argv[])
    56. {
    57. // init server
    58. struct sockaddr_in serv;
    59. memset(&serv, 0, sizeof(serv));
    60. serv.sin_family = AF_INET;
    61. serv.sin_port = htons(9876);
    62. serv.sin_addr.s_addr = htonl(INADDR_ANY);
    63. struct event_base* base;
    64. base = event_base_new();
    65. // 创建套接字
    66. // 绑定
    67. // 接收连接请求
    68. struct evconnlistener* listener;
    69. listener = evconnlistener_new_bind(base, cb_listener, base,
    70. LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
    71. 36, (struct sockaddr*)&serv, sizeof(serv));
    72. event_base_dispatch(base);
    73. evconnlistener_free(listener);
    74. event_base_free(base);
    75. return 0;
    76. }
    客户端流程简析和回顾
    1. 创建event_base
    2. 使用bufferevent_socket_new()创建一个用跟服务器通信的bufferevent事件对象
    3. 使用bufferevent_socket_connect连接服务器
    4. 使用bufferevent_setcb()给bufferevent对象的read,write,event设置回调
    5. 设置bufferevent对象的读写缓冲区使能 (4、5放connect前应该也没问题)
    6. 接受,发送数据bufferevent_read()/bufferevent_write()
    7. 启动循环监听event_base_dispath()
    8. 释放资源
    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. void read_cb(struct bufferevent *bev, void *arg)
    11. {
    12. char buf[1024] = {0};
    13. bufferevent_read(bev, buf, sizeof(buf));
    14. printf("fwq say:%s\n", buf);
    15. bufferevent_write(bev, buf, strlen(buf)+1);
    16. sleep(1);
    17. }
    18. void write_cb(struct bufferevent *bev, void *arg)
    19. {
    20. printf("----------我是客户端的写回调函数,没卵用\n");
    21. }
    22. void event_cb(struct bufferevent *bev, short events, void *arg)
    23. {
    24. if (events & BEV_EVENT_EOF)
    25. {
    26. printf("connection closed\n");
    27. }
    28. else if(events & BEV_EVENT_ERROR)
    29. {
    30. printf("some other error\n");
    31. }
    32. else if(events & BEV_EVENT_CONNECTED)
    33. {
    34. printf("已经连接服务器...\\(^o^)/...\n");
    35. return;
    36. }
    37. // 释放资源
    38. bufferevent_free(bev);
    39. }
    40. // 客户端与用户交互,从终端读取数据写给服务器
    41. void read_terminal(evutil_socket_t fd, short what, void *arg)
    42. {
    43. // 读数据
    44. char buf[1024] = {0};
    45. int len = read(fd, buf, sizeof(buf));
    46. struct bufferevent* bev = (struct bufferevent*)arg;
    47. // 发送数据
    48. bufferevent_write(bev, buf, len+1);
    49. }
    50. int main(int argc, const char* argv[])
    51. {
    52. struct event_base* base = NULL;
    53. base = event_base_new();
    54. int fd = socket(AF_INET, SOCK_STREAM, 0);
    55. // 通信的fd放到bufferevent中
    56. struct bufferevent* bev = NULL;
    57. bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
    58. // init server info
    59. struct sockaddr_in serv;
    60. memset(&serv, 0, sizeof(serv));
    61. serv.sin_family = AF_INET;
    62. serv.sin_port = htons(9876);
    63. inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
    64. // 连接服务器
    65. bufferevent_socket_connect(bev, (struct sockaddr*)&serv, sizeof(serv));
    66. // 设置回调
    67. bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);
    68. // 设置读回调生效
    69. // bufferevent_enable(bev, EV_READ);
    70. // 创建事件
    71. struct event* ev = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST,
    72. read_terminal, bev);
    73. // 添加事件
    74. event_add(ev, NULL);
    75. event_base_dispatch(base);
    76. event_free(ev);
    77. event_base_free(base);
    78. return 0;
    79. }

    问自己:bufferevent和epoll反应堆的关系??? 

  • 相关阅读:
    信创平台:查询CPU,内存等命令
    Vue 项目前端响应式布局及框架搭建
    如何快速熟悉业务系统知识以及情况
    Microsoft Outlook Lite 引入短信功能
    c语言指针加减法
    promise
    goproxy实现windows的mysql的内网穿透
    phy调试2
    ARM 中常用的汇编指令解释汇总
    cx3588 文档说明
  • 原文地址:https://blog.csdn.net/weixin_46697509/article/details/134241870