• 服务器搭建(TCP套接字)-libevent版(服务端)


         Libevent 是一个开源的事件驱动库,用于开发高性能、并发的网络应用程序。它提供了跨平台的事件处理和网络编程功能,具有高性能、可扩展性和可移植性。下面详细讲解 Libevent 的主要组成部分和使用方法。

    一、事件基础结构(event_base)

    事件基础结构(event_base)是 Libevent 的核心组件,用于管理和调度事件。它可以看作是事件循环的主要部分,负责监听和分发事件

    1.1、event_base_new

    用于创建事件基础结构的函数。

    • 原型
    struct event_base *event_base_new(void);
    
    • 1
    • 实现
    #define INT_MAX		2147483647
    
    //event_base_new 创建新的event_base
    struct event_base *
    event_base_new(void)
    {
    	struct event_base *base = NULL;
    	struct event_config *cfg = event_config_new();
    	if (cfg) {
    		base = event_base_new_with_config(cfg);
    		event_config_free(cfg);
    	}
    	return base;
    }
    
    //event_config_new
    struct event_config *
    event_config_new(void)
    {
    	struct event_config *cfg = mm_calloc(1, sizeof(*cfg));
    
    	if (cfg == NULL)
    		return (NULL);
    
    	TAILQ_INIT(&cfg->entries);
    	cfg->max_dispatch_interval.tv_sec = -1;
    	cfg->max_dispatch_callbacks = INT_MAX;//最大可分发的回调数
    	cfg->limit_callbacks_after_prio = 1;
    
    	return (cfg);
    }
    
    struct event_base *
    event_base_new_with_config(const struct event_config *cfg)
    {
    	int i;
    	struct event_base *base;
    	int should_check_environment;
    
    #ifndef EVENT__DISABLE_DEBUG_MODE
    	event_debug_mode_too_late = 1;
    #endif
    
    	if ((base = mm_calloc(1, sizeof(struct event_base))) == NULL) {
    		event_warn("%s: calloc", __func__);
    		return NULL;
    	}
    
    	if (cfg)
    		base->flags = cfg->flags;
    
    	should_check_environment =
    	    !(cfg && (cfg->flags & EVENT_BASE_FLAG_IGNORE_ENV));
    
    	{
    		struct timeval tmp;
    		int precise_time =
    		    cfg && (cfg->flags & EVENT_BASE_FLAG_PRECISE_TIMER);
    		int flags;
    		if (should_check_environment && !precise_time) {
    			precise_time = evutil_getenv_("EVENT_PRECISE_TIMER") != NULL;
    			if (precise_time) {
    				base->flags |= EVENT_BASE_FLAG_PRECISE_TIMER;
    			}
    		}
    		flags = precise_time ? EV_MONOT_PRECISE : 0;
    		evutil_configure_monotonic_time_(&base->monotonic_timer, flags);
    
    		gettime(base, &tmp);
    	}
    
    	min_heap_ctor_(&base->timeheap);
    
    	base->sig.ev_signal_pair[0] = -1;
    	base->sig.ev_signal_pair[1] = -1;
    	base->th_notify_fd[0] = -1;
    	base->th_notify_fd[1] = -1;
    
    	TAILQ_INIT(&base->active_later_queue);
    
    	evmap_io_initmap_(&base->io);
    	evmap_signal_initmap_(&base->sigmap);
    	event_changelist_init_(&base->changelist);
    
    	base->evbase = NULL;
    
    	if (cfg) {
    		memcpy(&base->max_dispatch_time,
    		    &cfg->max_dispatch_interval, sizeof(struct timeval));
    		base->limit_callbacks_after_prio =
    		    cfg->limit_callbacks_after_prio;
    	} else {
    		base->max_dispatch_time.tv_sec = -1;
    		base->limit_callbacks_after_prio = 1;
    	}
    	if (cfg && cfg->max_dispatch_callbacks >= 0) {
    		base->max_dispatch_callbacks = cfg->max_dispatch_callbacks;
    	} else {
    		base->max_dispatch_callbacks = INT_MAX;
    	}
    	if (base->max_dispatch_callbacks == INT_MAX &&
    	    base->max_dispatch_time.tv_sec == -1)
    		base->limit_callbacks_after_prio = INT_MAX;
    
    	for (i = 0; eventops[i] && !base->evbase; i++) {//①
    		if (cfg != NULL) {
    			/* determine if this backend should be avoided */
    			if (event_config_is_avoided_method(cfg,
    				eventops[i]->name))
    				continue;
    			/*
                *这里不符合我们cfg->require_features指定的I/O都不会往下走,
                *只有满足条件的才写到event_base里面去
                */
    			if ((eventops[i]->features & cfg->require_features)
    			    != cfg->require_features)
    				continue;
    		}
    
    		/* also obey the environment variables */
    		if (should_check_environment &&
    		    event_is_method_disabled(eventops[i]->name))
    			continue;
    
    		base->evsel = eventops[i];
    
    		base->evbase = base->evsel->init(base);
    	}
    
    	if (base->evbase == NULL) {
    		event_warnx("%s: no event mechanism available",
    		    __func__);
    		base->evsel = NULL;
    		event_base_free(base);
    		return NULL;
    	}
    
    	if (evutil_getenv_("EVENT_SHOW_METHOD"))
    		event_msgx("libevent using: %s", base->evsel->name);
    
    	/* allocate a single active event queue */
    	if (event_base_priority_init(base, 1) < 0) {
    		event_base_free(base);
    		return NULL;
    	}
    
    	/* prepare for threading */
    
    #if !defined(EVENT__DISABLE_THREAD_SUPPORT) && !defined(EVENT__DISABLE_DEBUG_MODE)
    	event_debug_created_threadable_ctx_ = 1;
    #endif
    
    #ifndef EVENT__DISABLE_THREAD_SUPPORT
    	if (EVTHREAD_LOCKING_ENABLED() &&
    	    (!cfg || !(cfg->flags & EVENT_BASE_FLAG_NOLOCK))) {
    		int r;
    		EVTHREAD_ALLOC_LOCK(base->th_base_lock, 0);
    		EVTHREAD_ALLOC_COND(base->current_event_cond);
    		r = evthread_make_base_notifiable(base);
    		if (r<0) {
    			event_warnx("%s: Unable to make base notifiable.", __func__);
    			event_base_free(base);
    			return NULL;
    		}
    	}
    #endif
    
    #ifdef _WIN32
    	if (cfg && (cfg->flags & EVENT_BASE_FLAG_STARTUP_IOCP))
    		event_base_start_iocp_(base, cfg->n_cpus_hint);
    #endif
    
    	return (base);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174

    • 设置支持的事件机制
          libevent 中,系统会自动选择适用的事件机制。libevent 会根据当前系统的支持情况,以及用户在编译时的配置和运行时的环境变量,来确定使用哪种事件机制。
          libevent 支持多种事件机制,如 epoll、kqueue、select、poll 等,每种事件机制都有其特定的优势和适用场景。选择事件机制的过程如下:
      • 首先,libevent 会根据编译时的配置和系统支持情况,确定可用的事件机制。这些配置选项通常是在编译 libevent 时使用 ./configure 命令指定的。
            常见的配置选项包括 –disable-epoll、–disable-kqueue、–disable-select 等,用于禁用特定的事件机制。如果用户没有指定特定的事件机制,libevent 将根据系统支持情况进行自动选择。
        在这里插入图片描述
        在这里插入图片描述
      • 运行时,libevent 会检测系统支持的事件机制并进行初始化。它会按照优先级顺序尝试初始化可用的事件机制,直到成功初始化一个事件机制或尝试完所有可用的事件机制。
      • 如果成功初始化了一个事件机制,libevent 将使用该事件机制来驱动事件循环。
      • 如果所有可用的事件机制都无法初始化,libevent 将抛出错误或警告,并无法正常工作。

    1.1.1、选择并配置特定的事件机制

    Libevent 中,可以通过以下步骤选择和配置特定的事件机制:

    • 获取系统支持的事件机制列表:使用 event_get_supported_methods 函数获取系统支持的事件机制名称列表。
       int i;
       const char **methods = event_get_supported_methods();
       printf("Starting Libevent %s. Available methods are:\n",event_get_version());
       for (i=0; methods[i] != NULL; ++i) {
        printf("Support: %s\n", methods[i]);
       }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    • 创建事件基础结构体:使用 event_base_new 函数创建事件基础结构体。该结构体将用于管理和驱动事件循环
    • 设置事件机制:通过调用 event_base_set 函数,将事件基础结构体与特定的事件机制关联起来。
    //将 `"event_method_name"` 替换为要使用的事件机制的名称,如 `"epoll"`、`"kqueue"`、`"poll"` 或 `"select"`。
    //如果事件机制设置成功,`event_base_set` 函数将返回 0。否则,返回 -1。
    int result = event_base_set(base, "event_method_name");
    
    • 1
    • 2
    • 3
    • 检查事件机制设置结果:可以通过调用 event_base_get_method 函数来获取当前事件基础结构体使用的事件机制名称,确认是否成功设置了特定的事件机制。

    符合条件的是epoll,poll,select,根据for循环条件!base->evbase 可知,第一个epoll赋给base->evbase后,循环就会结束,所以默认就是epoll。

    1.2、event_base_dispatch

    用于启动事件循环并处理注册的事件。它会一直运行,直到没有更多的活动事件或显式地停止事件循环

    • 原型
    int event_base_dispatch(struct event_base *);
    
    • 1
    • 实现
    int
    event_base_dispatch(struct event_base *event_base)
    {
    	return (event_base_loop(event_base, 0));
    }
    
    int
    event_base_loop(struct event_base *base, int flags)
    {
    	const struct eventop *evsel = base->evsel;
    	struct timeval tv;
    	struct timeval *tv_p;
    	int res, done, retval = 0;
    
    	/* Grab the lock.  We will release it inside evsel.dispatch, and again
    	 * as we invoke user callbacks. */
    	EVBASE_ACQUIRE_LOCK(base, th_base_lock);
    
    	if (base->running_loop) {
    		event_warnx("%s: reentrant invocation.  Only one event_base_loop"
    		    " can run on each event_base at once.", __func__);
    		EVBASE_RELEASE_LOCK(base, th_base_lock);
    		return -1;
    	}
    
    	base->running_loop = 1;
        /*
        *clear_time_cache 函数用于清除事件基础结构体 event_base 中的时间缓存。
        *时间缓存是用于存储上一次事件循环中的时间信息,
        *以便在下一次循环中进行快速访问而不需要每次都重新获取时间
        */
    	clear_time_cache(base);
    
    	if (base->sig.ev_signal_added && base->sig.ev_n_signals_added)
    		evsig_set_base_(base);
    
    	done = 0;
    
    #ifndef EVENT__DISABLE_THREAD_SUPPORT
    	base->th_owner_id = EVTHREAD_GET_ID();
    #endif
    
    	base->event_gotterm = base->event_break = 0;
    
    	while (!done) {
    		base->event_continue = 0;
    		base->n_deferreds_queued = 0;
    
    		/* Terminate the loop if we have been asked to */
    		if (base->event_gotterm) {
    			break;
    		}
    
    		if (base->event_break) {
    			break;
    		}
    
    		tv_p = &tv;
    		if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {
    			timeout_next(base, &tv_p);
    		} else {
    			/*
    			 * if we have active events, we just poll new events
    			 * without waiting.
    			 */
    			evutil_timerclear(&tv);
    		}
    
    		/* If we have no events, we just exit */
    		if (0==(flags&EVLOOP_NO_EXIT_ON_EMPTY) &&
    		    !event_haveevents(base) && !N_ACTIVE_CALLBACKS(base)) {
    			event_debug(("%s: no events registered.", __func__));
    			retval = 1;
    			goto done;
    		}
            /*
            *用于将执行的事件变为活动状态。这些事件是在事件循环中通过使用 event_add 
            *函数添加到执行队列中的。
            */
    		event_queue_make_later_events_active(base);
    
    		clear_time_cache(base);
          
    		res = evsel->dispatch(base, tv_p);//①
    
    		if (res == -1) {
    			event_debug(("%s: dispatch returned unsuccessfully.",
    				__func__));
    			retval = -1;
    			goto done;
    		}
    
    		update_time_cache(base);
    
    		timeout_process(base);
    
    		if (N_ACTIVE_CALLBACKS(base)) {
    			int n = event_process_active(base);
    			if ((flags & EVLOOP_ONCE)
    			    && N_ACTIVE_CALLBACKS(base) == 0
    			    && n != 0)
    				done = 1;
    		} else if (flags & EVLOOP_NONBLOCK)
    			done = 1;
    	}
    	event_debug(("%s: asked to terminate loop.", __func__));
    
    done:
    	clear_time_cache(base);
    	base->running_loop = 0;
    
    	EVBASE_RELEASE_LOCK(base, th_base_lock);
    
    	return (retval);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115

    • 启动事件循环派发。
      事件循环是 Libevent 的核心机制,用于监听和处理事件。通过调用 event_base_dispatch 函数启动事件循环,示例代码如下:
    int ret = event_base_dispatch(base);
    if (ret == -1) {
        // 事件循环启动失败,进行错误处理
        return -1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    事件循环会一直运行,直到没有事件需要处理或者调用 event_base_loopbreak 或 event_base_loopexit 函数终止事件循环。

    在事件循环中,Libevent 会不断地监听事件并分发给相应的回调函数进行处理。当事件满足触发条件时,注册的回调函数将被调用。

    1.3、event_base_free

    • 原型
    void event_base_free(struct event_base *);
    
    • 1
    • 实现
    static void
    event_base_free_(struct event_base *base, int run_finalizers)
    {
    	int i, n_deleted=0;
    	struct event *ev;
    	/* XXXX grab the lock? If there is contention when one thread frees
    	 * the base, then the contending thread will be very sad soon. */
    
    	/* event_base_free(NULL) is how to free the current_base if we
    	 * made it with event_init and forgot to hold a reference to it. */
    	if (base == NULL && current_base)
    		base = current_base;
    	/* Don't actually free NULL. */
    	if (base == NULL) {
    		event_warnx("%s: no base to free", __func__);
    		return;
    	}
    	/* XXX(niels) - check for internal events first */
    
    #ifdef _WIN32
    	event_base_stop_iocp_(base);//①
    #endif
    
    	/* threading fds if we have them */
    	if (base->th_notify_fd[0] != -1) {
    		event_del(&base->th_notify);
    		EVUTIL_CLOSESOCKET(base->th_notify_fd[0]);
    		if (base->th_notify_fd[1] != -1)
    			EVUTIL_CLOSESOCKET(base->th_notify_fd[1]);
    		base->th_notify_fd[0] = -1;
    		base->th_notify_fd[1] = -1;
    		event_debug_unassign(&base->th_notify);
    	}
    
    	/* Delete all non-internal events. */
    	evmap_delete_all_(base);//②
    
    	while ((ev = min_heap_top_(&base->timeheap)) != NULL) {
    		event_del(ev);
    		++n_deleted;
    	}
    	for (i = 0; i < base->n_common_timeouts; ++i) {
    		struct common_timeout_list *ctl =
    		    base->common_timeout_queues[i];
    		event_del(&ctl->timeout_event); /* Internal; doesn't count */
    		event_debug_unassign(&ctl->timeout_event);
    		for (ev = TAILQ_FIRST(&ctl->events); ev; ) {
    			struct event *next = TAILQ_NEXT(ev,
    			    ev_timeout_pos.ev_next_with_common_timeout);
    			if (!(ev->ev_flags & EVLIST_INTERNAL)) {
    				event_del(ev);
    				++n_deleted;
    			}
    			ev = next;
    		}
    		mm_free(ctl);
    	}
    	if (base->common_timeout_queues)
    		mm_free(base->common_timeout_queues);
    
    	for (;;) {
    		/* For finalizers we can register yet another finalizer out from
    		 * finalizer, and iff finalizer will be in active_later_queue we can
    		 * add finalizer to activequeues, and we will have events in
    		 * activequeues after this function returns, which is not what we want
    		 * (we even have an assertion for this).
    		 *
    		 * A simple case is bufferevent with underlying (i.e. filters).
    		 */
    		int i = event_base_free_queues_(base, run_finalizers);
    		event_debug(("%s: %d events freed", __func__, i));
    		if (!i) {
    			break;
    		}
    		n_deleted += i;
    	}
    
    	if (n_deleted)
    		event_debug(("%s: %d events were still set in base",
    			__func__, n_deleted));
    
    	while (LIST_FIRST(&base->once_events)) {
    		struct event_once *eonce = LIST_FIRST(&base->once_events);
    		LIST_REMOVE(eonce, next_once);
    		mm_free(eonce);
    	}
    
    	if (base->evsel != NULL && base->evsel->dealloc != NULL)
    		base->evsel->dealloc(base);
    
    	for (i = 0; i < base->nactivequeues; ++i)
    		EVUTIL_ASSERT(TAILQ_EMPTY(&base->activequeues[i]));
    
    	EVUTIL_ASSERT(min_heap_empty_(&base->timeheap));
    	min_heap_dtor_(&base->timeheap);
    
    	mm_free(base->activequeues);
    
    	evmap_io_clear_(&base->io);//③
    	evmap_signal_clear_(&base->sigmap);//④
    	event_changelist_freemem_(&base->changelist);//⑤
    
    	EVTHREAD_FREE_LOCK(base->th_base_lock, 0);
    	EVTHREAD_FREE_COND(base->current_event_cond);
    
    	/* If we're freeing current_base, there won't be a current_base. */
    	if (base == current_base)
    		current_base = NULL;
    	mm_free(base);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110

    • 停止 IOCP(Input/Output Completion Port)事件循环的内部函数。IOCP 是 Windows 平台上的一种事件通知机制,用于异步 I/O 操作的处理

    • 删除事件映射(event map)中的所有事件。事件映射是用于管理事件的数据结构,它将文件描述符(或套接字)映射到相应的事件处理器。

    • 清除 IO 事件映射中的特定事件处理器。IO 事件映射是用于管理文件描述符(或套接字)相关事件处理器的数据结构。

    • 清除信号事件映射中的特定事件处理器。信号事件映射是用于管理信号相关事件处理器的数据结构。

    • 释放事件改变列表(event changelist)相关的内存资源。

    二、 事件对象(event)

    2.1、event

    事件对象(event)用于表示一个特定的事件,可以是文件描述符事件、定时器事件或者信号事件。每个事件对象与一个特定的事件类型和回调函数相关联。

    创建事件对象的示例代码如下:

    #include 
    
    void event_callback(evutil_socket_t fd, short events, void *arg) {
        // 事件发生时的回调函数
    }
    
    struct event *ev = event_new(base, sockfd, EV_READ | EV_PERSIST, event_callback, arg);
    if (!ev) {
        // 创建失败,进行错误处理
        return -1;
    }
    
    // 使用事件对象进行事件处理...
    
    // 释放事件对象资源
    event_free(ev);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在创建事件对象后,可以将其添加到事件循环中,当事件满足触发条件时,注册的回调函数将被调用。可以使用 event_add 函数将事件对象添加到事件循环中,使用 event_del 函数将其从事件循环中移除。

    2.2、bufferevent

    bufferevent 是 libevent 中的一个高级抽象,用于管理基于缓冲区的 I/O 操作。它提供了对网络套接字和文件描述符的封装,并提供了方便的读写接口和事件回调机制。

    bufferevent 的主要功能包括:

    • 缓冲区管理:bufferevent 内部维护了输入缓冲区和输出缓冲区,可以方便地进行读取和写入操作,并支持自动调整缓冲区大小。

    • I/O 事件处理:bufferevent 可以注册读事件和写事件的回调函数,当有数据可读或可写时,会触发相应的事件回调。

    • 数据流处理:bufferevent 提供了高级数据流处理功能,可以自动处理粘包和拆包的问题,使得应用程序可以更方便地处理数据流。

    • 超时处理:bufferevent 支持设置读取超时和写入超时,可以在超时事件发生时触发相应的回调函数。

    • 错误处理:bufferevent 可以检测底层 I/O 操作的错误,并通过回调函数通知应用程序进行错误处理。

    通过使用 bufferevent,开发者可以更加方便地进行基于缓冲区的 I/O 操作,处理数据流,管理超时和错误处理等。

    libevent 提供了多种类型的 bufferevent 实现,包括基于套接字的 bufferevent_socket 和基于文件描述符的 bufferevent_fd 等

    三、 事件类型

    libevent-2.1.12-stable\include\event2\event.h

    /**
     * @name event flags
     *
     * Flags to pass to event_new(), event_assign(), event_pending(), and
     * anything else with an argument of the form "short events"
     */
    /**@{*/
    /** Indicates that a timeout has occurred.  It's not necessary to pass
     * this flag to event_for new()/event_assign() to get a timeout. */
    //超时事件
    #define EV_TIMEOUT	0x01
    /** Wait for a socket or FD to become readable */
    //读事件
    #define EV_READ		0x02
    /** Wait for a socket or FD to become writeable */
    //写事件
    #define EV_WRITE	0x04
    /** Wait for a POSIX signal to be raised*/
    //信号事件
    #define EV_SIGNAL	0x08
    /**
     * Persistent event: won't get removed automatically when activated.
     *
     * When a persistent event with a timeout becomes activated, its timeout
     * is reset to 0.
     */
    //指定事件是否持久性触发
    #define EV_PERSIST	0x10
    /** Select edge-triggered behavior, if supported by the backend. */
    //指定事件为边缘触发(Edge-Triggered)模式
    #define EV_ET		0x20
    /**
     * If this option is provided, then event_del() will not block in one thread
     * while waiting for the event callback to complete in another thread.
     *
     * To use this option safely, you may need to use event_finalize() or
     * event_free_finalize() in order to safely tear down an event in a
     * multithreaded application.  See those functions for more information.
     **/
    //
    #define EV_FINALIZE     0x40
    /**
     * Detects connection close events.  You can use this to detect when a
     * connection has been closed, without having to read all the pending data
     * from a connection.
     *
     * Not all backends support EV_CLOSED.  To detect or require it, use the
     * feature flag EV_FEATURE_EARLY_CLOSE.
     **/
    //关闭事件
    #define EV_CLOSED	0x80
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • EV_READ:指定事件是读事件,用于在套接字可读时触发事件。
    • EV_WRITE:指定事件是写事件,用于在套接字可写时触发事件。
    • EV_SIGNAL:指定事件是信号事件,用于在收到指定信号时触发事件。
    • EV_TIMEOUT:指定事件是超时事件,用于在指定时间间隔后触发事件。
    • EV_PERSIST:指定事件是持久性事件,用于重复触发事件,直到显式地禁用或删除。
    • EV_FINALIZE:指定事件的最终化,通常与event_base_loopexit()一起使用
    • EV_CLOSE:指定事件是连接关闭事件

    可以使用按位或运算符 | 将不同的事件类型进行组合,示例代码如下:

    Copy
    // 创建一个文件描述符读事件和定时器事件的组合
    struct event *ev = event_new(base, sockfd, EV_READ | EV_TIMEOUT, event_callback, arg);
    
    • 1
    • 2
    • 3

    四、 事件回调函数

    事件回调函数是在事件触发时被调用的函数,用于处理特定的事件。回调函数的原型通常为 void callback(evutil_socket_t fd, short events, void *arg),其中各个参数的含义如下:

    • fd:触发事件的文件描述符。
    • events:触发的事件类型,可以是 EV_READ、EV_WRITE、EV_TIMEOUT 等。
    • arg:传递给回调函数的参数。

    在回调函数中,可以编写相应的逻辑来处理事件,例如读取数据、写入数据、处理定时器等。

    五、基于libevent源码编译

    5.1、下载源码

    wget -c https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz
    
    • 1

    5.2、解压源码

    tar -zxvf libevent-2.1.12-stable.tar.gz
    
    • 1

    5.3、进入解压文件夹,编译安装libevent

    5.3.1、进入源码文件夹

    cd libevent-2.1.12-stable
    
    • 1

    5.3.2、查看configure的帮助文档

    ./configure --help
    
    • 1

    在这里插入图片描述
    在这里插入图片描述

    5.3.3、执行configure脚本

    ./configure --prefix=/usr/local/libevent
    
    • 1

    5.3.4、编译并安装

    make && sudo make install
    
    • 1

    可以看到默认动态库(shared)和静态库(static)都会编译生成,可以进入usr/local/libevent/lib查看
    在这里插入图片描述

    5.4、环境变量配置

    5.4.1、编写bashrc文件

    vim ~/.bashrc
    
    • 1

    5.4.2、配置环境变量

    在bashrc文件最后添加如下内容

    unset PKG_CONFIG_LIB
    export PKG_CONFIG_PATH=/usr/local/libevent/lib/pkgconfig:$PKG_CONFIG_PATH
    export LD_LIBRARY_PATH=/usr/local/libevent/lib:$LD_LIBRARY_PATH
    
    • 1
    • 2
    • 3

    5.4.3、环境变量生效

    source ~/.bashrc
    
    • 1

    5.5、g++编译

    g++编译的时候需要使用-levent指定对libevent的依赖

    g++ -o server server.cpp -levent
    
    • 1

    六、基于libevent动态库创建项目

    6.1、拷贝头文件和库文件

    拷贝5.3.4生成的头文件和库文件
    在这里插入图片描述

    6.2、配置CMakeLists.txt

    在这里插入图片描述

    cmake_minimum_required(VERSION 3.0)
    project(LIBEVENT)
    
    set(CMAKE_CXX_STANDARD 11)
    
    #设置头文件路径
    include_directories(${CMAKE_SOURCE_DIR}/include)
    
    find_library(LIBEVENT
      NAMES event
      PATHS ${CMAKE_SOURCE_DIR}/lib
    )
    
    find_library(LIBEVENT_CORE
      NAMES event_core
      PATHS ${CMAKE_SOURCE_DIR}/lib
    )
    
    find_library(LIBEVENT_EXTRA
      NAMES event_extra
      PATHS ${CMAKE_SOURCE_DIR}/lib
    )
    
    find_library(LIBEVENT_OPENSSL
      NAMES event_openssl
      PATHS ${CMAKE_SOURCE_DIR}/lib
    )
    
    find_library(LIBEVENT_PTHREADS
      NAMES event_pthreads
      PATHS ${CMAKE_SOURCE_DIR}/lib
    )
    
    add_executable(server_io server_ev_with_io.cpp)
    
    add_executable(server_nio server_ev_without_io.cpp)
    
    target_link_libraries(server_io ${LIBEVENT} ${LIBEVENT_CORE} ${LIBEVENT_EXTRA} ${LIBEVENT_OPENSSL} ${LIBEVENT_PTHREADS})
    
    target_link_libraries(server_nio ${LIBEVENT} ${LIBEVENT_CORE} ${LIBEVENT_EXTRA} ${LIBEVENT_OPENSSL} ${LIBEVENT_PTHREADS})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    七、基于libevent,原生处理IO

    7.1、event_new

    用于创建一个新的事件对象,并将其与指定的文件描述符和事件类型关联起来

    struct event *event_new(
        struct event_base *base,
        evutil_socket_t fd,
        short events,
        event_callback_fn callback,
        void *arg
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • base:指向 struct event_base 的指针,表示事件所属的事件基础结构。
    • fd:要关联的文件描述符(套接字、管道等)。
    • events:表示要监听的事件类型的位掩码,可以是以下常量之一或它们的组合:
      • EV_READ:读事件。
      • EV_WRITE:写事件。
      • EV_SIGNAL:信号事件。
      • EV_TIMEOUT:超时事件。
    • callback:指向事件回调函数的指针。当事件发生时,将调用该回调函数进行处理。
    • arg:一个指针,用于传递给回调函数的用户自定义数据。

    7.2、event_add

    用于将事件添加到事件基础结构中以进行监听

    int event_add(struct event *ev, const struct timeval *timeout);
    
    • 1
    • ev:指向 struct event 的指针,表示要添加的事件对象。
    • timeout:指向 struct timeval 的指针,表示事件的超时时间。如果传递 NULL,表示无超时限制。

    7.3、event_assign

    用于将事件对象与指定的事件回调函数和自定义数据关联起来

    void event_assign(
        struct event *ev,
        struct event_base *base,
        evutil_socket_t fd,
        short events,
        event_callback_fn callback,
        void *arg
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • ev:指向 struct event 的指针,表示要关联的事件对象。
    • base:指向 struct event_base 的指针,表示事件所属的事件基础结构。
    • fd:要关联的文件描述符(套接字、管道等)。
    • events:表示要监听的事件类型的位掩码,可以是以下常量之一或它们的组合:
      • EV_READ:读事件。
      • EV_WRITE:写事件。
      • EV_SIGNAL:信号事件。
      • EV_TIMEOUT:超时事件。
    • callback:指向事件回调函数的指针。当事件发生时,将调用该回调函数进行处理。
    • arg:一个指针,用于传递给回调函数的用户自定义数据。

    7.4、完整示例

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define PORT 8596
    #define MESSAGE_LEN 1024;
    
    //初始化socket
    int tcp_server_init(int port,int listen_num);
    //客户端连接事件的回调
    void ev_accept_cb(int fd,short events,void * arg);
    //客户端写事件的回调
    void ev_client_read_cb(int fd,short events,void *arg);
    
    int main(){
     
      int sock_fd=tcp_server_init(PORT,10);
      if(sock_fd==-1){
         perror("socket init error");
         return -1;
      }
    
      //创建event_base
      struct event_base *base=event_base_new();
      //创建accept状态的读事件
      struct event * ev_accept=event_new(base,sock_fd,EV_READ | EV_PERSIST,ev_accept_cb,base);
      event_add(ev_accept,NULL);
      event_base_dispatch(base);
    
      event_del(ev_accept);
      event_base_free(base);
       return 0;
    }
    
    int tcp_server_init(int port,int listen_num){
        int error_save;
        evutil_socket_t socket_fd= socket(AF_INET,SOCK_STREAM,0);
        if (socket_fd== -1)
        {
            return -1;
        }
        //允许多次绑定同一个地址。要用在socket和bind之间
        evutil_make_listen_socket_reuseable(socket_fd);
        struct sockaddr_in sin;
        sin.sin_family=AF_INET;
        sin.sin_addr.s_addr=INADDR_ANY;
        sin.sin_port=htons(port);
    
        if ((bind(socket_fd,(struct sockaddr *)&sin,sizeof(sin)))<0)
        {
            perror("bind socket error");
            return -1;
        }
    
        if((listen(socket_fd,listen_num))<0){
            perror("listen port error");
            return-1;
        }
        //将套接字设置为非阻塞状态
        evutil_make_socket_nonblocking(socket_fd);
        
        return socket_fd;
    }
    
    void ev_accept_cb(int fd,short events,void * arg){
        struct sockaddr_in accept_addr;
        socklen_t len=sizeof(accept_addr);
        //创建连接的客户端fd
        evutil_socket_t client_fd=accept(fd,(struct sockaddr*)&accept_addr,&len);
        evutil_make_socket_nonblocking(client_fd);
        struct event_base *base=(struct event_base*)arg;
        //创建客户端读事件
        struct event* ev_read=event_new(NULL,-1,0,NULL,NULL);
        //添加客户端读事件的回调
        event_assign(ev_read,base,client_fd,EV_READ | EV_PERSIST,ev_client_read_cb,(void *)ev_read);
        //添加客户端的读事件
        event_add(ev_read,NULL);
    }
    
    void ev_client_read_cb(int fd,short events,void *arg){
        char msg[4096];
        struct event* ev=(struct event*)arg;
        int len=read(fd,msg,sizeof(msg)-1);
        if(len <=0){
            event_free(ev);
            close(fd);
            return;
        }
        msg[len]='\0';
        printf("receive client msg:%s",msg);
    
        char replymsg[4096]="receive msg:";
        strcat(replymsg+strlen(replymsg),msg);
        write(fd,replymsg,strlen(replymsg));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98

    八、基于libevent封装IO

    8.1、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
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • base:指向 struct event_base 的指针,表示监听器所属的事件基础结构。
    • cb:指向监听器回调函数的指针,当有新连接到达时会调用该回调函数。
    • ptr:一个指针,用于传递给回调函数的用户自定义数据。
    • flags:监听器的标志位,用于指定监听器的行为。例如,可以使用 LEV_OPT_REUSEABLE 标志启用地址重用,使用 LEV_OPT_CLOSE_ON_FREE 标志在释放监听器时关闭套接字。
    • backlog:连接请求的等待队列长度。
    • sa:指向 struct sockaddr 的指针,表示要绑定的地址信息。
    • socklen:地址结构体的长度

    8.2、bufferevent_socket_new

    用于创建基于套接字的缓冲事件的函数

    struct bufferevent *bufferevent_socket_new(
        struct event_base *base,
        evutil_socket_t fd,
        enum bufferevent_options options
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5

    该函数用于创建一个新的缓冲事件,并与给定的套接字关联起来。下面是参数的说明:

    • base:指向 struct event_base 的指针,表示缓冲事件所属的事件基础结构。
    • fd:要关联的套接字描述符。
    • options:缓冲事件的选项,可以是以下常量之一:
      • BEV_OPT_CLOSE_ON_FREE:在释放缓冲事件时关闭关联的套接字。
      • BEV_OPT_THREADSAFE:启用缓冲事件的线程安全模式。

    8.3、bufferevent_setcb

    用于设置缓冲事件的回调函数,以便在不同的事件发生时进行处理

    void bufferevent_setcb(
        struct bufferevent *bev,
        bufferevent_data_cb readcb,
        bufferevent_data_cb writecb,
        bufferevent_event_cb eventcb,
        void *cbarg
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • bev:指向 struct bufferevent 的指针,表示要设置回调函数的缓冲事件对象。
    • readcb:指向读事件回调函数的指针。当有数据可读时,将调用该回调函数。
    • writecb:指向写事件回调函数的指针。当可写入数据时,将调用该回调函数。
    • eventcb:指向事件回调函数的指针。当发生异常事件(如连接关闭、错误等)时,将调用该回调函数。
    • cbarg:一个指针,用于传递给回调函数的用户自定义数据。

    8.4、bufferevent_enable

    用于启用或禁用缓冲事件的指定事件类型

    void bufferevent_enable(struct bufferevent *bev, short event);
    
    • 1
    • bev:指向 struct bufferevent 的指针,表示要启用或禁用的缓冲事件对象。
    • event:一个表示事件类型的位掩码,可以是以下常量之一或它们的组合:
      • EV_READ:启用读事件。
      • EV_WRITE:启用写事件。

    8.5、完整示例

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    //客户端连接回调
    void listener_cb(struct evconnlistener *listener,evutil_socket_t fd,struct sockaddr* addr,int socklen,void *arg);
    //客户端读事件回调
    void socket_read_cb(struct bufferevent * bev,void *arg);
    //客户端事件回调
    void socket_event_cb(struct bufferevent *bev, short event, void *arg);
    int main(){
        //创建event_base
        struct event_base * base=event_base_new();
        //声明地址结构体,设置服务端地址参数
        struct sockaddr_in sin;
        memset(&sin,0,sizeof(struct sockaddr_in));
        sin.sin_family=AF_INET;
        sin.sin_port=htons(8596);
    
        struct evconnlistener* evlistener= evconnlistener_new_bind(base,listener_cb,base,LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE,10,(struct sockaddr*)&sin,sizeof(struct sockaddr_in));
        event_base_dispatch(base);
        evconnlistener_free(evlistener);
        event_base_free(base);
         return 0;
    }
    
    void listener_cb(struct evconnlistener *listener,evutil_socket_t fd,struct sockaddr* addr,int socklen,void *arg){
        char ip[32] ={0};
        evutil_inet_ntop(AF_INET,addr,ip,sizeof(ip)-1);
        printf("accept client ip:%s\n",ip);
        struct event_base *base = evconnlistener_get_base(listener);
        struct bufferevent *be=bufferevent_socket_new(base,fd,BEV_OPT_CLOSE_ON_FREE);
        bufferevent_setcb(be,socket_read_cb,NULL,socket_event_cb,arg);
        bufferevent_enable(be,EV_READ | EV_PERSIST);
    
         // 添加以下代码来设置读回调函数
        //bufferevent_setwatermark(be, EV_READ, 0, 4096); // 设置读取水位标记
        //bufferevent_enable(be, EV_READ); // 启用读事件
    }
    
    void socket_read_cb(struct bufferevent * bev,void *arg){
       struct evbuffer *evbuf=bufferevent_get_input(bev);
       char *msg= evbuffer_readln(evbuf,NULL,EVBUFFER_EOL_LF);
       if(!msg) return;
       printf("server read data:%s\n",msg);
    
       char reply[4096]={0};
       sprintf(reply,"receive msg:%s\n",msg);
       free(msg);
       bufferevent_write(bev,reply,strlen(reply));
    }
    
    void socket_event_cb(struct bufferevent *bev, short event, void *arg){
         if (event & BEV_EVENT_EOF)
            printf("connection closed\n");
        else if (event & BEV_EVENT_ERROR)
            printf("some other error\n");
    
        //这将自动close套接字和free读写缓冲区
        bufferevent_free(bev);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
  • 相关阅读:
    Swift 网络请求 Moya+RxSwift
    11 月亚马逊云科技培训与认证课程,精彩不容错过!
    python第二节PyCharm创建项目和关联Anaconda以及设置PyCharm以及PyCharm常用快捷键
    PDCA循环
    【AIGC调研系列】copilot在自动化测试脚本中的实际应用效果
    华为数通安全产品介绍
    在“百模大战”重生,搜索引擎又行了?
    流式数据湖Hudi核心概念四:文件布局
    opnet物联网仿真2.5 陈敏 包交换网络全解----修正版
    华为OD七日集训第7期 - 按算法分类,由易到难,循序渐进,玩转OD
  • 原文地址:https://blog.csdn.net/u011557841/article/details/133312767