Xây dựng vòng lặp sự kiện hết thời gian
Điểm khởi đầu thường thấy trong thư viện là cơ chế đăng ký một sự kiện đơn lẻ để kích hoạt sau một khoảng thời gian xác định. Dưới đây là phiên bản đã được tối ưu hóa về cấu trúc và đặt tên biến nhằm làm rõ luồng điều khiển:
int run_timer_example(void) {
struct event timer_evt;
struct timeval delay_interval;
struct event_base *loop_mgr;
int persist_mode = 0;
#ifdef _WIN32
WSADATA wsa_info;
WSAStartup(MAKEWORD(2, 2), &wsa_info);
#endif
/* Kiểm tra cờ chạy liên tục từ tham số dòng lệnh */
extern int arg_is_persistent;
if (arg_is_persistent) {
persist_mode = 1;
}
/* Khởi tạo nền tảng quản lý sự kiện duy nhất */
loop_mgr = event_base_new();
if (!loop_mgr) return EXIT_FAILURE;
/* Gán cấu hình cho sự kiện: fd=-1 nghĩa là chỉ dựa trên bộ đếm thời gian */
event_assign(&timer_evt, loop_mgr, -1, persist_mode ? EV_PERSIST : 0, on_timeout_trigger, &timer_evt);
/* Thiết lập độ trễ 2 giây */
memset(&delay_interval, 0, sizeof(delay_interval));
delay_interval.tv_sec = 2;
event_add(&timer_evt, &delay_interval);
fflush(stdout);
event_base_dispatch(loop_mgr);
event_base_free(loop_mgr);
return EXIT_SUCCESS;
}
Luồng thực thi chính bao gồm việc cấp phát bộ nhớ cho trình quản lý vòng lặp, khai báo đối tượng sự kiện kèm hàm xử lý và ngữ cảnh truyền vào, sau đó thiết lập ngưỡng thời gian trước khi bật chế độ trực tiếp (dispatch). Hàm xử lý có chữ ký chuẩn của libevent:
static void on_timeout_trigger(evutil_socket_t fd, short evt_flags, void *ctx) {
struct event *evt_ref = ctx;
struct timeval current_tick, span_diff;
/* Tính toán thời gian trôi qua kể từ lần kích hoạt trước */
gettick_current(¤t_tick);
timersub(¤t_tick, &last_recorded_time, &span_diff);
double duration_sec = span_diff.tv_sec + (span_diff.tv_usec / 1e6);
printf("Hàm kích hoạt tại %ld: %.3f giây đã trôi qua\n",
(long)current_tick.tv_sec, duration_sec);
last_recorded_time = current_tick;
/* Tự động đăng ký lại nếu không ở chế độ liên tục */
if (!(evt_flags & EV_PERSIST)) {
struct timeval next_delay = {.tv_sec = 2};
event_add(evt_ref, &next_delay);
}
}
Lưu ý rằng tham số ctx chứa con trỏ ngược đến chính đối tượng sự kiện, cho phép callback tự điều chỉnh trạng thái mà không cần dùng biến toàn cục. Khi cờ EV_PERSIST được bật, hệ thống sẽ giữ sự kiện trong hàng đợi vòng lặp mà không cần gọi event_add thủ công mỗi lần.
Kết nối tín hiệu hệ thống (Signals)
Libevent hỗ trợ chặn các tín hiệu UNIX như SIGINT hoặc SIGTERM và chuyển hướng chúng vào cùng vòng lặp sự kiện. Cách tiếp cận này tránh tình trạng khối code bị gián đoạn đột ngột và cho phép dọn dẹp tài nguyên an toàn.
int run_signal_handler(void) {
struct event_base *loop_mgr = event_base_new();
struct event *sig_ctrl = NULL;
int exit_code = EXIT_SUCCESS;
if (!loop_mgr) return EXIT_FAILURE;
/* Tạo sự kiện gắn liền với SIGINT */
sig_ctrl = evsignal_new(loop_mgr, SIGINT, on_system_signal, loop_mgr);
if (!sig_ctrl) {
exit_code = EXIT_FAILURE;
goto cleanup;
}
/* Bật sự kiện ngay lập tức */
if (event_add(sig_ctrl, NULL) < 0) {
exit_code = EXIT_FAILURE;
goto cleanup;
}
event_base_dispatch(loop_mgr);
cleanup:
if (sig_ctrl) event_free(sig_ctrl);
if (loop_mgr) event_base_free(loop_mgr);
return exit_code;
}
Hàm xử lý tín hiệu nhận diện mã sự kiện thông qua API nội bộ và có thể hủy bỏ chính nó sau một ngưỡng kích hoạt nhất định để ngăn chặn vòng lặp vô hạn:
static void on_system_signal(evutil_socket_t sig_fd, short flags, void *user_arg) {
struct event_base *base_ctx = user_arg;
struct event *self_evt = evsignal_self(base_ctx);
printf("Tiếp nhận tín hiệu #%d\n", event_get_signal(self_evt));
static int hit_count = 0;
if (++hit_count >= 2) {
event_del(self_evt);
event_base_loopexit(base_ctx, NULL);
}
}
Việc sử dụng evsignal_new thay vì event_new giúp libevent tự động cài đặt cờ EV_SIGNAL và bảo vệ môi trường sigaction, đảm bảo tín hiệu luôn được chuyển giao đúng ngữ cảnh vòng lặp.
Triển khai máy chủ TCP với cơ chế Buffering
Một ứng dụng mạng thực tế thường yêu cầu quản lý đồng thời nhiều kết nối, phân tách việc đọc/ghi và tự động co giãn bộ đệm. Thư viện cung cấp lớp trừu tượng bufferevent giải quyết vấn đề này mà không cần viết code chọn socket (select/epoll) thủ công.
int run_tcp_server(void) {
struct event_base *loop_mgr = event_base_new();
struct sockaddr_in addr_cfg = {0};
struct evconnlistener *listen_sock = NULL;
struct event *ctrl_sig = NULL;
addr_cfg.sin_family = AF_INET;
addr_cfg.sin_port = htons(9995);
addr_cfg.sin_addr.s_addr = INADDR_ANY;
/* Bind socket, auto-reuse address và đóng khi giải phóng listener */
listen_sock = evconnlistener_new_bind(loop_mgr, on_new_conn, loop_mgr,
LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1,
(struct sockaddr*)&addr_cfg, sizeof(addr_cfg));
if (!listen_sock) return EXIT_FAILURE;
ctrl_sig = evsignal_new(loop_mgr, SIGINT, on_shutdown_req, loop_mgr);
if (!ctrl_sig || event_add(ctrl_sig, NULL) < 0) {
evconnlistener_free(listen_sock);
return EXIT_FAILURE;
}
event_base_dispatch(loop_mgr);
evconnlistener_free(listen_sock);
event_free(ctrl_sig);
event_base_free(loop_mgr);
return EXIT_SUCCESS;
}
Khi có kết nối mới tới, trình lắng nghe sẽ gọi on_new_conn. Tại đây, bufferevent_socket_new bao bọc descriptor mới thành đối tượng đọc/ghi độc lập. Cờ BEV_OPT_CLOSE_ON_FREE đảm bảo Descriptor sẽ được close() tự động khi bufferevent_free được gọi.
static void on_new_conn(struct evconnlistener *srv, evutil_socket_t client_fd,
struct sockaddr *peer, int len, void *app_ctx) {
struct event_base *base = app_ctx;
struct bufferevent *bevt = bufferevent_socket_new(base, client_fd, BEV_OPT_CLOSE_ON_FREE);
if (!bevt) return;
/* Chỉ kích hoạt callback khi buffer OUTPUT rỗng (flush hoàn tất) */
bufferevent_setcb(bevt, NULL, on_buf_write_complete, on_buf_event, NULL);
/* Tắt đọc tạm thời, chỉ gửi dữ liệu mẫu */
bufferevent_disable(bevt, EV_READ);
bufferevent_enable(bevt, EV_WRITE);
bufferevent_write(bevt, "Xin chào", 8);
}
Callback ghi chỉ được invoke khi lượng dữ liệu trong output buffer bằng 0 hoặc vượt ngưỡng dưới (low-water-mark). Sau khi flush xong, đối tượng có thể được giải phóng:
static void on_buf_write_complete(struct bufferevent *bev, void *priv) {
struct evbuffer *out = bufferevent_get_output(bev);
if (evbuffer_get_length(out) == 0) {
puts("Dữ liệu đã đẩy hết xuống socket");
bufferevent_free(bev);
}
}
Callback lỗi/sự kiện tổng hợp nắm bắt các trạng thái kết thúc hoặc ngoại lệ đường truyền. Sự kiện BEV_EVENT_EOF xuất hiện khi counterpart đóng kết nối sạch, còn BEV_EVENT_ERROR báo cáo các lỗi network hoặc protocol. Việc gọi bufferevent_free trong nhánh này kích hoạt chuỗi dọn dẹp đệ quy: giải phóng bộ đệm, đóng socket gốc, và xóa khỏi hash table của event_base.
static void on_buf_event(struct bufferevent *bev, short what, void *arg) {
if (what & BEV_EVENT_EOF) {
puts("Client ngắt kết nối bình thường.");
} else if (what & BEV_EVENT_ERROR) {
fprintf(stderr, "Lỗi network: %s\n", strerror(errno));
}
/* Luôn giải phóng để tránh memory leak */
bufferevent_free(bev);
}
Cấu trúc trên minh họa cách libevent tách biệt vòng lặp nhân (kernel polling) khỏi logic nghiệp vụ, cho phép nhà phát triển tập trung vào xử lý trạng thái và luồng dữ liệu thay vì chi tiết底层 IO đa nhiệm.