本篇通过libevent提供的Hello_World demo简单介绍基于libevent的TCP服务器的实现

listener

listener是libevent提供的一种监听本地端口的数据结构,在有客户端的连接到来时调用给定的回调函数。

bufferevent

上一篇中的event是不带缓存区的,读写直接在文件描述符所指向的对象(上一节中是有名管道)上进行。bufferent则是带缓冲区的event,对bufferevnet的读写操作不会直接作用在I/O上,而是对输入或输出缓存区操作。对bufferevent的读操作会从文件描述符相应的输入缓存区读数据;而写操作会将数据写进文件描述符相应的输出缓存区。

Hello_World

以下是官网提供的demo


#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#ifndef _WIN32
#include <netinet/in.h>
# ifdef _XOPEN_SOURCE_EXTENDED
# include <arpa/inet.h>
# endif
#include <sys/socket.h>
#endif #include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/listener.h>
#include <event2/util.h>
#include <event2/event.h> static const char MESSAGE[] = "Hello, World!\n"; static const int PORT = 9995; static void listener_cb(struct evconnlistener *, evutil_socket_t,
struct sockaddr *, int socklen, void *);
static void conn_writecb(struct bufferevent *, void *);
static void conn_eventcb(struct bufferevent *, short, void *);
static void signal_cb(evutil_socket_t, short, void *); int main(int argc, char **argv)
{
struct event_base *base;
struct evconnlistener *listener;
struct event *signal_event; struct sockaddr_in sin;
#ifdef _WIN32
WSADATA wsa_data;
WSAStartup(0x0201, &wsa_data);
#endif base = event_base_new();
if (!base) {
fprintf(stderr, "Could not initialize libevent!\n");
return 1;
} memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
//create a listrener
listener = evconnlistener_new_bind(base, listener_cb, (void *)base,
LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE, -1,
(struct sockaddr*)&sin,
sizeof(sin)); if (!listener) {
fprintf(stderr, "Could not create a listener!\n");
return 1;
} signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base); if (!signal_event || event_add(signal_event, NULL)<0) {
fprintf(stderr, "Could not create/add a signal event!\n");
return 1;
} event_base_dispatch(base); evconnlistener_free(listener);
event_free(signal_event);
event_base_free(base); printf("done\n");
return 0;
}
//invoke when a connection is listened by listener created in main
static void
listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
struct sockaddr *sa, int socklen, void *user_data)
{
struct event_base *base = user_data;
struct bufferevent *bev;
//create a new bufferevent over a existing socket bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
if (!bev) {
fprintf(stderr, "Error constructing bufferevent!");
event_base_loopbreak(base);
return;
}
bufferevent_setcb(bev, NULL, conn_writecb, conn_eventcb, NULL);
bufferevent_enable(bev, EV_WRITE);//enable write to bev
bufferevent_disable(bev, EV_READ);//disable read from bev bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
} static void
conn_writecb(struct bufferevent *bev, void *user_data)
{ struct evbuffer *output = bufferevent_get_output(bev);
if (evbuffer_get_length(output) == 0) {
printf("flushed answer\n");
bufferevent_free(bev);
}
} static void
conn_eventcb(struct bufferevent *bev, short events, void *user_data)
{
if (events & BEV_EVENT_EOF) {
printf("Connection closed.\n");
} else if (events & BEV_EVENT_ERROR) {
printf("Got an error on the connection: %s\n",
strerror(errno));/*XXX win32*/
}
/* None of the other events can happen here, since we haven't enabled
* timeouts */
bufferevent_free(bev);
} static void
signal_cb(evutil_socket_t sig, short events, void *user_data)
{
struct event_base *base = user_data;
struct timeval delay = { 2, 0 }; printf("Caught an interrupt signal; exiting cleanly in two seconds.\n"); event_base_loopexit(base, &delay);
}

编译并运行文件:

sunminming@sunminming:~/libevent/helloworld$ gcc helloworld.c -o main -levent
sunminming@sunminming:~/libevent/helloworld$ ./main

之后另开一个终端:

sunminming@sunminming:~$ nc 127.0.0.1 9995
Hello, World!

在第一个终端键入Ctrl+C

sunminming@sunminming:~/libevent/helloworld$ gcc helloworld.c -o main -levent
sunminming@sunminming:~/libevent/helloworld$ ./main
flushed answer
^CCaught an interrupt signal; exiting cleanly in two seconds.
done
sunminming@sunminming:~/libevent/helloworld$

对于这个TCP服务器,整体的逻辑如下:

  • 新建一个event_base,然后在其上绑定一个listener来监听特定端口(9995);

  • 新建一个处理信号的事件signal_event,并与上一步中的event_base绑定;

  • 调用event_base_dispatch来监控两个事件,包括指定的TCP连接及SIGINT信号:

    • 当监听到一个连接时,libevent会触发listen_callback,即为上面的listener_cb函数,该函数首先基于底层的socket建立一个bufferevent,并设置为可写不可读,最后调用bufferevent_write函数向其中缓存区写。

      • bufferevent_write函数写后回触发写回调函数conn_writecb,由于该例子中不需要其他操作,所以conn_writecb函数直接释放这个连接。(写回调函数的机制比较复杂,具体可以看这篇博客
    • 当中断信号(Ctrl+C)出现时,libevent会触发signal_event的回调函数signal_cb,该函数会在2秒后停止event_base的监听,并退回到主函数;

  • 主函数依次释放listenersignal_eventevent_base的资源,结束。

相关函数

  • evconnlistener * evconnlistener_new_bind (struct event_base *base,

                           evconnlistener_cb cb,

                void *ptr, unsigned flags, int backlog,

                const struct sockaddr *sa,

                int socklen):

    该函数创建一个evconnlistener结构体,即监听器,用来监听给定的地址sa,再监听到新的客户端连接后,调用cb函数。参数列表如下:

    • struct event_base *base 该监听器绑定的event_base;
    • evconnlistener_cb cb 连接回调函数;
    • void ptr 提供给回调函数cb的参数,cb一共有5个参数:第一个是监听器,第二个是用于与客户端连接的socket,第三个是sockaddr结构体,表示客户端的IP地址及端口号,第四个是socketaddr的长度,第五个则是由用户提供的参数,及ptr;
    • unsigned flags&ensp一些标志位,LEV_OPT_REUSEABLE表示同一个端口的重复使用不会有时间间隔;LEV_OPT_CLOSE_ON_FREE表示当释放一个监听器时同时释放底层socket;
    • int backlog 一般设为-1;
    • const struct sockaddr *sa 一个sockaddr结构体,表示需要监听的本地括IP地址及端口号;
    • int socklen 上一个参数sa的长度;
  • bufferevent* bufferevent_socket_new ( struct event_base * base,

             evutil_socket_t fd,

             int options):

    该函数用于创建一个bufferevent结构体。参数列表如下:

    • struct event_base *base 该bufferevent结构体绑定的event_base;
    • evutil_socket_t fd 底层读写的文件描述符,该参数不允许是一个管道,允许设为-1,并再之后通过其他函数设置;
    • int options 标识符,BEV_OPT_CLOSE_ON_FREE表示在释放bufferevent时释放底层文件描述符;
  • int event_base_loopbreak ( struct event_base * eb ):

    • 立即停止监听循环,该函数通常是在事件的回调函数中被调用,可以理解为中断;
  • void bufferevent_setcb ( struct bufferevent * bufev,

              bufferevent_data_cb readcb,

              bufferevent_data_cb writecb,

              bufferevent_event_cb eventcb,

              void * cbarg):

    该函数用于为bufferevent设置读写/事件回调函数。参数列表如下:

    • struct bufferevent * bufev 需要设置的bufferevent
    • bufferevent_data_cb readcb 读回调函数,但输入缓存区有数据可读时调用,可以设置为NULL;
    • bufferevent_data_cb readcb 写回调函数,但输出缓存区可写时调用,可以设置为NULL;
    • bufferevent_event_cb eventcb 事件回调函数,但有其他事件发生时调用,可以设置为NULL;
    • void * cbarg 提供给各个回调函数的参数;
  • int bufferevent_enable/bufferevent_disable ( struct bufferevent * bufev,

           short event):

    该函数能允许bufferevnet能够/不能够被读或写。参数列表如下:

    • struct bufferevent * bufev 该函数操作的bufferevent
    • short event 任意的读写组合,EV_READ表示可读,EV_WRITE表示可写, EV_READ | EV_WRITE表示可读写;
  • int bufferevent_write( struct bufferevent * bufev,

            const void * data,

            size_t size):

    用于向bufferevent的输出缓存区写入数据,之后这些数据会自动写入对应的文件描述符。参数列表如下:

    • struct bufferevent * bufev 需要写入的bufferevent
    • const void * data 写入的数据;
    • size_t size 写入数据的长度;

libevent笔记2:Hello_World的更多相关文章

  1. libevent笔记6:ssl bufferevent

    Libevent另外提供了基于openssl的bufferevent来支持ssl,通过特殊的ssl bufferevent来对数据进行加密. ps:本文不对openssl相应的接口做介绍因为不熟 SS ...

  2. libevent笔记5:水位watermarks

    bufferevent中提供了对读写回调的触发条件及最大缓存长度的设置,即低高水位: 低水位:是读写回调函数的最低触发数据长度,当输入/输出缓存区中的数据长度小于低水位时,读/写回调函数不会被触发: ...

  3. libevent笔记4:Filter_bufferevent过滤器

    Filter_bufferevent是一种基于bufferevent的过滤器,其本身也是一个bufferevent.能够对底层bufferevent输入缓存区中的数据进行操作(加/解密等)后再读取,同 ...

  4. libevent笔记3:evbuffer

    evbuffer 之前提到bufferevent结构体提供两个缓存区用来为读写提供缓存,并自动进行IO操作.这两个缓存区是使用Libevent中的evbuffer实现的,同样,Libevent中也提供 ...

  5. libevent笔记1:安装及DEMO

    本篇简单记录了libevent的安装过程及基础的先进先出管道Demo,其中demo来自这篇博客,安装过程在这篇博客 实验环境 系统:Ubuntu 18.04.3 libevent版本:libevent ...

  6. libevent源码阅读笔记(一):libevent对epoll的封装

    title: libevent源码阅读笔记(一):libevent对epoll的封装 最近开始阅读网络库libevent的源码,阅读源码之前,大致看了张亮写的几篇博文(libevent源码深度剖析 h ...

  7. 【传智播客】Libevent学习笔记(三):事件循环

    目录 00. 目录 01. event_base_loop函数 02. event_base_dispatch函数 03. event_base_loopexit函数 04. event_base_l ...

  8. Libevent库学习笔记

    Libevent是一个事件触发的网络库,适用于windows.linux.bsd等多种平台,Libevent在底层select.pool.kqueue和epoll等机制基础上,封装出一致的事件接口.可 ...

  9. libevent学习笔记 一、基础知识【转】

    转自:https://blog.csdn.net/majianfei1023/article/details/46485705 欢迎转载,转载请注明原文地址:http://blog.csdn.net/ ...

随机推荐

  1. kvm虚拟机的克隆

    kvm虚拟机的克隆分为两种情况 (1) KVM主机本机虚拟机直接克隆. (2) 通过复制配置文件与磁盘文件的虚拟机复制克隆(适用于异机的静态迁移). 1.  本机虚拟机直接克隆 (1) 查看虚拟机配置 ...

  2. torch.max

    torch.max() torch.max(input) -> Tensor Explation: ​ Returns the maximum value of all elements in ...

  3. ELK学习笔记之ElasticSearch的集群(Cluster),节点(Node),分片(Shard),Indices(索引),replicas(备份)之间关系

    [Cluster]集群,一个ES集群由一个或多个节点(Node)组成,每个集群都有一个cluster name作为标识----------------------------------------- ...

  4. mongodb 更新数据时int32变为double的解决办法

       场景: 在命令手动的修改签到表的整型字段synState,multi参数是可以更新多条,如果是false则更新一条. db.getCollection("ClassRecordOneD ...

  5. Redis配置过程中的问题

    记录一下配置过程中的坑~~ 当Redis在服务器上安装完成后,get.set没有问题了,接下来在程序中使用看看... 首先 在配置文件redis.conf中,默认的bind 接口是127.0.0.1, ...

  6. 3-RocketMQ 简单梳理 及 集群部署笔记

    原文:https://www.cnblogs.com/kevingrace/p/9015836.html 一.RocketMQ 基础知识介绍Apache RocketMQ是阿里开源的一款高性能.高吞吐 ...

  7. vue项目开发遇见bug

    1.附件的点击问题,与原生交互的问题: 原生的调用对象和vue的调用对象不同,注意原生方法的位置. 2.10.2以下fetch请求数据的问题(检查是否可以使用 can i use) 10.2以下ios ...

  8. shell公共函数functions

    checkpid:检查是否已存在pid,如果有一个存在,返回0(通过查看/proc目录) daemon:启动服务 killproc:杀死某个进程 pidfileofproc:寻找某个进程的pid pi ...

  9. Jenkins+GitLab+Docker+SpringCloud实现可持续自动化微服务

    本文很长很长,但是句句干货,点赞关注收藏后有惊喜在文末等你 现有混合云平台的场景下,即有线下和线上的环境,又有测试与正式的场景,而且结合了Docker,导致打包内容有所区分,且服务的发布流程复杂起来, ...

  10. Cloud Alert 实现告警智能降噪,成功规避告警风暴

    # 前言 睿象云前段时间发表了一篇[< Zabbix 实现电话.邮件.微信告警通知的实践分享>](https://www.toutiao.com/i6734876723126469127/ ...