• libevent笔记2:Hello_World


    本篇通过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!
    ";
    
    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!
    ");
    		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!
    ");
    		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!
    ");
    		return 1;
    	}
    
    	event_base_dispatch(base);
    
    	evconnlistener_free(listener);
    	event_free(signal_event);
    	event_base_free(base);
    
    	printf("done
    ");
    	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
    ");
    		bufferevent_free(bev);
    	}
    }
    
    static void
    conn_eventcb(struct bufferevent *bev, short events, void *user_data)
    {
    	if (events & BEV_EVENT_EOF) {
    		printf("Connection closed.
    ");
    	} else if (events & BEV_EVENT_ERROR) {
    		printf("Got an error on the connection: %s
    ",
    		    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.
    ");
    
    	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 写入数据的长度;
  • 相关阅读:
    数据库之01-数据库概述
    Bootstrap框架
    jQuery
    补充:html速查表
    BOM,DOM相关案例
    BOM,DOM
    函数,词法分析,内置对象和方法
    前端 之 JaveScript 基础语法: 数据类型; 运算符; 数据转换; 流程控制; 常用内置对象;
    favicon.ioc使用以及注意事项
    redux-undo
  • 原文地址:https://www.cnblogs.com/sunminming/p/11862914.html
Copyright © 2020-2023  润新知