libevent是一个事件驱动型的跨平台的网络通信基础库。libevent支持包括:/dev/poll、kqueue(2)、select(2)、poll(2)、epoll(4)、evports等在内的多种异步通信模型。同时,支持在Linux、 *BSD、Mac OS X、Solaris、Windows等多种类型的操作系统上编译使用。libevent遵循BSD开源协议。
libevent-release-2.1.7-rc
xxxx$ tree
.
├── CMakeLists.txt
├── CONTRIBUTING.md
├── ChangeLog
├── ChangeLog-1.4
├── ChangeLog-2.0
├── Doxyfile
├── LICENSE
├── Makefile.am
├── Makefile.nmake
├── README.md
├── Vagrantfile
├── WIN32-Code
│ ├── getopt.c
│ ├── getopt.h
│ ├── getopt_long.c
│ ├── nmake
│ │ ├── evconfig-private.h
│ │ └── event2
│ │ └── event-config.h
│ └── tree.h
├── appveyor.yml
├── arc4random.c
├── autogen.sh
├── buffer.c
├── buffer_iocp.c
├── bufferevent-internal.h
├── bufferevent.c
├── bufferevent_async.c
├── bufferevent_filter.c
├── bufferevent_openssl.c
├── bufferevent_pair.c
├── bufferevent_ratelim.c
├── bufferevent_sock.c
├── changelist-internal.h
├── checkpatch.sh
├── cmake
│ ├── AddCompilerFlags.cmake
│ ├── COPYING-CMAKE-SCRIPTS
│ ├── CheckFileOffsetBits.c
│ ├── CheckFileOffsetBits.cmake
│ ├── CheckFunctionExistsEx.c
│ ├── CheckFunctionExistsEx.cmake
│ ├── CheckFunctionKeywords.cmake
│ ├── CheckPrototypeDefinition.c.in
│ ├── CheckPrototypeDefinition.cmake
│ ├── CheckWaitpidSupportWNOWAIT.cmake
│ ├── CheckWorkingKqueue.cmake
│ ├── CodeCoverage.cmake
│ ├── Copyright.txt
│ ├── FindGit.cmake
│ ├── LibeventConfig.cmake.in
│ ├── LibeventConfigBuildTree.cmake.in
│ ├── LibeventConfigVersion.cmake.in
│ └── VersionViaGit.cmake
├── compat
│ └── sys
│ └── queue.h
├── configure.ac
├── defer-internal.h
├── devpoll.c
├── epoll.c
├── epoll_sub.c
├── epolltable-internal.h
├── evbuffer-internal.h
├── evconfig-private.h.cmake
├── evconfig-private.h.in
├── evdns.3
├── evdns.c
├── event-config.h.cmake
├── event-internal.h
├── event.3
├── event.c
├── event_iocp.c
├── event_rpcgen.py
├── event_tagging.c
├── evmap-internal.h
├── evmap.c
├── evport.c
├── evrpc-internal.h
├── evrpc.c
├── evsignal-internal.h
├── evthread-internal.h
├── evthread.c
├── evthread_pthread.c
├── evthread_win32.c
├── evutil.c
├── evutil_rand.c
├── evutil_time.c
├── ht-internal.h
├── http-internal.h
├── http.c
├── include
│ ├── evdns.h
│ ├── event.h
│ ├── event2
│ │ ├── buffer.h
│ │ ├── buffer_compat.h
│ │ ├── bufferevent.h
│ │ ├── bufferevent_compat.h
│ │ ├── bufferevent_ssl.h
│ │ ├── bufferevent_struct.h
│ │ ├── dns.h
│ │ ├── dns_compat.h
│ │ ├── dns_struct.h
│ │ ├── event.h
│ │ ├── event_compat.h
│ │ ├── event_struct.h
│ │ ├── http.h
│ │ ├── http_compat.h
│ │ ├── http_struct.h
│ │ ├── keyvalq_struct.h
│ │ ├── listener.h
│ │ ├── rpc.h
│ │ ├── rpc_compat.h
│ │ ├── rpc_struct.h
│ │ ├── tag.h
│ │ ├── tag_compat.h
│ │ ├── thread.h
│ │ ├── util.h
│ │ └── visibility.h
│ ├── evhttp.h
│ ├── evrpc.h
│ ├── evutil.h
│ └── include.am
├── iocp-internal.h
├── ipv6-internal.h
├── kqueue-internal.h
├── kqueue.c
├── libevent.pc.in
├── libevent_core.pc.in
├── libevent_extra.pc.in
├── libevent_openssl.pc.in
├── libevent_pthreads.pc.in
├── listener.c
├── log-internal.h
├── log.c
├── m4
│ ├── ac_backport_259_ssizet.m4
│ ├── acx_pthread.m4
│ ├── libevent_openssl.m4
│ └── ntp_pkg_config.m4
├── make-event-config.sed
├── make_epoll_table.py
├── minheap-internal.h
├── mm-internal.h
├── openssl-compat.h
├── poll.c
├── ratelim-internal.h
├── sample
│ ├── dns-example.c
│ ├── event-read-fifo.c
│ ├── hello-world.c
│ ├── hostcheck.c
│ ├── hostcheck.h
│ ├── http-connect.c
│ ├── http-server.c
│ ├── https-client.c
│ ├── include.am
│ ├── le-proxy.c
│ ├── openssl_hostname_validation.c
│ ├── openssl_hostname_validation.h
│ ├── signal-test.c
│ └── time-test.c
├── select.c
├── signal.c
├── strlcpy-internal.h
├── strlcpy.c
├── test
│ ├── Makefile.nmake
│ ├── bench.c
│ ├── bench_cascade.c
│ ├── bench_http.c
│ ├── bench_httpclient.c
│ ├── check-dumpevents.py
│ ├── include.am
│ ├── print-winsock-errors.c
│ ├── regress.c
│ ├── regress.h
│ ├── regress.rpc
│ ├── regress_buffer.c
│ ├── regress_bufferevent.c
│ ├── regress_dns.c
│ ├── regress_et.c
│ ├── regress_finalize.c
│ ├── regress_http.c
│ ├── regress_iocp.c
│ ├── regress_listener.c
│ ├── regress_main.c
│ ├── regress_minheap.c
│ ├── regress_rpc.c
│ ├── regress_ssl.c
│ ├── regress_testutils.c
│ ├── regress_testutils.h
│ ├── regress_thread.c
│ ├── regress_thread.h
│ ├── regress_util.c
│ ├── regress_zlib.c
│ ├── rpcgen_wrapper.sh
│ ├── test-changelist.c
│ ├── test-closed.c
│ ├── test-dumpevents.c
│ ├── test-eof.c
│ ├── test-fdleak.c
│ ├── test-init.c
│ ├── test-ratelim.c
│ ├── test-ratelim.sh
│ ├── test-time.c
│ ├── test-weof.c
│ ├── test.sh
│ ├── tinytest.c
│ ├── tinytest.h
│ ├── tinytest_demo.c
│ ├── tinytest_local.h
│ └── tinytest_macros.h
├── time-internal.h
├── util-internal.h
├── whatsnew-2.0.txt
├── whatsnew-2.1.txt
└── win32select.c
11 directories, 208 files
xxxx$
从源码目录的结构和内容可以得出如下结论:
libevent支持对用户注册到库中的event进行集中监听,并在event发生时或超时后调用event指定的callback函数,触发event注册模块进行相应event响应操作。
event2/event.h: libevent最主要的头文件。里面包含了主要接口的定义(如:event_base_new、event_base_dispatch、event_new、event_add、event_del等)和主要结构体的申明(如:struct event_base、struct event等);
event2/thread.h: 一些供多线程程序使用的函数;
event2/buffer.h & event2/bufferevent.h: 用于网络读写的缓冲区管理接口;
event2/util.h: 可移植的非阻塞网络代码的实用函数;
event2/dns.h: 基于libevent的异步DNS解析器。
event2/http.h: 基于libevent的一个简单的http server接口;
event2/rpc.h: 基于libevent的一个RPC框架接口(包括server和client);
struct event_base {
/** Function pointers and other data to describe this event_base's
* backend. */
const struct eventop *evsel;
/** Pointer to backend-specific data. */
void *evbase;
/** List of changes to tell backend about at next dispatch. Only used
* by the O(1) backends. */
struct event_changelist changelist;
/** Function pointers used to describe the backend that this event_base
* uses for signals */
const struct eventop *evsigsel;
/** Data to implement the common signal handelr code. */
struct evsig_info sig;
/** Number of virtual events */
int virtual_event_count;
/** Maximum number of virtual events active */
int virtual_event_count_max;
/** Number of total events added to this event_base */
int event_count;
/** Maximum number of total events added to this event_base */
int event_count_max;
/** Number of total events active in this event_base */
int event_count_active;
/** Maximum number of total events active in this event_base */
int event_count_active_max;
/** Set if we should terminate the loop once we're done processing
* events. */
int event_gotterm;
/** Set if we should terminate the loop immediately */
int event_break;
/** Set if we should start a new instance of the loop immediately. */
int event_continue;
/** The currently running priority of events */
int event_running_priority;
/** Set if we're running the event_base_loop function, to prevent
* reentrant invocation. */
int running_loop;
/** Set to the number of deferred_cbs we've made 'active' in the
* loop. This is a hack to prevent starvation; it would be smarter
* to just use event_config_set_max_dispatch_interval's max_callbacks
* feature */
int n_deferreds_queued;
/* Active event management. */
/** An array of nactivequeues queues for active event_callbacks (ones
* that have triggered, and whose callbacks need to be called). Low
* priority numbers are more important, and stall higher ones.
*/
struct evcallback_list *activequeues;
/** The length of the activequeues array */
int nactivequeues;
/** A list of event_callbacks that should become active the next time
* we process events, but not this time. */
struct evcallback_list active_later_queue;
/* common timeout logic */
/** An array of common_timeout_list* for all of the common timeout
* values we know. */
struct common_timeout_list **common_timeout_queues;
/** The number of entries used in common_timeout_queues */
int n_common_timeouts;
/** The total size of common_timeout_queues. */
int n_common_timeouts_allocated;
/** Mapping from file descriptors to enabled (added) events */
struct event_io_map io;
/** Mapping from signal numbers to enabled (added) events. */
struct event_signal_map sigmap;
/** Priority queue of events with timeouts. */
struct min_heap timeheap;
/** Stored timeval: used to avoid calling gettimeofday/clock_gettime
* too often. */
struct timeval tv_cache;
struct evutil_monotonic_timer monotonic_timer;
/** Difference between internal time (maybe from clock_gettime) and
* gettimeofday. */
struct timeval tv_clock_diff;
/** Second in which we last updated tv_clock_diff, in monotonic time. */
time_t last_updated_clock_diff;
#ifndef EVENT__DISABLE_THREAD_SUPPORT
/* threading support */
/** The thread currently running the event_loop for this base */
unsigned long th_owner_id;
/** A lock to prevent conflicting accesses to this event_base */
void *th_base_lock;
/** A condition that gets signalled when we're done processing an
* event with waiters on it. */
void *current_event_cond;
/** Number of threads blocking on current_event_cond. */
int current_event_waiters;
#endif
/** The event whose callback is executing right now */
struct event_callback *current_event;
#ifdef _WIN32
/** IOCP support structure, if IOCP is enabled. */
struct event_iocp_port *iocp;
#endif
/** Flags that this base was configured with */
enum event_base_config_flag flags;
struct timeval max_dispatch_time;
int max_dispatch_callbacks;
int limit_callbacks_after_prio;
/* Notify main thread to wake up break, etc. */
/** True if the base already has a pending notify, and we don't need
* to add any more. */
int is_notify_pending;
/** A socketpair used by some th_notify functions to wake up the main
* thread. */
evutil_socket_t th_notify_fd[2];
/** An event used by some th_notify functions to wake up the main
* thread. */
struct event th_notify;
/** A function used to wake up the main thread from another thread. */
int (*th_notify_fn)(struct event_base *base);
/** Saved seed for weak random number generator. Some backends use
* this to produce fairness among sockets. Protected by th_base_lock. */
struct evutil_weakrand_state weakrand_seed;
/** List of event_onces that have not yet fired. */
LIST_HEAD(once_event_list, event_once) once_events;
};
/** Structure to define the backend of a given event_base. */
struct eventop {
/** The name of this backend. */
const char *name;
/** Function to set up an event_base to use this backend. It should
* create a new structure holding whatever information is needed to
* run the backend, and return it. The returned pointer will get
* stored by event_init into the event_base.evbase field. On failure,
* this function should return NULL. */
void *(*init)(struct event_base *);
/** Enable reading/writing on a given fd or signal. 'events' will be
* the events that we're trying to enable: one or more of EV_READ,
* EV_WRITE, EV_SIGNAL, and EV_ET. 'old' will be those events that
* were enabled on this fd previously. 'fdinfo' will be a structure
* associated with the fd by the evmap; its size is defined by the
* fdinfo field below. It will be set to 0 the first time the fd is
* added. The function should return 0 on success and -1 on error.
*/
int (*add)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
/** As "add", except 'events' contains the events we mean to disable. */
int (*del)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
/** Function to implement the core of an event loop. It must see which
added events are ready, and cause event_active to be called for each
active event (usually via event_io_active or such). It should
return 0 on success and -1 on error.
*/
int (*dispatch)(struct event_base *, struct timeval *);
/** Function to clean up and free our data from the event_base. */
void (*dealloc)(struct event_base *);
/** Flag: set if we need to reinitialize the event base after we fork.
*/
int need_reinit;
/** Bit-array of supported event_method_features that this backend can
* provide. */
enum event_method_feature features;
/** Length of the extra information we should record for each fd that
has one or more active events. This information is recorded
as part of the evmap entry for each fd, and passed as an argument
to the add and del functions above.
*/
size_t fdinfo_len;
};
struct event {
struct event_callback ev_evcallback;
/* for managing timeouts */
union {
TAILQ_ENTRY(event) ev_next_with_common_timeout;
int min_heap_idx;
} ev_timeout_pos;
evutil_socket_t ev_fd;
struct event_base *ev_base;
union {
/* used for io events */
struct {
LIST_ENTRY (event) ev_io_next;
struct timeval ev_timeout;
} ev_io;
/* used by signal events */
struct {
LIST_ENTRY (event) ev_signal_next;
short ev_ncalls;
/* Allows deletes in callback */
short *ev_pncalls;
} ev_signal;
} ev_;
short ev_events;
short ev_res; /* result passed to event callback */
struct timeval ev_timeout;
};
/** Internal structure: describes the configuration we want for an event_base
* that we're about to allocate. */
struct event_config {
TAILQ_HEAD(event_configq, event_config_entry) entries;
int n_cpus_hint;
struct timeval max_dispatch_interval;
int max_dispatch_callbacks;
int limit_callbacks_after_prio;
enum event_method_feature require_features;
enum event_base_config_flag flags;
};
函数原型: struct event_config *event_config_new(void);
解释: 创建一个event_config对象,这个对象可以用来在创建event_base对象时指定对象的属性。
函数原型: void event_config_free(struct event_config *cfg);
解释: 释放一个event_config对象。
函数原型: int event_config_avoid_method(struct event_config *cfg, const char *method);
解释: 向event_config对象中新增排除使用的方法。如:epoll、poll、select等。
函数原型: int event_config_require_features(struct event_config *cfg, int feature);
解释: 设置event_config对象中,应用程序所需的特性。
函数原型: int event_config_set_flag(struct event_config *cfg, int flag);
解释: 设置event_config对象中的flag的值,这些值用于event_base对象创建时初始化event_base对象。
函数原型: int event_config_set_num_cpus_hint(struct event_config *cfg, int cpus);
解释: 记录系统中cpu数量的提示。它用于调优线程池等以获得最佳性能。在Libevent 2.0中,它只在Windows中使用,并且只在IOCP被使用时使用。
函数原型: int event_config_set_max_dispatch_interval(struct event_config *cfg, const struct timeval *max_interval, int max_callbacks, int min_priority);
解释: 设置event_config的最大时间间隔、一次最多调用的callback函数次数、最小优先级的参数。
函数原型: struct event_base *event_base_new(void);
解释: 创建一个event_base对象。
函数原型: int event_base_dispatch(struct event_base *);
解释: 开始事件的循环调度。本函数将循环执行,直到event_base中不在有挂起的或循环的事件时,或者直到系统中调用了event_base_loopbreak()或event_base_loopexist()函数时才推出循环。
函数原型: struct event_base *event_base_new_with_config(const struct event_config *);
解释: 在event_config对象的指导下创建一个event_base对象。
函数原型: void event_base_free(struct event_base *);
解释: 销毁并释放一个event_base对象。
函数原型: void event_base_free_nofinalize(struct event_base *);
解释: 释放一个event_base对象,但是不强制终止执行中的event。
函数原型: int event_base_set(struct event_base *, struct event *);
解释: 将event 对象和event_base对象关联起来。
函数原型: int event_base_loop(struct event_base *, int);
解释: 循环等待event_base中挂载的event对象的event事件被触发,并调用对应的callback函数。
函数原型: int event_base_loopexit(struct event_base *, const struct timeval *);
解释: 在指定时间退出event_base
函数原型: int event_base_loopbreak(struct event_base *);
解释: 立即终止正在运行的event_base_loop。
函数原型: int event_base_loopcontinue(struct event_base *);
解释: 通知event_base_loop函数立即重新扫描加载新的event事件。
函数原型: struct event *event_new(struct event_base *, evutil_socket_t, short, event_callback_fn, void *);
解释: 创建一个event对象,对event对象进行初始化。并将event对象和event_base对象关联起来。
函数原型: event_assign(struct event *, struct event_base *, evutil_socket_t, short, event_callback_fn, void *);
解释: 对event对象进行初始化,并将event对象和event_base对象关联起来。
函数原型: void event_free(struct event *);
解释: 释放一个通过event_new创建的对象。
函数原型: int event_add(struct event *ev, const struct timeval *timeout);
解释: 将event对象添加到events集中,这时该event对象将会在event_base_loop中被集中监听。
函数原型: int event_del(struct event *);
解释: 从监听的events集合中删除一个event对象。如果event对象的事件已经执行或从未添加,则调用将不起作用。
函数原型: int event_del_noblock(struct event *ev);
解释: 从监听的events集合中删除一个event对象。但是如果该event对象的callback函数正在其它线程中执行,则该callback的执行将不会被中断,即使该event对象在创建时没有设置EV_FINALIZE 标记。
函数原型: int event_del_block(struct event *ev);
解释: 从监听的events集合中删除一个event对象。如果该event对象的callback函数正在其它线程中执行也将被中断,即使该event对象在创建时设置了EV_FINALIZE标记也会别中断。
函数原型: void event_active(struct event *ev, int res, short ncalls);
解释: 激活指定的event对象的事件。
函数原型: int event_pending(const struct event *ev, short events, struct timeval *tv);
解释: 检查指定event对象的事件是否已经挂起或被调度。
函数原型: int event_finalize(unsigned, struct event *, event_finalize_callback_fn);
解释: 本函数用于在多线程应用中安全的销毁一个event对象。
函数原型: int event_free_finalize(unsigned, struct event *, event_finalize_callback_fn);
解释: 本函数在event_finalize函数的基础上还要增加对event对象资源的释放。
函数原型: int event_base_once(struct event_base *, evutil_socket_t, short, event_callback_fn, void *, const struct timeval *);
解释: 创建一个只会被触发调用一次对event对象。
函数原型: #define evtimer_assign(ev, b, cb, arg) event_assign((ev), (b), -1, 0, (cb), (arg))
解释:
函数原型: #define evtimer_new(b, cb, arg) event_new((b), -1, 0, (cb), (arg))
解释:
函数原型: #define evtimer_add(ev, tv) event_add((ev), (tv))
解释:
函数原型: #define evtimer_del(ev) event_del(ev)
解释:
函数原型: #define evtimer_pending(ev, tv) event_pending((ev), EV_TIMEOUT, (tv))
解释:
函数原型: #define evtimer_initialized(ev) event_initialized(ev)
解释:
函数原型: #define evsignal_add(ev, tv) event_add((ev), (tv))
解释:
函数原型: #define evsignal_assign(ev, b, x, cb, arg) event_assign((ev), (b), (x), EV_SIGNAL|EV_PERSIST, cb, (arg))
解释:
函数原型: #define evsignal_new(b, x, cb, arg) event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))
解释:
函数原型: #define evsignal_del(ev) event_del(ev)
解释:
函数原型: #define evsignal_pending(ev, tv) event_pending((ev), EV_SIGNAL, (tv))
解释:
函数原型: #define evsignal_initialized(ev) event_initialized(ev)
解释:
依次在libevent源码所在目录执行"./autogen.sh"、"./configure"、“make”。
依次在libevent源码所在目录执行"cmake ."、“make”。
对应测试代码的源文件:sample/hello-world.c。
测试代码内容:
/*
This example program provides a trivial server program that listens for TCP
connections on port 9995. When they arrive, it writes a short message to
each client connection, and closes each connection once it is flushed.
Where possible, it exits cleanly in response to a SIGINT (ctrl-c).
*/
#include
#include
#include
#include
#ifndef _WIN32
#include
# ifdef _XOPEN_SOURCE_EXTENDED
# include
# endif
#include
#endif
#include
#include
#include
#include
#include
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);
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;
}
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));
}
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);
}
通过./sample/hello-world命令启动hello-world命令后,在另一个终端中使用telnet命令测试该hello-world监听的9995端口。结果如下:
telnet结果
(base) xxxx:libevent-release-2.1.7-rc zdns$ telnet 127.0.0.1 9995
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello, World!
Connection closed by foreign host.
(base) xxxx:libevent-release-2.1.7-rc zdns$
hello-world进程输出结果
(base) xxxx:libevent-release-2.1.7-rc zdns$ ./sample/hello-world
flushed answer
在hello-world进程启动的终端中按ctrl+C终止进程。终端输出结果如下:
(base) xxxx:libevent-release-2.1.7-rc zdns$ ./sample/hello-world
flushed answer
^CCaught an interrupt signal; exiting cleanly in two seconds.
done
(base) xxxx:libevent-release-2.1.7-rc zdns$