libevent绑定监听和读写数据

Posted cpp加油站

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了libevent绑定监听和读写数据相关的知识,希望对你有一定的参考价值。

1. 绑定和监听

在上一篇文章中,以epoll为例说到了事件机制,会按顺序调用init和dispatch这两个回调函数,但是,我们回忆一下网络编程的过程,首先是需要创建socket、绑定socket、监听socket的,但目前为止还并没有涉及到,再去看源代码,会发现里面有listener.c,这个文件里面就会去做创建socket的过程。

看evconnlistener_new_bind函数,如下:

struct evconnlistener *
evconnlistener_new_bind(struct event_base *base, evconnlistener_cb cb,
    void *ptr, unsigned flags, int backlog, const struct sockaddr *sa,
    int socklen)
{
 struct evconnlistener *listener;
 evutil_socket_t fd;
 int on = 1;
 int family = sa ? sa->sa_family : AF_UNSPEC;
 int socktype = SOCK_STREAM | EVUTIL_SOCK_NONBLOCK;

 if (backlog == 0)
  return NULL;

 if (flags & LEV_OPT_CLOSE_ON_EXEC)
  socktype |= EVUTIL_SOCK_CLOEXEC;

 //调用socket函数
 fd = evutil_socket_(family, socktype, 0);
 if (fd == -1)
  return NULL;

 //设置存货检测
 if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void*)&on, sizeof(on))<0)
  goto err;

 //设置地址重用
 if (flags & LEV_OPT_REUSEABLE) {
  if (evutil_make_listen_socket_reuseable(fd) < 0)
   goto err;
 }

 //设置端口重用
 if (flags & LEV_OPT_REUSEABLE_PORT) {
  if (evutil_make_listen_socket_reuseable_port(fd) < 0)
   goto err;
 }

 //设置延迟接收
 if (flags & LEV_OPT_DEFERRED_ACCEPT) {
  if (evutil_make_tcp_listen_socket_deferred(fd) < 0)
   goto err;
 }

 //调用bind函数
 if (sa) {
  if (bind(fd, sa, socklen)<0)
   goto err;
 }

 //evconnlistener_new函数里面会调用listen函数
 listener = evconnlistener_new(base, cb, ptr, flags, backlog, fd);
 if (!listener)
  goto err;

 return listener;
err:
 evutil_closesocket(fd);
 return NULL;
}

上面的代码我加了注释,说的很清楚,从创建、绑定、设置属性一直到监听整个都调用了,这里不再多说。

evconnlistener_new函数不只会调用listen,还会注册一个监听回调函数,如下:

struct evconnlistener *
evconnlistener_new(struct event_base *base,
    evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,
    evutil_socket_t fd)
{
 struct evconnlistener_event *lev;

#ifdef _WIN32
 if (base && event_base_get_iocp_(base)) {
  const struct win32_extension_fns *ext =
   event_get_win32_extension_fns_();
  if (ext->AcceptEx && ext->GetAcceptExSockaddrs)
   return evconnlistener_new_async(base, cb, ptr, flags,
    backlog, fd);
 }
#endif

 if (backlog > 0) {
  if (listen(fd, backlog) < 0)
   return NULL;
 } else if (backlog < 0) {
  if (listen(fd, 128) < 0)
   return NULL;
 }

 lev = mm_calloc(1, sizeof(struct evconnlistener_event));
 if (!lev)
  return NULL;

 lev->base.ops = &evconnlistener_event_ops;
 //注册回调函数,当监听到有新的连接时,就会调用该函数
 lev->base.cb = cb;
 lev->base.user_data = ptr;
 lev->base.flags = flags;
 lev->base.refcnt = 1;

 lev->base.accept4_flags = 0;
 if (!(flags & LEV_OPT_LEAVE_SOCKETS_BLOCKING))
  lev->base.accept4_flags |= EVUTIL_SOCK_NONBLOCK;
 if (flags & LEV_OPT_CLOSE_ON_EXEC)
  lev->base.accept4_flags |= EVUTIL_SOCK_CLOEXEC;

 if (flags & LEV_OPT_THREADSAFE) {
  EVTHREAD_ALLOC_LOCK(lev->base.lock, EVTHREAD_LOCKTYPE_RECURSIVE);
 }

 event_assign(&lev->listener, base, fd, EV_READ|EV_PERSIST,
     listener_read_cb, lev);

 if (!(flags & LEV_OPT_DISABLED))
     evconnlistener_enable(&lev->base);

 return &lev->base;
}

evconnlistener_cb回调函数声明如下:

typedef void (*evconnlistener_cb)(struct evconnlistener *, evutil_socket_t, struct sockaddr *, int socklen, void *);

该回调函数需要我们自己实现,看sample目录中hello-world.c中实现的该函数代码如下:

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;

 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);
 bufferevent_disable(bev, EV_READ);

 bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
}

2. 读写数据

libevent是基于事件的,它的很多动作都是调用事先注册好的回调函数来解决的,读写数据也不例外。

看上面第一节中,监听回调函数里面使用了bufferevent_setcb,这个函数会注册读写事件的回调函数,如下:

void
bufferevent_setcb(struct bufferevent *bufev,
    bufferevent_data_cb readcb, bufferevent_data_cb writecb,
    bufferevent_event_cb eventcb, void *cbarg)
{
 BEV_LOCK(bufev);

 bufev->readcb = readcb;
 bufev->writecb = writecb;
 bufev->errorcb = eventcb;

 bufev->cbarg = cbarg;
 BEV_UNLOCK(bufev);
}

当有可读事件时会调用readcb函数,当有可写事件时调用writecb函数,发生错误时调用eventcb函数。
bufferevent_data_cb声明如下:

typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);

还是看看sample目录中hello-world.c中对该函数的定义,如下:

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);
 }
}

我们自己使用时可以参照sample目录中的例子,这里就不再细说了。

以上是关于libevent绑定监听和读写数据的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 libevent 在两个端点之间进行读写的示例?

Vue数据绑定原理及简单实现

动态数据绑定之监听对象变化

时间绑定和事件监听!

libevent信号事件监听

socket异步编程--libevent的使用