原生 epoll 版本服務器出現詭異問題,需快速切換至成熟事件庫 libev 上線。本文記錄一個功能完整的 Echo 服務器範例,包含連接接受、數據收發、心跳定時器、資源釋放,適合作為生產環境骨架參考。
ev_default_loop() + ev_run()。ev_io 監聽 EV_READ 事件,分 accept_cb (監聽 socket) 與 read_cb (客戶端 socket)。ev_timer 週期性發送心跳 (範例中註釋啟用)。libevlist[MAX_ALLOWED_CLIENT] 映射 fd → ev_io*,O(1) 查找。[c]
#include
#include
#include
#include
#include
#include
#include
#define PORT 57789
#define BUFFER_SIZE 2048
#define MAX_ALLOWED_CLIENT 10240
struct ev_io *libevlist[MAX_ALLOWED_CLIENT] = {NULL};
void accept_cb(struct ev_loop *loop, struct ev_io *watcher, int revents);
void read_cb(struct ev_loop *loop, struct ev_io *watcher, int revents);
void timer_beat(struct ev_loop *loop, struct ev_timer *watcher, int revents);
int freelibev(struct ev_loop *loop, int fd);
int main() {
struct ev_loop *loop = ev_default_loop(0);
int sd;
struct sockaddr_in addr;
int addr_len = sizeof(addr);
struct ev_io socket_accept;
struct ev_timer timeout_w;
sd = socket(PF_INET, SOCK_STREAM, 0);
if (sd < 0) { perror("socket"); return -1; }
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0) { perror("bind"); return -1; }
if (listen(sd, SOMAXCONN) < 0) { perror("listen"); return -1; }
int reuse = 1;
setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
ev_io_init(&socket_accept, accept_cb, sd, EV_READ);
ev_io_start(loop, &socket_accept);
// 心跳定時器 (每 2 秒)
// ev_timer_init(&timeout_w, timer_beat, 2., 2.);
// ev_timer_start(loop, &timeout_w);
ev_run(loop, 0);
return 0;
}
void accept_cb(struct ev_loop *loop, struct ev_io *watcher, int revents) {
if (EV_ERROR & revents) { perror("accept error event"); return; }
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_sd = accept(watcher->fd, (struct sockaddr*)&client_addr, &client_len);
if (client_sd < 0) { perror("accept"); return; }
if (client_sd >= MAX_ALLOWED_CLIENT) {
fprintf(stderr, "fd too large [%d]\n", client_sd);
close(client_sd);
return;
}
if (libevlist[client_sd] != NULL) {
fprintf(stderr, "fd [%d] already in use\n", client_sd);
close(client_sd);
return;
}
struct ev_io *w_client = malloc(sizeof(struct ev_io));
if (!w_client) { perror("malloc"); close(client_sd); return; }
ev_io_init(w_client, read_cb, client_sd, EV_READ);
ev_io_start(loop, w_client);
libevlist[client_sd] = w_client;
printf("client connected: fd=%d\n", client_sd);
}
void read_cb(struct ev_loop *loop, struct ev_io *watcher, int revents) {
if (EV_ERROR & revents) { perror("read error event"); freelibev(loop, watcher->fd); return; }
char buffer[BUFFER_SIZE];
ssize_t n = recv(watcher->fd, buffer, BUFFER_SIZE, 0);
if (n < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK) perror("recv");
return;
}
if (n == 0) { // 客戶端正常關閉
printf("client disconnected: fd=%d\n", watcher->fd);
freelibev(loop, watcher->fd);
return;
}
printf("recv fd=%d: %.*s\n", watcher->fd, (int)n, buffer);
send(watcher->fd, buffer, n, 0); // Echo 回傳
}
void timer_beat(struct ev_loop *loop, struct ev_timer *watcher, int revents) {
if (EV_ERROR & revents) return;
printf("heartbeat\n");
// 此處可遍歷 libevlist 發送心跳包
}
int freelibev(struct ev_loop *loop, int fd) {
if (fd >= MAX_ALLOWED_CLIENT || libevlist[fd] == NULL) return -1;
close(fd);
ev_io_stop(loop, libevlist[fd]);
free(libevlist[fd]);
libevlist[fd] = NULL;
return 1;
}
[makefile]
CC = gcc
CFLAGS = -I. -I/path/to/libev/include -Wall -O2
LDFLAGS = -L/path/to/libev/lib -lev
OBJS = server.o
TARGET = server
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
%.o: %.c
$(CC) $(CFLAGS) -c $<
clean:
rm -f $(OBJS) $(TARGET)
epoll 原生 epoll_data:靜態陣列限制最大連接數且浪費記憶體 (稀疏 fd)。buffer_t (read/write 索引、擴容) 替代棧上固定陣列,支援大包、分包、粘包處理。fcntl(fd, F_SETFL, O_NONBLOCK) + ev_io_set(..., EV_READ | EV_WRITE) 手動控制寫事件,避免忙輪詢。SIGINT/SIGTERM,ev_break(loop, EVBREAK_ALL) 後遍歷釋放所有 watcher。ev_loop_new(EVFLAG_NOSIGMASK) 每執行緒一循環,主執行緒負責 accept,分發至工作執行緒循環 (需 ev_async 通知)。